Skip to content
This repository has been archived by the owner on Sep 12, 2024. It is now read-only.

Mypy symbol resolve #537

Merged
merged 33 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1c59831
Extracting nodde info from nameExpr
mgtm98 Jul 27, 2024
4c5ee9e
Doing PyRaise based on extracting python dependacies using MyPy
mgtm98 Jul 27, 2024
3a49bbe
Merge branch 'main' into mypy_symbol_resolve
marsninja Jul 29, 2024
8f45096
Merge branch 'main' into mypy_symbol_resolve
marsninja Jul 29, 2024
4abaff1
Importing python module the same way jac files are included
mgtm98 Aug 1, 2024
f80c902
Support from import in python imports, handled the linking to symbol …
mgtm98 Aug 3, 2024
f3018fd
Adding test for python module raising
mgtm98 Aug 3, 2024
9dbe1f2
Support import alias with pythin raise
mgtm98 Aug 4, 2024
6447817
Run symbol table link check only on Jac modules
mgtm98 Aug 4, 2024
a46aeb2
Merge pull request #557 from Jaseci-Labs/main
mgtm98 Aug 6, 2024
b8e0a49
Add the module path to mypy path to correctly detect python imports i…
mgtm98 Aug 6, 2024
8a2ef5a
Allow tree printer to print raised python modules
mgtm98 Aug 6, 2024
3c9b020
Disable symbol link warning with the name token that indicate the typ…
mgtm98 Aug 6, 2024
0c24374
Fixing tests
mgtm98 Aug 6, 2024
93b6bb4
Initial implementation to populate type symbol table info from import…
mgtm98 Aug 8, 2024
a66e91e
chore: man merge
marsninja Aug 10, 2024
accae9c
refactor: for linter passing and cleanliness
marsninja Aug 11, 2024
7af5574
refactor: more descriptive name
marsninja Aug 11, 2024
3d8e4b4
test: removed random stragler
marsninja Aug 11, 2024
ff6eeae
Removing print statements from jac engine
mgtm98 Aug 12, 2024
67d422f
Merge branch 'mypy_symbol_resolve' of https://github.com/Jaseci-Labs/…
mgtm98 Aug 12, 2024
4bc514e
Replacing pygame to a mock python module in tests
mgtm98 Aug 12, 2024
c142e93
Fixing a test issue
mgtm98 Aug 12, 2024
06a7bc7
Fixing an issue with JacImportPass
mgtm98 Aug 13, 2024
97964fd
man merge
marsninja Aug 15, 2024
66523fe
test: many more passing now
marsninja Aug 15, 2024
afcc974
test: i think im making the fixture better
marsninja Aug 15, 2024
3761ebd
test: i think im making the fixture better
marsninja Aug 15, 2024
a9a658f
test: better
marsninja Aug 15, 2024
b947ee1
feat: can now follow python symbols
marsninja Aug 15, 2024
a2fe74e
refactor: cleanup
marsninja Aug 15, 2024
fff63a3
refactor: cleanup
marsninja Aug 15, 2024
dc84535
refactor: clean up glob var hack
marsninja Aug 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ repos:
hooks:
- id: flake8
additional_dependencies: [pep8-naming, flake8_import_order, flake8_docstrings, flake8_comprehensions, flake8_bugbear, flake8_annotations, flake8_simplify]
exclude: "examples|vendor|langserve/tests"
exclude: "examples|vendor|langserve/tests|pygame_mock"
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.1
hooks:
Expand Down
3 changes: 3 additions & 0 deletions jaclang/compiler/absyntree.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,8 +630,11 @@ def __init__(
self.impl_mod: list[Module] = []
self.test_mod: list[Module] = []
self.mod_deps: dict[str, Module] = {}
self.py_mod_dep_map: dict[str, str] = {}
self.py_raise_map: dict[str, str] = {}
self.registry = registry
self.terminals: list[Token] = terminals
self.is_raised_from_py: bool = False
AstNode.__init__(self, kid=kid)
AstDocNode.__init__(self, doc=doc)

Expand Down
2 changes: 1 addition & 1 deletion jaclang/compiler/passes/main/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Collection of passes for Jac IR."""

from .sub_node_tab_pass import SubNodeTabPass
from .import_pass import JacImportPass, PyImportPass # noqa: I100
from .sym_tab_build_pass import SymTabBuildPass # noqa: I100
from .import_pass import JacImportPass, PyImportPass # noqa: I100
from .def_impl_match_pass import DeclImplMatchPass # noqa: I100
from .def_use_pass import DefUsePass # noqa: I100
from .pyout_pass import PyOutPass # noqa: I100
Expand Down
14 changes: 12 additions & 2 deletions jaclang/compiler/passes/main/access_modifier_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,20 @@ def after_pass(self) -> None:
"""After pass."""
pass

def exit_node(self, node: ast.AstNode) -> None:
def exit_node(self, node: ast.AstNode) -> None: # TODO: Move to debug pass
"""Exit node."""
super().exit_node(node)
if settings.lsp_debug and isinstance(node, ast.NameAtom) and not node.sym:
if (
settings.lsp_debug
and isinstance(node, ast.NameAtom)
and not node.sym
and not node.parent_of_type(ast.Module).is_raised_from_py
and not (
node.sym_name == "py"
and node.parent
and isinstance(node.parent.parent, ast.Import)
)
):
self.warning(f"Name {node.sym_name} not present in symbol table")

def access_check(self, node: ast.Name) -> None:
Expand Down
62 changes: 44 additions & 18 deletions jaclang/compiler/passes/main/fuse_typeinfo_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

import jaclang.compiler.absyntree as ast
from jaclang.compiler.passes import Pass
from jaclang.compiler.symtable import SymbolTable
from jaclang.settings import settings
from jaclang.utils.helpers import pascal_to_snake
from jaclang.vendor.mypy.nodes import Node as VNode # bit of a hack
Expand Down Expand Up @@ -65,6 +64,35 @@ def __set_sym_table_link(self, node: ast.AstSymbolNode) -> None:
if typ_sym_table != self.ir.sym_tab:
node.name_spec.type_sym_tab = typ_sym_table

def __collect_python_dependencies(self, node: ast.AstNode) -> None:
assert isinstance(node, ast.AstSymbolNode)
assert isinstance(self.ir, ast.Module)

mypy_node = node.gen.mypy_ast[0]

if isinstance(mypy_node, MypyNodes.RefExpr):
node_full_name = mypy_node.node.fullname
if "." in node_full_name:
mod_name = node_full_name[: node_full_name.rindex(".")]
else:
mod_name = node_full_name

if mod_name not in self.ir.py_mod_dep_map:
self.__debug_print(
f"Can't find a python file associated with {type(node)}::{node.loc}"
)
return

mode_path = self.ir.py_mod_dep_map[mod_name]
if mode_path.endswith(".jac"):
return

self.ir.py_raise_map[mod_name] = mode_path
else:
self.__debug_print(
f"Collect python dependencies is not supported in {type(node)}::{node.loc}"
)

@staticmethod
def __handle_node(
func: Callable[[FuseTypeInfoPass, T], None]
Expand All @@ -86,6 +114,7 @@ def node_handler(self: FuseTypeInfoPass, node: T) -> None:
if len(node.gen.mypy_ast) == 1:
func(self, node)
self.__set_sym_table_link(node)
self.__collect_python_dependencies(node)

# Jac node has multiple mypy nodes linked to it
elif len(node.gen.mypy_ast) > 1:
Expand All @@ -109,6 +138,7 @@ def node_handler(self: FuseTypeInfoPass, node: T) -> None:
)
func(self, node)
self.__set_sym_table_link(node)
self.__collect_python_dependencies(node)

# Jac node doesn't have mypy nodes linked to it
else:
Expand Down Expand Up @@ -446,20 +476,16 @@ def exit_assignment(self, node: ast.Assignment) -> None:
if self_obj.type_sym_tab and isinstance(right_obj, ast.AstSymbolNode):
self_obj.type_sym_tab.def_insert(right_obj)

def exit_name(self, node: ast.Name) -> None:
"""Add new symbols in the symbol table in case of atom trailer."""
if isinstance(node.parent, ast.AtomTrailer):
target_node = node.parent.target
if isinstance(target_node, ast.AstSymbolNode):
parent_symbol_table = target_node.type_sym_tab
if isinstance(parent_symbol_table, SymbolTable):
node.sym = parent_symbol_table.lookup(node.sym_name)

# def exit_in_for_stmt(self, node: ast.InForStmt):
# print(node.loc.mod_path, node.loc)
# print(node.target, node.target.loc)
# # node.sym_tab.def_insert()
# # exit()

# def after_pass(self) -> None:
# exit()
def exit_atom_trailer(self, node: ast.AtomTrailer) -> None:
"""Adding symbol links to AtomTrailer right nodes."""
# This will fix adding the symbol links to nodes in atom trailer
# self.x.z = 5 # will add symbol links to both x and z
for i in range(1, len(node.as_attr_list)):
left = node.as_attr_list[i - 1]
right = node.as_attr_list[i]
# assert isinstance(left, ast.NameAtom)
# assert isinstance(right, ast.NameAtom)
if left.type_sym_tab and not isinstance(
right, ast.IndexSlice
): # TODO check why IndexSlice produce an issue
right.name_spec.sym = left.type_sym_tab.lookup(right.sym_name)
147 changes: 88 additions & 59 deletions jaclang/compiler/passes/main/import_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,16 @@
"""

import ast as py_ast
import importlib.util
import os
import sys
from typing import Optional


import jaclang.compiler.absyntree as ast
from jaclang.compiler.passes import Pass
from jaclang.compiler.passes.main import SubNodeTabPass
from jaclang.settings import settings
from jaclang.utils.helpers import is_standard_lib_module
from jaclang.compiler.passes.main import SubNodeTabPass, SymTabBuildPass
from jaclang.utils.log import logging


logger = logging.getLogger(__name__)


Expand All @@ -28,37 +25,29 @@ class JacImportPass(Pass):
def before_pass(self) -> None:
"""Run once before pass."""
self.import_table: dict[str, ast.Module] = {}
self.__py_imports: set[str] = set()
self.py_resolve_list: set[str] = set()

def enter_module(self, node: ast.Module) -> None:
"""Run Importer."""
self.cur_node = node
self.import_table[node.loc.mod_path] = node
self.__py_imports.clear()
self.__annex_impl(node)
self.annex_impl(node)
self.terminate() # Turns off auto traversal for deliberate traversal
self.run_again = True
while self.run_again:
self.run_again = False
all_imports = self.get_all_sub_nodes(node, ast.ModulePath)
for i in all_imports:
self.process_import(node, i)
self.process_import(i)
self.enter_module_path(i)
SubNodeTabPass(prior=self, input_ir=node)

for i in self.get_all_sub_nodes(node, ast.AtomTrailer):
self.enter_atom_trailer(i)

node.mod_deps = self.import_table
node.mod_deps.update(self.import_table)

def process_import(self, node: ast.Module, i: ast.ModulePath) -> None:
def process_import(self, i: ast.ModulePath) -> None:
"""Process an import."""
imp_node = i.parent_of_type(ast.Import)
if imp_node.is_jac and not i.sub_module:
self.import_jac_module(node=i)
elif imp_node.is_py:
self.__py_imports.add(i.path_str)

def attach_mod_to_node(
self, node: ast.ModulePath | ast.ModuleItem, mod: ast.Module | None
Expand All @@ -67,10 +56,11 @@ def attach_mod_to_node(
if mod:
self.run_again = True
node.sub_module = mod
self.__annex_impl(mod)
self.annex_impl(mod)
node.add_kids_right([mod], pos_update=False)
mod.parent = node

def __annex_impl(self, node: ast.Module) -> None:
def annex_impl(self, node: ast.Module) -> None:
"""Annex impl and test modules."""
if node.stub_only:
return
Expand Down Expand Up @@ -131,11 +121,6 @@ def enter_module_path(self, node: ast.ModulePath) -> None:
node.sub_module.name = node.alias.value
# Items matched during def/decl pass

def enter_atom_trailer(self, node: ast.AtomTrailer) -> None:
"""Iterate on AtomTrailer nodes to get python paths to resolve."""
if node.as_attr_list[0].sym_name in self.__py_imports:
self.py_resolve_list.add(".".join([i.sym_name for i in node.as_attr_list]))

def import_jac_module(self, node: ast.ModulePath) -> None:
"""Import a module."""
self.cur_node = node # impacts error reporting
Expand Down Expand Up @@ -214,58 +199,102 @@ class PyImportPass(JacImportPass):
def before_pass(self) -> None:
"""Only run pass if settings are set to raise python."""
super().before_pass()
if not settings.py_raise:
self.terminate()

def process_import(self, node: ast.Module, i: ast.ModulePath) -> None:
def __get_current_module(self, node: ast.AstNode) -> str:
parent = node.find_parent_of_type(ast.Module)
mod_list = []
while parent is not None:
mod_list.append(parent)
parent = parent.find_parent_of_type(ast.Module)
mod_list.reverse()
return ".".join(p.name for p in mod_list)

def process_import(self, i: ast.ModulePath) -> None:
"""Process an import."""
# Process import is orginally implemented to handle ModulePath in Jaclang
# This won't work with py imports as this will fail to import stuff in form of
# from a import b
# from a import (c, d, e)
# Solution to that is to get the import node and check the from loc then
# handle it based on if there a from loc or not
imp_node = i.parent_of_type(ast.Import)
if (
imp_node.is_py
and not i.sub_module
and not is_standard_lib_module(i.path_str)
):
mod = self.import_py_module(node=i, mod_path=node.loc.mod_path)
if mod:
i.sub_module = mod
i.add_kids_right([mod], pos_update=False)
if settings.py_raise_deep:
self.run_again = True
if imp_node.is_py and not i.sub_module:
if imp_node.from_loc:
for j in imp_node.items.items:
assert isinstance(j, ast.ModuleItem)
mod_path = f"{imp_node.from_loc.path_str}.{j.name.sym_name}"
self.import_py_module(
parent_node=j,
mod_path=mod_path,
imported_mod_name=(
j.name.sym_name if not j.alias else j.alias.sym_name
),
)
else:
for j in imp_node.items.items:
assert isinstance(j, ast.ModulePath)
self.import_py_module(
parent_node=j,
mod_path=j.path_str,
imported_mod_name=(
j.path_str.replace(".", "")
if not j.alias
else j.alias.sym_name
),
)

def import_py_module(
self, node: ast.ModulePath, mod_path: str
self,
parent_node: ast.ModulePath | ast.ModuleItem,
imported_mod_name: str,
mod_path: str,
) -> Optional[ast.Module]:
"""Import a module."""
from jaclang.compiler.passes.main import PyastBuildPass

base_dir = os.path.dirname(mod_path)
sys.path.append(base_dir)
assert isinstance(self.ir, ast.Module)

python_raise_map = self.ir.py_raise_map
file_to_raise = None

if mod_path in python_raise_map:
file_to_raise = python_raise_map[mod_path]
else:
resolved_mod_path = (
f"{self.__get_current_module(parent_node)}.{imported_mod_name}"
)
assert isinstance(self.ir, ast.Module)
resolved_mod_path = resolved_mod_path.replace(f"{self.ir.name}.", "")
file_to_raise = python_raise_map.get(resolved_mod_path)

if file_to_raise is None:
return None

try:
# Dynamically import the module
spec = importlib.util.find_spec(node.path_str)
sys.path.remove(base_dir)
if spec and spec.origin and spec.origin not in {None, "built-in", "frozen"}:
if spec.origin in self.import_table:
return self.import_table[spec.origin]
with open(spec.origin, "r", encoding="utf-8") as f:
if file_to_raise not in {None, "built-in", "frozen"}:
if file_to_raise in self.import_table:
return self.import_table[file_to_raise]

with open(file_to_raise, "r", encoding="utf-8") as f:
mod = PyastBuildPass(
input_ir=ast.PythonModuleAst(
py_ast.parse(f.read()), mod_path=spec.origin
py_ast.parse(f.read()), mod_path=file_to_raise
),
).ir
SubNodeTabPass(input_ir=mod, prior=self)
if mod:
self.import_table[spec.origin] = mod
mod.name = imported_mod_name
self.import_table[file_to_raise] = mod
self.attach_mod_to_node(parent_node, mod)
SymTabBuildPass(input_ir=mod, prior=self)
return mod
else:
raise self.ice(
f"Failed to import python module {node.path_str}: {spec.origin}"
)
raise self.ice(f"Failed to import python module {mod_path}")
except Exception as e:
if "Empty kid for Token ModulePath" in str(e) or "utf-8" in str(e): # FIXME
return None
self.error(
f"Failed to import python module {node.path_str}: {e}",
node_override=node,
)
self.error(f"Failed to import python module {mod_path}")
raise e
return None

def annex_impl(self, node: ast.Module) -> None:
"""Annex impl and test modules."""
return None
1 change: 1 addition & 0 deletions jaclang/compiler/passes/main/pyast_load_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class Module(mod):
is_imported=False,
kid=valid,
)
ret.is_raised_from_py = True
return self.nu(ret)

def proc_function_def(
Expand Down
Loading
Loading