jaclang 0.7.27__py3-none-any.whl → 0.7.28__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 (41) 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 +11 -3
  22. jaclang/plugin/feature.py +2 -0
  23. jaclang/plugin/spec.py +1 -0
  24. jaclang/runtimelib/test.py +59 -4
  25. jaclang/settings.py +3 -0
  26. jaclang/tests/fixtures/base_class1.jac +11 -0
  27. jaclang/tests/fixtures/base_class2.jac +11 -0
  28. jaclang/tests/fixtures/import_all.jac +7 -0
  29. jaclang/tests/fixtures/import_all_py.py +8 -0
  30. jaclang/tests/fixtures/jactest_imported.jac +6 -0
  31. jaclang/tests/fixtures/jactest_main.jac +22 -0
  32. jaclang/tests/fixtures/multi_dim_array_split.jac +2 -6
  33. jaclang/tests/fixtures/test_py.py +12 -0
  34. jaclang/tests/test_cli.py +82 -0
  35. jaclang/tests/test_language.py +7 -7
  36. jaclang/utils/helpers.py +9 -1
  37. jaclang/utils/treeprinter.py +6 -3
  38. {jaclang-0.7.27.dist-info → jaclang-0.7.28.dist-info}/METADATA +2 -2
  39. {jaclang-0.7.27.dist-info → jaclang-0.7.28.dist-info}/RECORD +41 -31
  40. {jaclang-0.7.27.dist-info → jaclang-0.7.28.dist-info}/WHEEL +1 -1
  41. {jaclang-0.7.27.dist-info → jaclang-0.7.28.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,103 @@
1
+ """Pass used to add the inherited symbols for architypes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ import jaclang.compiler.absyntree as ast
8
+ from jaclang.compiler.passes import Pass
9
+ from jaclang.compiler.symtable import Symbol, SymbolTable
10
+ from jaclang.settings import settings
11
+
12
+
13
+ class InheritancePass(Pass):
14
+ """Add inherited abilities in the target symbol tables."""
15
+
16
+ def __debug_print(self, msg: str) -> None:
17
+ if settings.inherit_pass_debug:
18
+ self.log_info("[PyImportPass] " + msg)
19
+
20
+ def __lookup(self, name: str, sym_table: SymbolTable) -> Optional[Symbol]:
21
+ symbol = sym_table.lookup(name)
22
+ if symbol is None:
23
+ # Check if the needed symbol in builtins
24
+ builtins_symtable = self.ir.sym_tab.find_scope("builtins")
25
+ assert builtins_symtable is not None
26
+ symbol = builtins_symtable.lookup(name)
27
+ return symbol
28
+
29
+ def enter_architype(self, node: ast.Architype) -> None:
30
+ """Fill architype symbol tables with abilities from parent architypes."""
31
+ if node.base_classes is None:
32
+ return
33
+
34
+ for item in node.base_classes.items:
35
+ # The assumption is that the base class can only be a name node
36
+ # or an atom trailer only.
37
+ assert isinstance(item, (ast.Name, ast.AtomTrailer))
38
+
39
+ # In case of name node, then get the symbol table that contains
40
+ # the current class and lookup for that name after that use the
41
+ # symbol to get the symbol table of the base class
42
+ if isinstance(item, ast.Name):
43
+ assert node.sym_tab.parent is not None
44
+ base_class_symbol = self.__lookup(item.sym_name, node.sym_tab.parent)
45
+ if base_class_symbol is None:
46
+ msg = "Missing symbol for base class "
47
+ msg += f"{ast.Module.get_href_path(item)}.{item.sym_name}"
48
+ msg += f" needed for {ast.Module.get_href_path(node)}"
49
+ self.__debug_print(msg)
50
+ continue
51
+ base_class_symbol_table = base_class_symbol.fetch_sym_tab
52
+ if (
53
+ base_class_symbol_table is None
54
+ and base_class_symbol.defn[0]
55
+ .parent_of_type(ast.Module)
56
+ .py_info.is_raised_from_py
57
+ ):
58
+ msg = "Missing symbol table for python base class "
59
+ msg += f"{ast.Module.get_href_path(item)}.{item.sym_name}"
60
+ msg += f" needed for {ast.Module.get_href_path(node)}"
61
+ self.__debug_print(msg)
62
+ continue
63
+ assert base_class_symbol_table is not None
64
+ node.sym_tab.inherit_sym_tab(base_class_symbol_table)
65
+
66
+ # In case of atom trailer, unwind it and use each name node to
67
+ # as the code above to lookup for the base class
68
+ elif isinstance(item, ast.AtomTrailer):
69
+ current_sym_table = node.sym_tab.parent
70
+ not_found: bool = False
71
+ assert current_sym_table is not None
72
+ for name in item.as_attr_list:
73
+ sym = self.__lookup(name.sym_name, current_sym_table)
74
+ if sym is None:
75
+ msg = "Missing symbol for base class "
76
+ msg += f"{ast.Module.get_href_path(name)}.{name.sym_name}"
77
+ msg += f" needed for {ast.Module.get_href_path(node)}"
78
+ self.__debug_print(msg)
79
+ not_found = True
80
+ break
81
+ current_sym_table = sym.fetch_sym_tab
82
+
83
+ # In case of python nodes, the base class may not be
84
+ # raised so ignore these classes for now
85
+ # TODO Do we need to import these classes?
86
+ if (
87
+ sym.defn[0].parent_of_type(ast.Module).py_info.is_raised_from_py
88
+ and current_sym_table is None
89
+ ):
90
+ msg = "Missing symbol table for python base class "
91
+ msg += f"{ast.Module.get_href_path(name)}.{name.sym_name}"
92
+ msg += f" needed for {ast.Module.get_href_path(node)}"
93
+ self.__debug_print(msg)
94
+ not_found = True
95
+ break
96
+
97
+ assert current_sym_table is not None
98
+
99
+ if not_found:
100
+ continue
101
+
102
+ assert current_sym_table is not None
103
+ node.sym_tab.inherit_sym_tab(current_sym_table)
@@ -35,7 +35,7 @@ class PyCollectDepsPass(Pass):
35
35
  if isinstance(node, ast.ModulePath):
36
36
  if node.path:
37
37
  path = ".".join([i.value for i in node.path])
38
- node.abs_path = self.ir.py_mod_dep_map.get(path)
38
+ node.abs_path = self.ir.py_info.py_mod_dep_map.get(path)
39
39
  if node.abs_path and os.path.isfile(node.abs_path.replace(".pyi", ".py")):
40
40
  node.abs_path = node.abs_path.replace(".pyi", ".py")
41
41
 
@@ -45,7 +45,7 @@ class PyCollectDepsPass(Pass):
45
45
  if mod_path_node.path:
46
46
  path = ".".join([i.value for i in mod_path_node.path])
47
47
  path += f".{node.name.value}"
48
- node.abs_path = self.ir.py_mod_dep_map.get(path)
48
+ node.abs_path = self.ir.py_info.py_mod_dep_map.get(path)
49
49
  if node.abs_path and os.path.isfile(node.abs_path.replace(".pyi", ".py")):
50
50
  node.abs_path = node.abs_path.replace(".pyi", ".py")
51
51
 
@@ -61,17 +61,17 @@ class PyCollectDepsPass(Pass):
61
61
  else:
62
62
  mod_name = node_full_name
63
63
 
64
- if mod_name not in self.ir.py_mod_dep_map:
64
+ if mod_name not in self.ir.py_info.py_mod_dep_map:
65
65
  self.__debug_print(
66
66
  f"Can't find a python file associated with {type(node)}::{node.loc}"
67
67
  )
68
68
  return
69
69
 
70
- mode_path = self.ir.py_mod_dep_map[mod_name]
70
+ mode_path = self.ir.py_info.py_mod_dep_map[mod_name]
71
71
  if mode_path.endswith(".jac"):
72
72
  return
73
73
 
74
- self.ir.py_raise_map[mod_name] = mode_path
74
+ self.ir.py_info.py_raise_map[mod_name] = mode_path
75
75
  else:
76
76
  self.__debug_print(
77
77
  f"Collect python dependencies is not supported in {type(node)}::{node.loc}"
@@ -129,7 +129,7 @@ class PyastBuildPass(Pass[ast.PythonModuleAst]):
129
129
  is_imported=False,
130
130
  kid=valid,
131
131
  )
132
- ret.is_raised_from_py = True
132
+ ret.py_info.is_raised_from_py = True
133
133
  return self.nu(ret)
134
134
 
135
135
  def proc_function_def(
@@ -20,6 +20,7 @@ from .fuse_typeinfo_pass import FuseTypeInfoPass # noqa: I100
20
20
  from .registry_pass import RegistryPass # noqa: I100
21
21
  from .access_modifier_pass import AccessCheckPass # noqa: I100
22
22
  from .py_collect_dep_pass import PyCollectDepsPass # noqa: I100
23
+ from .inheritance_pass import InheritancePass # noqa: I100
23
24
 
24
25
  py_code_gen = [
25
26
  SubNodeTabPass,
@@ -38,6 +39,7 @@ type_checker_sched = [
38
39
  PyCollectDepsPass,
39
40
  PyImportPass,
40
41
  DefUsePass,
42
+ InheritancePass,
41
43
  FuseTypeInfoPass,
42
44
  AccessCheckPass,
43
45
  ]
@@ -4,6 +4,8 @@ This pass builds the symbol table tree for the Jaseci Ast. It also adds symbols
4
4
  for globals, imports, architypes, and abilities declarations and definitions.
5
5
  """
6
6
 
7
+ from typing import TypeVar
8
+
7
9
  import jaclang.compiler.absyntree as ast
8
10
  from jaclang.compiler.passes import Pass
9
11
  from jaclang.compiler.symtable import SymbolTable
@@ -19,6 +21,7 @@ class SymTabBuildPass(Pass):
19
21
  def push_scope(self, name: str, key_node: ast.AstNode) -> None:
20
22
  """Push scope."""
21
23
  inherit = key_node.parent
24
+
22
25
  if not len(self.cur_sym_tab) and not inherit:
23
26
  self.cur_sym_tab.append(SymbolTable(name, key_node))
24
27
  elif not len(self.cur_sym_tab) and inherit:
@@ -1284,3 +1287,17 @@ class SymTabBuildPass(Pass):
1284
1287
  def enter_comment_token(self, node: ast.CommentToken) -> None:
1285
1288
  """Sub objects."""
1286
1289
  self.sync_node_to_scope(node)
1290
+
1291
+
1292
+ T = TypeVar("T", bound=ast.AstNode)
1293
+
1294
+
1295
+ class PyInspectSymTabBuildPass(SymTabBuildPass):
1296
+ """Jac Symbol table build pass."""
1297
+
1298
+ def push_scope(self, name: str, key_node: ast.AstNode) -> None:
1299
+ """Push scope."""
1300
+ if not len(self.cur_sym_tab):
1301
+ self.cur_sym_tab.append(SymbolTable(name, key_node))
1302
+ else:
1303
+ self.cur_sym_tab.append(self.cur_scope.push_kid_scope(name, key_node))
@@ -0,0 +1,130 @@
1
+ node NodeA {
2
+ has value: int = 10;
3
+ }
4
+
5
+ node NodeC {
6
+ has value: int = 10;
7
+ }
8
+
9
+ edge EdgeB {
10
+ has value: int = 20;
11
+ }
12
+
13
+ with entry {
14
+ node_1 = a(value=5);
15
+ node_2 = a();
16
+ node_3 = a(value=15);
17
+ node_4 = a(value=20);
18
+ node_5 = c(value=25);
19
+ print(type(root ++> node_1));
20
+ node_1 +:edge_1 := b(value=5):+> node_2;
21
+ node_1 +:edge_2 := b(value=10):+> node_3;
22
+ node_1 +:edge_3 := b(value=15):+> node_4;
23
+ node_1 +:edge_4 := b():+> node_5;
24
+ node_1 del--> node_2;
25
+ del node_3;
26
+ }
27
+
28
+
29
+ node A {
30
+ has val: int = 0;
31
+ }
32
+
33
+ edge a {}
34
+
35
+ walker W {
36
+ can create with `root entry;
37
+ }
38
+
39
+ :walker:W:can:create {
40
+ Start = A(5);
41
+ here +:a:+> Start;
42
+ Start +:a:+> A(10) +:a:+> A(15);
43
+ Start +:a:+> A(20) +:a:+> A(25);
44
+ }
45
+
46
+ with entry {
47
+ root spawn W();
48
+ print([root-->-->(`?A)]);
49
+ print([root-->-->-->(`?A)]);
50
+ }
51
+
52
+ """Bubble sort using DS Features of Jac (somparision and swapping happens in inner nodes)."""
53
+ glob list1 = [80, 79, 60, 59, 40, 35, 19, 1];
54
+
55
+ node main_node {
56
+ has no: int = 0;
57
+ }
58
+
59
+ node inner_node {
60
+ has main: int = 0,
61
+ sub: int = 0;
62
+ }
63
+
64
+ walker walker1 {
65
+ can create_main_node with `root entry;
66
+ }
67
+
68
+ :walker:walker1:can:create_main_node {
69
+ end = here;
70
+ for i=0 to i<len(list1)+1 by i+=1 {
71
+ end ++> (end := main_node(no=i + 1));
72
+ visit [-->];
73
+ }
74
+ }
75
+
76
+ walker walker2 {
77
+ can skip_root with `root entry {
78
+ visit [-->];
79
+ }
80
+
81
+ can process with main_node entry;
82
+ }
83
+
84
+ :walker:walker2:can:process {
85
+ :g: list1 ;
86
+
87
+ for j in range(0, len(list1) - (here.no)) {
88
+ here ++> inner_node(main=here.no, sub=j + 1);
89
+ }
90
+ visit [-->];
91
+ }
92
+
93
+ walker walker3 {
94
+ can skiproot with `root entry {
95
+ visit [-->];
96
+ }
97
+
98
+ can adjust with main_node entry;
99
+ }
100
+
101
+ :walker:walker3:can:adjust {
102
+ here spawn walker4();
103
+ }
104
+
105
+ walker walker4 {
106
+ can skipmain with main_node entry {
107
+ visit [-->];# print(f"iteration {here.no} started {list1}");
108
+ }
109
+
110
+ can skipin with inner_node entry {
111
+ :g: list1 ;
112
+
113
+ j = here.sub - 1;
114
+ if list1[j] > list1[j + 1] {
115
+ x = list1[j];
116
+ list1[j] = list1[j + 1];
117
+ list1[j + 1] = x;
118
+ }
119
+ #uncomment below to see the swap in each inner nodes
120
+ # print(list1);
121
+
122
+ }
123
+ }
124
+
125
+ with entry {
126
+ root spawn walker1();
127
+ root spawn walker2();
128
+ root spawn walker3();
129
+ print(Jac.node_dot(root));
130
+ }
@@ -1,3 +1,3 @@
1
- from .display import *
2
- from .color import *
3
- from .constants import *
1
+ from .display import set_mode
2
+ from .color import Color
3
+ from .constants import CONST_VALUE, CL
@@ -1,3 +1,3 @@
1
- from .display import *
2
- from .color import *
3
- from .constants import *
1
+ from .display import set_mode
2
+ from .color import Color
3
+ from .constants import CONST_VALUE, CL
@@ -81,39 +81,35 @@ class ImportPassPassTests(TestCase):
81
81
  "genericpath": r"jaclang/vendor/mypy/typeshed/stdlib/genericpath.pyi$",
82
82
  }
83
83
  for i in p:
84
- self.assertIn(i, build.ir.py_raise_map)
85
- self.assertRegex(re.sub(r".*fixtures/", "", build.ir.py_raise_map[i]), p[i])
84
+ self.assertIn(i, build.ir.py_info.py_raise_map)
85
+ self.assertRegex(
86
+ re.sub(r".*fixtures/", "", build.ir.py_info.py_raise_map[i]), p[i]
87
+ )
86
88
 
87
89
  def test_py_raised_mods(self) -> None:
88
90
  """Basic test for pass."""
89
91
  state = jac_file_to_pass(
90
92
  self.fixture_abs_path("py_imp_test.jac"), schedule=py_code_gen_typed
91
93
  )
94
+ for i in list(
95
+ filter(
96
+ lambda x: x.py_info.is_raised_from_py,
97
+ state.ir.get_all_sub_nodes(ast.Module),
98
+ )
99
+ ):
100
+ print(ast.Module.get_href_path(i))
92
101
  self.assertEqual(
93
102
  len(
94
103
  list(
95
104
  filter(
96
- lambda x: x.is_raised_from_py,
105
+ lambda x: x.py_info.is_raised_from_py,
97
106
  state.ir.get_all_sub_nodes(ast.Module),
98
107
  )
99
108
  )
100
109
  ),
101
- 7,
110
+ 11, # TODO: Need to only link the modules one time
102
111
  )
103
112
 
104
- # def test_py_resolve_list(self) -> None:
105
- # """Basic test for pass."""
106
- # state: JacImportPass = jac_file_to_pass(
107
- # self.examples_abs_path("rpg_game/jac_impl/jac_impl_5/main.jac"),
108
- # JacImportPass,
109
- # )
110
- # self.assertGreater(len(state.py_resolve_list), 20)
111
- # self.assertIn("pygame.sprite.Sprite.__init__", state.py_resolve_list)
112
- # self.assertIn("pygame.mouse.get_pressed", state.py_resolve_list)
113
- # self.assertIn("pygame.K_SPACE", state.py_resolve_list)
114
- # self.assertIn("random.randint", state.py_resolve_list)
115
- # self.assertIn("pygame.font.Font", state.py_resolve_list)
116
-
117
113
  def test_double_empty_anx(self) -> None:
118
114
  """Test importing python."""
119
115
  captured_output = io.StringIO()
@@ -62,3 +62,27 @@ class MypyTypeCheckPassTests(TestCase):
62
62
  self.assertEqual(out.count("Type: builtins.str"), 35)
63
63
  for i in lis:
64
64
  self.assertNotIn(i, out)
65
+
66
+ def test_data_spatial_type_info(self) -> None:
67
+ """Testing for type info for dataspatial constructs."""
68
+ out = AstTool().ir(
69
+ ["ast", f"{self.fixture_abs_path('data_spatial_types.jac')}"]
70
+ )
71
+ self.assertRegex(
72
+ out,
73
+ r"129:24 - 129:28.*SpecialVarRef - _Jac.get_root\(\) \- Type\: jaclang.runtimelib.architype.Root",
74
+ )
75
+ self.assertRegex(out, r"129:11 - 129:29.*FuncCall \- Type\: builtins\.str")
76
+ self.assertRegex(
77
+ out,
78
+ r"129:15 - 129:23.*Name \- node_dot \- Type\: builtins.str, SymbolTable\: str",
79
+ )
80
+ self.assertRegex(
81
+ out,
82
+ r"128:5 - 128:25.*BinaryExpr \- Type\: jaclang.runtimelib.architype.WalkerArchitype",
83
+ )
84
+ self.assertRegex(
85
+ out,
86
+ r"48:11 - 48:28.*EdgeRefTrailer \- Type\: builtins.list\[data_spatial_types.A\]",
87
+ )
88
+ self.assertRegex(out, r"24:5 - 24:25.*BinaryExpr \- Type\: builtins.bool", out)
@@ -115,13 +115,14 @@ class JacTypeCheckPass(Pass):
115
115
  for k, v in mypy_graph.items()
116
116
  if (
117
117
  k.startswith("jaclang.plugin")
118
+ or k.startswith("jaclang.runtimelib")
118
119
  or not (k.startswith("jaclang.") or k.startswith("mypy."))
119
120
  )
120
121
  }
121
122
  for i in mypy_graph:
122
- self.ir.py_mod_dep_map[i] = mypy_graph[i].xpath
123
+ self.ir.py_info.py_mod_dep_map[i] = mypy_graph[i].xpath
123
124
  for j in mypy_graph[i].dependencies:
124
- self.ir.py_mod_dep_map[j] = str(
125
+ self.ir.py_info.py_mod_dep_map[j] = str(
125
126
  myab.find_module_with_reason(j, manager)
126
127
  )
127
128
  myab.process_graph(mypy_graph, manager)
@@ -0,0 +1,22 @@
1
+ """Code location info for AST nodes."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class PyInfo:
7
+ """Python info related to python imports."""
8
+
9
+ def __init__(self) -> None:
10
+ """Object Initialization."""
11
+ # Module dependacy map used to store all module dependacies detected
12
+ # by mypy. The dependacies are computed using the mypy graph in
13
+ # TypeCheck pass
14
+ self.py_mod_dep_map: dict[str, str] = {}
15
+
16
+ # Get all the modules that we really need to raise inorder to make
17
+ # all Jac types correct (FuseTypeInfo works). This is computed using
18
+ # PyCollectDepsPass.
19
+ self.py_raise_map: dict[str, str] = {}
20
+
21
+ # Flag for python modules raised into jac
22
+ self.is_raised_from_py: bool = False
@@ -115,6 +115,7 @@ class SymbolTable:
115
115
  node: ast.AstSymbolNode,
116
116
  access_spec: Optional[ast.AstAccessNode] | SymbolAccess = None,
117
117
  single: bool = False,
118
+ force_overwrite: bool = False,
118
119
  ) -> Optional[ast.AstNode]:
119
120
  """Set a variable in the symbol table.
120
121
 
@@ -126,7 +127,7 @@ class SymbolTable:
126
127
  if single and node.sym_name in self.tab
127
128
  else None
128
129
  )
129
- if node.sym_name not in self.tab:
130
+ if force_overwrite or node.sym_name not in self.tab:
130
131
  self.tab[node.sym_name] = Symbol(
131
132
  defn=node.name_spec,
132
133
  access=(
@@ -163,11 +164,17 @@ class SymbolTable:
163
164
  node: ast.AstSymbolNode,
164
165
  access_spec: Optional[ast.AstAccessNode] | SymbolAccess = None,
165
166
  single_decl: Optional[str] = None,
167
+ force_overwrite: bool = False,
166
168
  ) -> Optional[Symbol]:
167
169
  """Insert into symbol table."""
168
170
  if node.sym and self == node.sym.parent_tab:
169
171
  return node.sym
170
- self.insert(node=node, single=single_decl is not None, access_spec=access_spec)
172
+ self.insert(
173
+ node=node,
174
+ single=single_decl is not None,
175
+ access_spec=access_spec,
176
+ force_overwrite=force_overwrite,
177
+ )
171
178
  self.update_py_ctx_for_def(node)
172
179
  return node.sym
173
180
 
@@ -330,7 +330,7 @@ class TestJacLangServer(TestCase):
330
330
  "doubleinner",
331
331
  "apply_red",
332
332
  ],
333
- 8,
333
+ 11,
334
334
  ),
335
335
  (
336
336
  lspt.Position(65, 23),
@@ -359,7 +359,7 @@ class TestJacLangServer(TestCase):
359
359
  "doubleinner",
360
360
  "apply_red",
361
361
  ],
362
- 8,
362
+ 11,
363
363
  ),
364
364
  (
365
365
  lspt.Position(73, 22),
jaclang/plugin/default.py CHANGED
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import ast as ast3
6
6
  import fnmatch
7
7
  import html
8
+ import inspect
8
9
  import os
9
10
  import types
10
11
  from collections import OrderedDict
@@ -818,12 +819,14 @@ class JacFeatureImpl(
818
819
  @hookimpl
819
820
  def create_test(test_fun: Callable) -> Callable:
820
821
  """Create a new test."""
822
+ file_path = inspect.getfile(test_fun)
823
+ func_name = test_fun.__name__
821
824
 
822
825
  def test_deco() -> None:
823
826
  test_fun(JacTestCheck())
824
827
 
825
828
  test_deco.__name__ = test_fun.__name__
826
- JacTestCheck.add_test(test_deco)
829
+ JacTestCheck.add_test(file_path, func_name, test_deco)
827
830
 
828
831
  return test_deco
829
832
 
@@ -831,6 +834,7 @@ class JacFeatureImpl(
831
834
  @hookimpl
832
835
  def run_test(
833
836
  filepath: str,
837
+ func_name: Optional[str],
834
838
  filter: Optional[str],
835
839
  xit: bool,
836
840
  maxfail: Optional[int],
@@ -849,7 +853,9 @@ class JacFeatureImpl(
849
853
  mod_name = mod_name[:-5]
850
854
  JacTestCheck.reset()
851
855
  Jac.jac_import(target=mod_name, base_path=base, cachable=False)
852
- JacTestCheck.run_test(xit, maxfail, verbose)
856
+ JacTestCheck.run_test(
857
+ xit, maxfail, verbose, os.path.abspath(filepath), func_name
858
+ )
853
859
  ret_count = JacTestCheck.failcount
854
860
  else:
855
861
  print("Not a .jac file.")
@@ -875,7 +881,9 @@ class JacFeatureImpl(
875
881
  print(f"\n\n\t\t* Inside {root_dir}" + "/" + f"{file} *")
876
882
  JacTestCheck.reset()
877
883
  Jac.jac_import(target=file[:-4], base_path=root_dir)
878
- JacTestCheck.run_test(xit, maxfail, verbose)
884
+ JacTestCheck.run_test(
885
+ xit, maxfail, verbose, os.path.abspath(file), func_name
886
+ )
879
887
 
880
888
  if JacTestCheck.breaker and (xit or maxfail):
881
889
  break
jaclang/plugin/feature.py CHANGED
@@ -359,6 +359,7 @@ class JacFeature(
359
359
  @staticmethod
360
360
  def run_test(
361
361
  filepath: str,
362
+ func_name: Optional[str] = None,
362
363
  filter: Optional[str] = None,
363
364
  xit: bool = False,
364
365
  maxfail: Optional[int] = None,
@@ -368,6 +369,7 @@ class JacFeature(
368
369
  """Run the test suite in the specified .jac file."""
369
370
  return plugin_manager.hook.run_test(
370
371
  filepath=filepath,
372
+ func_name=func_name,
371
373
  filter=filter,
372
374
  xit=xit,
373
375
  maxfail=maxfail,
jaclang/plugin/spec.py CHANGED
@@ -345,6 +345,7 @@ class JacFeatureSpec(
345
345
  @hookspec(firstresult=True)
346
346
  def run_test(
347
347
  filepath: str,
348
+ func_name: Optional[str],
348
349
  filter: Optional[str],
349
350
  xit: bool,
350
351
  maxfail: Optional[int],