jaclang 0.7.27__py3-none-any.whl → 0.7.29__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of jaclang might be problematic. Click here for more details.

Files changed (44) hide show
  1. jaclang/cli/cli.py +3 -0
  2. jaclang/compiler/absyntree.py +18 -3
  3. jaclang/compiler/passes/main/__init__.py +1 -1
  4. jaclang/compiler/passes/main/access_modifier_pass.py +1 -1
  5. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +62 -33
  6. jaclang/compiler/passes/main/import_pass.py +284 -63
  7. jaclang/compiler/passes/main/inheritance_pass.py +103 -0
  8. jaclang/compiler/passes/main/py_collect_dep_pass.py +5 -5
  9. jaclang/compiler/passes/main/pyast_load_pass.py +1 -1
  10. jaclang/compiler/passes/main/schedules.py +2 -0
  11. jaclang/compiler/passes/main/sym_tab_build_pass.py +17 -0
  12. jaclang/compiler/passes/main/tests/fixtures/data_spatial_types.jac +130 -0
  13. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/__init__.py +3 -3
  14. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/__init__.pyi +3 -3
  15. jaclang/compiler/passes/main/tests/test_import_pass.py +13 -17
  16. jaclang/compiler/passes/main/tests/test_type_check_pass.py +24 -0
  17. jaclang/compiler/passes/main/type_check_pass.py +3 -2
  18. jaclang/compiler/py_info.py +22 -0
  19. jaclang/compiler/symtable.py +9 -2
  20. jaclang/langserve/tests/test_server.py +2 -2
  21. jaclang/plugin/default.py +82 -50
  22. jaclang/plugin/feature.py +2 -0
  23. jaclang/plugin/spec.py +1 -0
  24. jaclang/runtimelib/architype.py +18 -14
  25. jaclang/runtimelib/test.py +59 -4
  26. jaclang/runtimelib/utils.py +15 -0
  27. jaclang/settings.py +3 -0
  28. jaclang/tests/fixtures/base_class1.jac +11 -0
  29. jaclang/tests/fixtures/base_class2.jac +11 -0
  30. jaclang/tests/fixtures/import_all.jac +7 -0
  31. jaclang/tests/fixtures/import_all_py.py +8 -0
  32. jaclang/tests/fixtures/jactest_imported.jac +6 -0
  33. jaclang/tests/fixtures/jactest_main.jac +22 -0
  34. jaclang/tests/fixtures/multi_dim_array_split.jac +2 -6
  35. jaclang/tests/fixtures/test_py.py +12 -0
  36. jaclang/tests/fixtures/visit_sequence.jac +50 -0
  37. jaclang/tests/test_cli.py +82 -0
  38. jaclang/tests/test_language.py +24 -9
  39. jaclang/utils/helpers.py +9 -1
  40. jaclang/utils/treeprinter.py +6 -3
  41. {jaclang-0.7.27.dist-info → jaclang-0.7.29.dist-info}/METADATA +2 -2
  42. {jaclang-0.7.27.dist-info → jaclang-0.7.29.dist-info}/RECORD +44 -33
  43. {jaclang-0.7.27.dist-info → jaclang-0.7.29.dist-info}/WHEEL +1 -1
  44. {jaclang-0.7.27.dist-info → jaclang-0.7.29.dist-info}/entry_points.txt +0 -0
jaclang/cli/cli.py CHANGED
@@ -307,6 +307,7 @@ def enter(
307
307
  @cmd_registry.register
308
308
  def test(
309
309
  filepath: str,
310
+ test_name: str = "",
310
311
  filter: str = "",
311
312
  xit: bool = False,
312
313
  maxfail: int = None, # type:ignore
@@ -316,6 +317,7 @@ def test(
316
317
  """Run the test suite in the specified .jac file.
317
318
 
318
319
  :param filepath: Path/to/file.jac
320
+ :param test_name: Run a specific test.
319
321
  :param filter: Filter the files using Unix shell style conventions.
320
322
  :param xit(exit): Stop(exit) running tests as soon as finds an error.
321
323
  :param maxfail: Stop running tests after n failures.
@@ -328,6 +330,7 @@ def test(
328
330
 
329
331
  failcount = Jac.run_test(
330
332
  filepath=filepath,
333
+ func_name=("test_" + test_name) if test_name else None,
331
334
  filter=filter,
332
335
  xit=xit,
333
336
  maxfail=maxfail,
@@ -30,6 +30,7 @@ from jaclang.compiler.constant import (
30
30
  SymbolType,
31
31
  )
32
32
  from jaclang.compiler.constant import DELIM_MAP, SymbolAccess, Tokens as Tok
33
+ from jaclang.compiler.py_info import PyInfo
33
34
  from jaclang.compiler.semtable import SemRegistry
34
35
  from jaclang.utils.treeprinter import dotgen_ast_tree, print_ast_tree
35
36
 
@@ -636,11 +637,10 @@ class Module(AstDocNode):
636
637
  self.impl_mod: list[Module] = []
637
638
  self.test_mod: list[Module] = []
638
639
  self.mod_deps: dict[str, Module] = {}
639
- self.py_mod_dep_map: dict[str, str] = {}
640
- self.py_raise_map: dict[str, str] = {}
641
640
  self.registry = registry
642
641
  self.terminals: list[Token] = terminals
643
- self.is_raised_from_py: bool = False
642
+ self.py_info: PyInfo = PyInfo()
643
+
644
644
  AstNode.__init__(self, kid=kid)
645
645
  AstDocNode.__init__(self, doc=doc)
646
646
 
@@ -693,6 +693,21 @@ class Module(AstDocNode):
693
693
  super().unparse()
694
694
  return self.format()
695
695
 
696
+ @staticmethod
697
+ def get_href_path(node: AstNode) -> str:
698
+ """Return the full path of the module that contains this node."""
699
+ parent = node.find_parent_of_type(Module)
700
+ mod_list: list[Module | Architype] = []
701
+ if isinstance(node, (Module, Architype)):
702
+ mod_list.append(node)
703
+ while parent is not None:
704
+ mod_list.append(parent)
705
+ parent = parent.find_parent_of_type(Module)
706
+ mod_list.reverse()
707
+ return ".".join(
708
+ p.name if isinstance(p, Module) else p.name.sym_name for p in mod_list
709
+ )
710
+
696
711
 
697
712
  class GlobalVars(ElementStmt, AstAccessNode):
698
713
  """GlobalVars node type for Jac Ast."""
@@ -2,9 +2,9 @@
2
2
 
3
3
  from .sub_node_tab_pass import SubNodeTabPass
4
4
  from .sym_tab_build_pass import SymTabBuildPass # noqa: I100
5
+ from .def_use_pass import DefUsePass # noqa: I100
5
6
  from .import_pass import JacImportPass, PyImportPass # noqa: I100
6
7
  from .def_impl_match_pass import DeclImplMatchPass # noqa: I100
7
- from .def_use_pass import DefUsePass # noqa: I100
8
8
  from .pyout_pass import PyOutPass # noqa: I100
9
9
  from .pyast_load_pass import PyastBuildPass # type: ignore # noqa: I100
10
10
  from .pyast_gen_pass import PyastGenPass # noqa: I100
@@ -45,7 +45,7 @@ class AccessCheckPass(Pass):
45
45
  settings.lsp_debug
46
46
  and isinstance(node, ast.NameAtom)
47
47
  and not node.sym
48
- and not node.parent_of_type(ast.Module).is_raised_from_py
48
+ and not node.parent_of_type(ast.Module).py_info.is_raised_from_py
49
49
  and not (
50
50
  node.sym_name == "py"
51
51
  and node.parent
@@ -10,6 +10,7 @@ import re
10
10
  from typing import Callable, Optional, TypeVar
11
11
 
12
12
  import jaclang.compiler.absyntree as ast
13
+ from jaclang.compiler.constant import Constants, Tokens
13
14
  from jaclang.compiler.passes import Pass
14
15
  from jaclang.compiler.symtable import SymbolTable
15
16
  from jaclang.settings import settings
@@ -76,35 +77,6 @@ class FuseTypeInfoPass(Pass):
76
77
  if typ_sym_table != self.ir.sym_tab:
77
78
  node.name_spec.type_sym_tab = typ_sym_table
78
79
 
79
- def __collect_python_dependencies(self, node: ast.AstNode) -> None:
80
- assert isinstance(node, ast.AstSymbolNode)
81
- assert isinstance(self.ir, ast.Module)
82
-
83
- mypy_node = node.gen.mypy_ast[0]
84
-
85
- if isinstance(mypy_node, MypyNodes.RefExpr) and mypy_node.node:
86
- node_full_name = mypy_node.node.fullname
87
- if "." in node_full_name:
88
- mod_name = node_full_name[: node_full_name.rindex(".")]
89
- else:
90
- mod_name = node_full_name
91
-
92
- if mod_name not in self.ir.py_mod_dep_map:
93
- self.__debug_print(
94
- f"Can't find a python file associated with {type(node)}::{node.loc}"
95
- )
96
- return
97
-
98
- mode_path = self.ir.py_mod_dep_map[mod_name]
99
- if mode_path.endswith(".jac"):
100
- return
101
-
102
- self.ir.py_raise_map[mod_name] = mode_path
103
- else:
104
- self.__debug_print(
105
- f"Collect python dependencies is not supported in {type(node)}::{node.loc}"
106
- )
107
-
108
80
  @staticmethod
109
81
  def __handle_node(
110
82
  func: Callable[[FuseTypeInfoPass, T], None]
@@ -126,7 +98,6 @@ class FuseTypeInfoPass(Pass):
126
98
  if len(node.gen.mypy_ast) == 1:
127
99
  func(self, node)
128
100
  self.__set_type_sym_table_link(node)
129
- self.__collect_python_dependencies(node)
130
101
 
131
102
  # Jac node has multiple mypy nodes linked to it
132
103
  elif len(node.gen.mypy_ast) > 1:
@@ -150,7 +121,6 @@ class FuseTypeInfoPass(Pass):
150
121
  )
151
122
  func(self, node)
152
123
  self.__set_type_sym_table_link(node)
153
- self.__collect_python_dependencies(node)
154
124
 
155
125
  # Special handing for BuiltinType
156
126
  elif isinstance(node, ast.BuiltinType):
@@ -266,6 +236,43 @@ class FuseTypeInfoPass(Pass):
266
236
  if len(node.gen.mypy_ast) == 0:
267
237
  return
268
238
 
239
+ # No need to run this handling in case of a previous type
240
+ # was set during the pass, if not then we hope that the
241
+ # mypy node has a type associated to it
242
+ if node.expr_type != "NoType":
243
+ return
244
+
245
+ # Check if the expression is a data spatial expression
246
+ # Support disconnectOp
247
+ if isinstance(node, ast.BinaryExpr):
248
+ if isinstance(node.op, ast.DisconnectOp):
249
+ node.expr_type = "builtins.bool"
250
+ return
251
+
252
+ # Support spwan and connectOp
253
+ elif (
254
+ isinstance(node.op, ast.ConnectOp)
255
+ or node.op.name == Tokens.KW_SPAWN.value
256
+ ):
257
+ if node.gen.mypy_ast[-1] in self.node_type_hash:
258
+ node.expr_type = (
259
+ self.__call_type_handler(
260
+ self.node_type_hash[node.gen.mypy_ast[-1]]
261
+ )
262
+ or node.expr_type
263
+ )
264
+ return
265
+
266
+ if isinstance(node, ast.EdgeRefTrailer) and any(
267
+ isinstance(k, ast.FilterCompr) for k in node.kid
268
+ ):
269
+ if node.gen.mypy_ast[-1] in self.node_type_hash:
270
+ node.expr_type = (
271
+ self.__call_type_handler(self.node_type_hash[node.gen.mypy_ast[-1]])
272
+ or node.expr_type
273
+ )
274
+ return
275
+
269
276
  # If the corrosponding mypy ast node type has stored here, get the values.
270
277
  mypy_node = node.gen.mypy_ast[0]
271
278
  if mypy_node in self.node_type_hash:
@@ -427,7 +434,14 @@ class FuseTypeInfoPass(Pass):
427
434
  @__handle_node
428
435
  def enter_special_var_ref(self, node: ast.SpecialVarRef) -> None:
429
436
  """Pass handler for SpecialVarRef nodes."""
430
- return self.enter_name(node)
437
+ if node.py_resolve_name() == Constants.ROOT:
438
+ if node.gen.mypy_ast[-1] in self.node_type_hash:
439
+ node.name_spec.expr_type = (
440
+ self.__call_type_handler(self.node_type_hash[node.gen.mypy_ast[-1]])
441
+ or node.name_spec.expr_type
442
+ )
443
+ else:
444
+ self.enter_name(node)
431
445
 
432
446
  @__handle_node
433
447
  def enter_edge_op_ref(self, node: ast.EdgeOpRef) -> None:
@@ -491,7 +505,7 @@ class FuseTypeInfoPass(Pass):
491
505
  self, mypy_type: MypyTypes.Overloaded
492
506
  ) -> Optional[str]:
493
507
  """Get type info from mypy type Overloaded."""
494
- return self.__call_type_handler(mypy_type.items[0])
508
+ return self.__call_type_handler(mypy_type.items[-1])
495
509
 
496
510
  def get_type_from_none_type(self, mypy_type: MypyTypes.NoneType) -> Optional[str]:
497
511
  """Get type info from mypy type NoneType."""
@@ -509,6 +523,12 @@ class FuseTypeInfoPass(Pass):
509
523
  """Get type info from mypy type TypeType."""
510
524
  return str(mypy_type.item)
511
525
 
526
+ def get_type_from_type_var_type(
527
+ self, mypy_type: MypyTypes.TypeVarType
528
+ ) -> Optional[str]:
529
+ """Get type info from mypy type TypeType."""
530
+ return str(mypy_type.name)
531
+
512
532
  def exit_assignment(self, node: ast.Assignment) -> None:
513
533
  """Add new symbols in the symbol table in case of self."""
514
534
  # This will fix adding new items to the class through self
@@ -590,6 +610,7 @@ class FuseTypeInfoPass(Pass):
590
610
  # right index slice is a range then it's type is the same as left
591
611
  if right.is_range:
592
612
  right.expr_type = left.expr_type
613
+ right.parent_of_type(ast.AtomTrailer).expr_type = node_type
593
614
  continue
594
615
 
595
616
  # left type is a dictionary
@@ -603,6 +624,7 @@ class FuseTypeInfoPass(Pass):
603
624
  continue
604
625
 
605
626
  right.expr_type = node_type
627
+ right.parent_of_type(ast.AtomTrailer).expr_type = node_type
606
628
 
607
629
  # Getting the correct symbol table and link it
608
630
  type_symtab: Optional[SymbolTable] = self.ir.sym_tab
@@ -621,6 +643,13 @@ class FuseTypeInfoPass(Pass):
621
643
  right.type_sym_tab = type_symtab
622
644
 
623
645
  else:
646
+ # Fix the symbolTable linking in case of type annotations
647
+ if left.type_sym_tab is None and isinstance(node.parent, ast.SubTag):
648
+ assert isinstance(left, ast.AstSymbolNode)
649
+ left.name_spec.type_sym_tab = self.ir.sym_tab.find_scope(
650
+ left.sym_name
651
+ )
652
+
624
653
  if left.type_sym_tab:
625
654
  right.name_spec.sym = left.type_sym_tab.lookup(right.sym_name)
626
655
  if right.name_spec.sym:
@@ -12,8 +12,11 @@ from typing import Optional
12
12
 
13
13
 
14
14
  import jaclang.compiler.absyntree as ast
15
+ from jaclang.compiler.constant import SymbolType
15
16
  from jaclang.compiler.passes import Pass
16
- from jaclang.compiler.passes.main import SubNodeTabPass, SymTabBuildPass
17
+ from jaclang.compiler.passes.main import DefUsePass, SubNodeTabPass, SymTabBuildPass
18
+ from jaclang.compiler.passes.main.sym_tab_build_pass import PyInspectSymTabBuildPass
19
+ from jaclang.compiler.symtable import Symbol, SymbolTable
17
20
  from jaclang.settings import settings
18
21
  from jaclang.utils.log import logging
19
22
 
@@ -198,19 +201,20 @@ class JacImportPass(Pass):
198
201
  class PyImportPass(JacImportPass):
199
202
  """Jac statically imports Python modules."""
200
203
 
204
+ def __debug_print(self, msg: str) -> None:
205
+ if settings.py_import_pass_debug:
206
+ self.log_info("[PyImportPass] " + msg)
207
+
201
208
  def before_pass(self) -> None:
202
209
  """Only run pass if settings are set to raise python."""
210
+ self.import_from_build_list: list[tuple[ast.Import, ast.Module]] = []
203
211
  super().before_pass()
204
212
  self.__load_builtins()
205
213
 
206
- def __get_current_module(self, node: ast.AstNode) -> str:
207
- parent = node.find_parent_of_type(ast.Module)
208
- mod_list = []
209
- while parent is not None:
210
- mod_list.append(parent)
211
- parent = parent.find_parent_of_type(ast.Module)
212
- mod_list.reverse()
213
- return ".".join(p.name for p in mod_list)
214
+ def after_pass(self) -> None:
215
+ """Build symbol tables for import from nodes."""
216
+ self.__import_from_symbol_table_build()
217
+ return super().after_pass()
214
218
 
215
219
  def process_import(self, i: ast.ModulePath) -> None:
216
220
  """Process an import."""
@@ -218,87 +222,273 @@ class PyImportPass(JacImportPass):
218
222
  # This won't work with py imports as this will fail to import stuff in form of
219
223
  # from a import b
220
224
  # from a import (c, d, e)
221
- # Solution to that is to get the import node and check the from loc then
225
+ # Solution to that is to get the import node and check the from loc `then`
222
226
  # handle it based on if there a from loc or not
223
227
  imp_node = i.parent_of_type(ast.Import)
228
+
224
229
  if imp_node.is_py and not i.sub_module:
225
230
  if imp_node.from_loc:
226
- for j in imp_node.items.items:
227
- assert isinstance(j, ast.ModuleItem)
228
- mod_path = f"{imp_node.from_loc.dot_path_str}.{j.name.sym_name}"
229
- self.import_py_module(
230
- parent_node=j,
231
- mod_path=mod_path,
232
- imported_mod_name=(
233
- j.name.sym_name if not j.alias else j.alias.sym_name
234
- ),
235
- )
231
+ msg = "Processing import from node at href="
232
+ msg += ast.Module.get_href_path(imp_node)
233
+ msg += f' path="{imp_node.loc.mod_path}, {imp_node.loc}"'
234
+ self.__debug_print(msg)
235
+ self.__process_import_from(imp_node)
236
+ else:
237
+ msg = "Processing import node at href="
238
+ msg += ast.Module.get_href_path(imp_node)
239
+ msg += f' path="{imp_node.loc.mod_path}, {imp_node.loc}"'
240
+ self.__debug_print(msg)
241
+ self.__process_import(imp_node)
242
+
243
+ def __process_import_from(self, imp_node: ast.Import) -> None:
244
+ """Process imports in the form of `from X import I`."""
245
+ assert isinstance(self.ir, ast.Module)
246
+ assert isinstance(imp_node.from_loc, ast.ModulePath)
247
+
248
+ self.__debug_print(f"\tTrying to import {imp_node.from_loc.dot_path_str}")
249
+
250
+ # Attempt to import the Python module X and process it
251
+ imported_mod = self.__import_py_module(
252
+ parent_node_path=ast.Module.get_href_path(imp_node),
253
+ mod_path=imp_node.from_loc.dot_path_str,
254
+ )
255
+
256
+ if imported_mod:
257
+ # Cyclic imports will happen in case of import sys
258
+ # sys stub file imports sys module which means that we need
259
+ # to import sys stub file again in the sys stub file and so on
260
+ # This can be detected by iterating over all the parents and make sure
261
+ # that the parent is in another file than the imported module
262
+ if self.__check_cyclic_imports(imp_node, imported_mod):
263
+ self.__debug_print(
264
+ f"\tCycled imports is found at {imp_node.loc.mod_path} {imp_node.loc}"
265
+ )
266
+ return
267
+
268
+ if imported_mod.name == "builtins":
269
+ self.__debug_print(
270
+ f"\tIgnoring attaching builtins {imp_node.loc.mod_path} {imp_node.loc}"
271
+ )
272
+ return
273
+
274
+ self.__debug_print(
275
+ f"\tAttaching {imported_mod.name} into {ast.Module.get_href_path(imp_node)}"
276
+ )
277
+ msg = f"\tRegistering module:{imported_mod.name} to "
278
+ msg += f"import_from handling with {imp_node.loc.mod_path}:{imp_node.loc}"
279
+ self.__debug_print(msg)
280
+
281
+ self.attach_mod_to_node(imp_node.from_loc, imported_mod)
282
+ self.import_from_build_list.append((imp_node, imported_mod))
283
+ if imported_mod._sym_tab is None:
284
+ self.__debug_print(
285
+ f"\tBuilding symbol table for module:{ast.Module.get_href_path(imported_mod)}"
286
+ )
236
287
  else:
237
- for j in imp_node.items.items:
238
- assert isinstance(j, ast.ModulePath)
239
- self.import_py_module(
240
- parent_node=j,
241
- mod_path=j.dot_path_str,
242
- imported_mod_name=(
243
- j.dot_path_str.replace(".", "")
244
- if not j.alias
245
- else j.alias.sym_name
246
- ),
288
+ self.__debug_print(
289
+ f"\tRefreshing symbol table for module:{ast.Module.get_href_path(imported_mod)}"
290
+ )
291
+ PyInspectSymTabBuildPass(input_ir=imported_mod, prior=self)
292
+ DefUsePass(input_ir=imported_mod, prior=self)
293
+
294
+ def __import_from_symbol_table_build(self) -> None:
295
+ """Build symbol tables for the imported python modules."""
296
+ is_symbol_tabled_refreshed: list[str] = []
297
+ self.import_from_build_list.reverse()
298
+ for imp_node, imported_mod in self.import_from_build_list:
299
+ # Need to build the symbol tables again to make sure that the
300
+ # complete symbol table is built.
301
+ #
302
+ # Complete symbol tables won't be built in case of another
303
+ # import from statements in the imported modules.
304
+ #
305
+ # A solution was to only build the symbol table here after the
306
+ # full ast is raised but this will cause an issue with symboltable
307
+ # building with normal imports
308
+ #
309
+ # TODO: Change normal imports to call symbolTable here too
310
+
311
+ if imported_mod.loc.mod_path not in is_symbol_tabled_refreshed:
312
+ self.__debug_print(
313
+ f"Refreshing symbol table for module:{ast.Module.get_href_path(imported_mod)}"
314
+ )
315
+ PyInspectSymTabBuildPass(input_ir=imported_mod, prior=self)
316
+ DefUsePass(input_ir=imported_mod, prior=self)
317
+ is_symbol_tabled_refreshed.append(imported_mod.loc.mod_path)
318
+
319
+ sym_tab = imported_mod.sym_tab
320
+ parent_sym_tab = imp_node.parent_of_type(ast.Module).sym_tab
321
+
322
+ if imp_node.is_absorb:
323
+ for symbol in sym_tab.tab.values():
324
+ if symbol.sym_type == SymbolType.MODULE:
325
+ continue
326
+ self.__import_from_sym_table_add_symbols(symbol, parent_sym_tab)
327
+ else:
328
+ for i in imp_node.items.items:
329
+ assert isinstance(i, ast.ModuleItem)
330
+ needed_sym = sym_tab.lookup(i.name.sym_name)
331
+
332
+ if needed_sym and needed_sym.defn[0].parent:
333
+ self.__import_from_sym_table_add_symbols(
334
+ needed_sym, parent_sym_tab
335
+ )
336
+ else:
337
+ self.__debug_print(
338
+ f"Can't find a symbol matching {i.name.sym_name} in {sym_tab.name}"
339
+ )
340
+
341
+ def __import_from_sym_table_add_symbols(
342
+ self, sym: Symbol, sym_table: SymbolTable
343
+ ) -> None:
344
+ self.__debug_print(
345
+ f"\tAdding {sym.sym_type}:{sym.sym_name} into {sym_table.name}"
346
+ )
347
+ assert isinstance(sym.defn[0], ast.AstSymbolNode)
348
+ sym_table.def_insert(
349
+ node=sym.defn[0],
350
+ access_spec=sym.access,
351
+ force_overwrite=True,
352
+ )
353
+
354
+ if sym.fetch_sym_tab:
355
+ msg = f"\tAdding SymbolTable:{sym.fetch_sym_tab.name} into "
356
+ msg += f"SymbolTable:{sym_table.name} kids"
357
+ self.__debug_print(msg)
358
+ sym_table.kid.append(sym.fetch_sym_tab)
359
+ elif sym.sym_type not in (SymbolType.VAR, SymbolType.MOD_VAR):
360
+ raise AssertionError(
361
+ f"Unexpected symbol type '{sym.sym_type}' that doesn't have a symbl table"
362
+ )
363
+
364
+ def __process_import(self, imp_node: ast.Import) -> None:
365
+ """Process the imports in form of `import X`."""
366
+ # Expected that each ImportStatement will import one item
367
+ # In case of this assertion fired then we need to revisit this item
368
+ assert len(imp_node.items.items) == 1
369
+ imported_item = imp_node.items.items[0]
370
+ assert isinstance(imported_item, ast.ModulePath)
371
+
372
+ self.__debug_print(f"\tTrying to import {imported_item.dot_path_str}")
373
+ imported_mod = self.__import_py_module(
374
+ parent_node_path=ast.Module.get_href_path(imported_item),
375
+ mod_path=imported_item.dot_path_str,
376
+ imported_mod_name=(
377
+ # TODO: Check this replace
378
+ imported_item.dot_path_str.replace(".", "")
379
+ if not imported_item.alias
380
+ else imported_item.alias.sym_name
381
+ ),
382
+ )
383
+ if imported_mod:
384
+ if self.__check_cyclic_imports(imp_node, imported_mod):
385
+ self.__debug_print(
386
+ f"\tCycled imports is found at {imp_node.loc.mod_path} {imp_node.loc}"
387
+ )
388
+ return
389
+ elif imported_mod.name == "builtins":
390
+ self.__debug_print(
391
+ f"\tIgnoring attaching builtins {imp_node.loc.mod_path} {imp_node.loc}"
392
+ )
393
+ return
394
+
395
+ self.__debug_print(
396
+ f"\tAttaching {imported_mod.name} into {ast.Module.get_href_path(imp_node)}"
397
+ )
398
+ self.attach_mod_to_node(imported_item, imported_mod)
399
+
400
+ if imp_node.is_absorb:
401
+ msg = f"\tRegistering module:{imported_mod.name} to "
402
+ msg += f"import_from (import all) handling with {imp_node.loc.mod_path}:{imp_node.loc}"
403
+ self.__debug_print(msg)
404
+
405
+ self.import_from_build_list.append((imp_node, imported_mod))
406
+ if imported_mod._sym_tab is None:
407
+ self.__debug_print(
408
+ f"\tBuilding symbol table for module:{ast.Module.get_href_path(imported_mod)}"
409
+ )
410
+ else:
411
+ self.__debug_print(
412
+ f"\tRefreshing symbol table for module:{ast.Module.get_href_path(imported_mod)}"
247
413
  )
414
+ PyInspectSymTabBuildPass(input_ir=imported_mod, prior=self)
415
+ DefUsePass(input_ir=imported_mod, prior=self)
416
+
417
+ else:
418
+ self.__debug_print(
419
+ f"\tBuilding symbol table for module:{ast.Module.get_href_path(imported_mod)}"
420
+ )
421
+ SymTabBuildPass(input_ir=imported_mod, prior=self)
248
422
 
249
- def import_py_module(
423
+ def __import_py_module(
250
424
  self,
251
- parent_node: ast.ModulePath | ast.ModuleItem,
252
- imported_mod_name: str,
425
+ parent_node_path: str,
253
426
  mod_path: str,
427
+ imported_mod_name: Optional[str] = None,
254
428
  ) -> Optional[ast.Module]:
255
- """Import a module."""
429
+ """Import a python module."""
256
430
  from jaclang.compiler.passes.main import PyastBuildPass
257
431
 
258
432
  assert isinstance(self.ir, ast.Module)
259
433
 
260
- python_raise_map = self.ir.py_raise_map
261
- file_to_raise = None
434
+ python_raise_map = self.ir.py_info.py_raise_map
435
+ file_to_raise: Optional[str] = None
262
436
 
263
437
  if mod_path in python_raise_map:
264
438
  file_to_raise = python_raise_map[mod_path]
265
439
  else:
266
- resolved_mod_path = (
267
- f"{self.__get_current_module(parent_node)}.{imported_mod_name}"
268
- )
269
- assert isinstance(self.ir, ast.Module)
440
+ # TODO: Is it fine to use imported_mod_name or get it from mod_path?
441
+ resolved_mod_path = f"{parent_node_path}.{mod_path}"
442
+ resolved_mod_path = resolved_mod_path.replace("..", ".")
270
443
  resolved_mod_path = resolved_mod_path.replace(f"{self.ir.name}.", "")
271
444
  file_to_raise = python_raise_map.get(resolved_mod_path)
272
445
 
273
446
  if file_to_raise is None:
447
+ self.__debug_print("\tNo file is found to do the import")
274
448
  return None
275
449
 
450
+ self.__debug_print(f"\tFile used to do the import is {file_to_raise}")
451
+
276
452
  try:
277
- if file_to_raise not in {None, "built-in", "frozen"}:
278
- if file_to_raise in self.import_table:
279
- return self.import_table[file_to_raise]
280
-
281
- with open(file_to_raise, "r", encoding="utf-8") as f:
282
- file_source = f.read()
283
- mod = PyastBuildPass(
284
- input_ir=ast.PythonModuleAst(
285
- py_ast.parse(file_source),
286
- orig_src=ast.JacSource(file_source, file_to_raise),
287
- ),
288
- ).ir
289
- SubNodeTabPass(input_ir=mod, prior=self)
290
- if mod:
291
- mod.name = imported_mod_name
292
- self.import_table[file_to_raise] = mod
293
- self.attach_mod_to_node(parent_node, mod)
294
- SymTabBuildPass(input_ir=mod, prior=self)
295
- return mod
296
- else:
297
- raise self.ice(f"Failed to import python module {mod_path}")
453
+ if file_to_raise in {None, "built-in", "frozen"}:
454
+ return None
455
+
456
+ if file_to_raise in self.import_table:
457
+ self.__debug_print(
458
+ f"\t{file_to_raise} was raised before, getting it from cache"
459
+ )
460
+ return self.import_table[file_to_raise]
461
+
462
+ with open(file_to_raise, "r", encoding="utf-8") as f:
463
+ file_source = f.read()
464
+ mod = PyastBuildPass(
465
+ input_ir=ast.PythonModuleAst(
466
+ py_ast.parse(file_source),
467
+ orig_src=ast.JacSource(file_source, file_to_raise),
468
+ ),
469
+ ).ir
470
+ SubNodeTabPass(input_ir=mod, prior=self)
471
+
472
+ if mod:
473
+ mod.name = imported_mod_name if imported_mod_name else mod.name
474
+ if mod.name == "__init__":
475
+ mod_name = mod.loc.mod_path.split("/")[-2]
476
+ self.__debug_print(
477
+ f"\tRaised the __init__ file and rename the mod to be {mod_name}"
478
+ )
479
+ mod.name = mod_name
480
+ self.import_table[file_to_raise] = mod
481
+ mod.py_info.is_raised_from_py = True
482
+ self.__debug_print(
483
+ f"\t{file_to_raise} is raised, adding it to the cache"
484
+ )
485
+ return mod
486
+ else:
487
+ raise self.ice(f"\tFailed to import python module {mod_path}")
488
+
298
489
  except Exception as e:
299
- self.error(f"Failed to import python module {mod_path}")
490
+ self.error(f"\tFailed to import python module {mod_path}")
300
491
  raise e
301
- return None
302
492
 
303
493
  def __load_builtins(self) -> None:
304
494
  """Pyraise builtins to help with builtins auto complete."""
@@ -330,3 +520,34 @@ class PyImportPass(JacImportPass):
330
520
  def annex_impl(self, node: ast.Module) -> None:
331
521
  """Annex impl and test modules."""
332
522
  return None
523
+
524
+ def __handle_different_site_packages(self, mod_path: str) -> str:
525
+ if "site-packages" in mod_path:
526
+ mod_path = mod_path[mod_path.index("site-packages") :]
527
+ return mod_path
528
+
529
+ def __check_cyclic_imports(
530
+ self, imp_node: ast.AstNode, imported_module: ast.Module
531
+ ) -> bool:
532
+ """Check cyclic imports that might happen."""
533
+ # Example of cyclic imports is import os
534
+ # In the os stub file it imports the real os module which will cause os
535
+ # stub to be raised again and so on
536
+ # Another example is numpy. numpy init file imports multidim array file
537
+ # which imports again more items from numpy and so on.
538
+ imp_node_file = self.__handle_different_site_packages(imp_node.loc.mod_path)
539
+ imported_module_file = self.__handle_different_site_packages(
540
+ imported_module.loc.mod_path
541
+ )
542
+ if imp_node_file == imported_module_file:
543
+ return True
544
+
545
+ parent: Optional[ast.AstNode] = imp_node.parent
546
+ while parent is not None:
547
+ parent_file = self.__handle_different_site_packages(parent.loc.mod_path)
548
+ if parent_file == imported_module_file:
549
+ return True
550
+ else:
551
+ parent = parent.find_parent_of_type(ast.Module)
552
+
553
+ return False