jaclang 0.8.4__py3-none-any.whl → 0.8.6__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 (88) hide show
  1. jaclang/cli/cli.md +1 -0
  2. jaclang/cli/cli.py +109 -37
  3. jaclang/compiler/jac.lark +3 -3
  4. jaclang/compiler/larkparse/jac_parser.py +2 -2
  5. jaclang/compiler/parser.py +14 -21
  6. jaclang/compiler/passes/main/__init__.py +5 -1
  7. jaclang/compiler/passes/main/binder_pass.py +594 -0
  8. jaclang/compiler/passes/main/cfg_build_pass.py +21 -1
  9. jaclang/compiler/passes/main/import_pass.py +8 -256
  10. jaclang/compiler/passes/main/inheritance_pass.py +10 -3
  11. jaclang/compiler/passes/main/pyast_gen_pass.py +92 -77
  12. jaclang/compiler/passes/main/pyast_load_pass.py +24 -13
  13. jaclang/compiler/passes/main/sem_def_match_pass.py +1 -1
  14. jaclang/compiler/passes/main/sym_tab_build_pass.py +4 -0
  15. jaclang/compiler/passes/main/tests/fixtures/M1.jac +3 -0
  16. jaclang/compiler/passes/main/tests/fixtures/cfg_has_var.jac +12 -0
  17. jaclang/compiler/passes/main/tests/fixtures/cfg_if_no_else.jac +11 -0
  18. jaclang/compiler/passes/main/tests/fixtures/cfg_return.jac +9 -0
  19. jaclang/compiler/passes/main/tests/fixtures/checker_imported.jac +2 -0
  20. jaclang/compiler/passes/main/tests/fixtures/checker_importer.jac +6 -0
  21. jaclang/compiler/passes/main/tests/fixtures/data_spatial_types.jac +1 -1
  22. jaclang/compiler/passes/main/tests/fixtures/import_symbol_type_infer.jac +11 -0
  23. jaclang/compiler/passes/main/tests/fixtures/infer_type_assignment.jac +5 -0
  24. jaclang/compiler/passes/main/tests/fixtures/member_access_type_inferred.jac +13 -0
  25. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +11 -0
  26. jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +47 -0
  27. jaclang/compiler/passes/main/tests/fixtures/type_annotation_assignment.jac +8 -0
  28. jaclang/compiler/passes/main/tests/test_binder_pass.py +111 -0
  29. jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +62 -24
  30. jaclang/compiler/passes/main/tests/test_checker_pass.py +87 -0
  31. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +13 -13
  32. jaclang/compiler/passes/main/tests/test_sem_def_match_pass.py +6 -6
  33. jaclang/compiler/passes/main/type_checker_pass.py +128 -0
  34. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +2 -0
  35. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +3 -0
  36. jaclang/compiler/program.py +32 -11
  37. jaclang/compiler/tests/test_sr_errors.py +32 -0
  38. jaclang/compiler/type_system/__init__.py +1 -0
  39. jaclang/compiler/type_system/type_evaluator.py +421 -0
  40. jaclang/compiler/type_system/type_utils.py +41 -0
  41. jaclang/compiler/type_system/types.py +240 -0
  42. jaclang/compiler/unitree.py +36 -24
  43. jaclang/langserve/dev_engine.jac +645 -0
  44. jaclang/langserve/dev_server.jac +201 -0
  45. jaclang/langserve/engine.jac +24 -5
  46. jaclang/langserve/tests/server_test/test_lang_serve.py +2 -2
  47. jaclang/langserve/tests/test_dev_server.py +80 -0
  48. jaclang/langserve/tests/test_server.py +13 -0
  49. jaclang/runtimelib/builtin.py +28 -39
  50. jaclang/runtimelib/importer.py +34 -63
  51. jaclang/runtimelib/machine.py +48 -64
  52. jaclang/runtimelib/memory.py +23 -5
  53. jaclang/runtimelib/tests/fixtures/savable_object.jac +10 -2
  54. jaclang/runtimelib/utils.py +42 -6
  55. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  56. jaclang/tests/fixtures/edges_walk.jac +1 -1
  57. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  58. jaclang/tests/fixtures/py_run.jac +8 -0
  59. jaclang/tests/fixtures/py_run.py +23 -0
  60. jaclang/tests/fixtures/pyfunc.py +2 -0
  61. jaclang/tests/fixtures/pyfunc_fmt.py +60 -0
  62. jaclang/tests/fixtures/pyfunc_fstr.py +25 -0
  63. jaclang/tests/fixtures/pyfunc_kwesc.py +33 -0
  64. jaclang/tests/fixtures/python_run_test.py +19 -0
  65. jaclang/tests/test_cli.py +107 -0
  66. jaclang/tests/test_language.py +106 -5
  67. jaclang/utils/lang_tools.py +6 -3
  68. jaclang/utils/module_resolver.py +90 -0
  69. jaclang/utils/symtable_test_helpers.py +125 -0
  70. jaclang/utils/test.py +3 -4
  71. jaclang/vendor/interegular/__init__.py +34 -0
  72. jaclang/vendor/interegular/comparator.py +163 -0
  73. jaclang/vendor/interegular/fsm.py +1015 -0
  74. jaclang/vendor/interegular/patterns.py +732 -0
  75. jaclang/vendor/interegular/py.typed +0 -0
  76. jaclang/vendor/interegular/utils/__init__.py +15 -0
  77. jaclang/vendor/interegular/utils/simple_parser.py +165 -0
  78. jaclang/vendor/interegular-0.3.3.dist-info/INSTALLER +1 -0
  79. jaclang/vendor/interegular-0.3.3.dist-info/LICENSE.txt +21 -0
  80. jaclang/vendor/interegular-0.3.3.dist-info/METADATA +64 -0
  81. jaclang/vendor/interegular-0.3.3.dist-info/RECORD +20 -0
  82. jaclang/vendor/interegular-0.3.3.dist-info/REQUESTED +0 -0
  83. jaclang/vendor/interegular-0.3.3.dist-info/WHEEL +5 -0
  84. jaclang/vendor/interegular-0.3.3.dist-info/top_level.txt +1 -0
  85. {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/METADATA +2 -1
  86. {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/RECORD +88 -43
  87. {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/WHEEL +0 -0
  88. {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,11 @@
1
+ def test_if_without_else(x: int) {
2
+ if (x > 0) {
3
+ print("Positive");
4
+ }
5
+ print("Done");
6
+ }
7
+
8
+ with entry {
9
+ test_if_without_else(5);
10
+ test_if_without_else(-3);
11
+ }
@@ -0,0 +1,9 @@
1
+ def test_return_direct() {
2
+ print("Before return");
3
+ return;
4
+ print("After return");
5
+ }
6
+
7
+ with entry {
8
+ test_return_direct();
9
+ }
@@ -0,0 +1,2 @@
1
+
2
+ glob some_int: int = 42;
@@ -0,0 +1,6 @@
1
+ import from checker_imported {
2
+ some_int as alias
3
+ }
4
+
5
+ glob i: int = alias; # <-- Ok
6
+ glob s: str = alias; # <-- Error
@@ -126,5 +126,5 @@ with entry {
126
126
  root spawn walker1();
127
127
  root spawn walker2();
128
128
  root spawn walker3();
129
- print(_.node_dot(root));
129
+ print(printgraph(root));
130
130
  }
@@ -0,0 +1,11 @@
1
+ import math as alias;
2
+ with entry {
3
+
4
+ # math module imports sys so it has the symbol
5
+ # we're not using math.pi since it's a Final[float]
6
+ # and we haven't implemented generic types yet.
7
+ m = alias;
8
+
9
+ i: int = m.sys.prefix; # <-- Error
10
+ s: str = m.sys.prefix; # <-- Ok
11
+ }
@@ -0,0 +1,5 @@
1
+ with entry {
2
+ some_int_inferred = 42;
3
+ assigning_to_int: int = some_int_inferred; # <-- Ok
4
+ assigning_to_str: str = some_int_inferred; # <-- Error
5
+ }
@@ -0,0 +1,13 @@
1
+ node Foo {
2
+ # We can infer the type of `bar` but the jac
3
+ # syntax makes the type annotation a must here.
4
+ has bar: int = 42;
5
+ }
6
+ with entry {
7
+ f: Foo = Foo();
8
+
9
+ i = 42; s = "foo";
10
+
11
+ i = f.bar; # <-- Ok
12
+ s = f.bar; # <-- Error
13
+ }
@@ -0,0 +1,11 @@
1
+ node Bar {
2
+ has baz: int;
3
+ }
4
+ node Foo {
5
+ has bar: Bar;
6
+ }
7
+ with entry {
8
+ f: Foo = Foo();
9
+ i: int = f.bar.baz; # <-- Ok
10
+ s: str = f.bar.baz; # <-- Error
11
+ }
@@ -0,0 +1,47 @@
1
+ import M1;
2
+ # import M2 as m2alias;
3
+ # import M3, M4;
4
+ # import M5 as m5alias, M6 as m6alias;
5
+ # import from M7 {M7S1}
6
+ # import from M8 {M8S1 as m8s1alias, M8S2 as m8s2alias}
7
+ # include M9;
8
+ # glob a = 5, k = 9;
9
+ glob aa = 9;
10
+ with entry{
11
+ Y: int;
12
+ Y = 0;
13
+ # b.Person.ss = 9;
14
+ n = 0;
15
+ z = Y;
16
+ aa = 9;
17
+ }
18
+ with entry {
19
+ Y = 99;
20
+
21
+ }
22
+ def ccc() {
23
+ # aa = 99; #Error:name 'aa' is assigned to before global declaration
24
+ if (0) {
25
+ global aa;
26
+ global bb;
27
+ aa = 88;
28
+ bb = 0;
29
+ p = 90;
30
+ M1.k = 0;
31
+ }
32
+ aa = 99;
33
+ print(aa);
34
+ }
35
+ with entry {
36
+ ccc();
37
+ print(aa);
38
+ }
39
+
40
+
41
+ # import random;
42
+
43
+
44
+
45
+ # with entry{
46
+ # d = random.randint(1, 100);
47
+ # }
@@ -0,0 +1,8 @@
1
+ glob should_pass1: int = 42;
2
+ glob should_fail1: int = "foo";
3
+ glob should_pass2: str = "bar";
4
+ glob should_fail2: str = 42;
5
+
6
+ # This is without any explicit type annotation.
7
+ # was failing after the first PR.
8
+ glob should_be_fine = "baz";
@@ -0,0 +1,111 @@
1
+ """Test Binder pass."""
2
+
3
+ from jaclang.compiler.program import JacProgram
4
+ from jaclang.utils.symtable_test_helpers import SymTableTestMixin
5
+
6
+
7
+ class BinderPassTests( SymTableTestMixin):
8
+ """Test pass module."""
9
+
10
+ def setUp(self) -> None:
11
+ """Set up test."""
12
+ return super().setUp()
13
+
14
+ def test_glob_sym_build(self) -> None:
15
+ """Test symbol table construction for symbol_binding_test.jac fixture."""
16
+ mod_targ = JacProgram().bind(self.fixture_abs_path("sym_binder.jac"))
17
+ sym_table = mod_targ.sym_tab
18
+
19
+ #currenlty 'aa' is not in the main table, need fix #TODO
20
+ # defns=[(9, 6), (16, 5), (27, 9), (32, 5)],
21
+ # uses=[(33, 11), (37, 11)]
22
+ # Test global variable 'aa'
23
+ self.assert_symbol_complete(
24
+ sym_table, "aa", "variable",
25
+ decl=(9, 6),
26
+ defns=[(9, 6), (16, 5), ],
27
+ uses=[ (37, 11)]
28
+ )
29
+
30
+ # Test global variable 'n'
31
+ self.assert_symbol_complete(
32
+ sym_table, "n", "variable",
33
+ decl=(14, 5),
34
+ defns=[(14, 5)]
35
+ )
36
+
37
+ # Test imported module 'M1'
38
+ self.assert_symbol_complete(
39
+ sym_table, "M1", "module",
40
+ decl=(1, 8),
41
+ defns=[(1, 8)]
42
+ )
43
+
44
+ # Test global variable 'z'
45
+ self.assert_symbol_complete(
46
+ sym_table, "z", "variable",
47
+ decl=(15, 5),
48
+ defns=[(15, 5)]
49
+ )
50
+
51
+ # Test global variable 'Y'
52
+ self.assert_symbol_complete(
53
+ sym_table, "Y", "variable",
54
+ decl=(11, 5),
55
+ defns=[(11, 5), (12, 5), (19, 5)],
56
+ uses=[(15, 9)]
57
+ )
58
+
59
+ # Test ability 'ccc'
60
+ self.assert_symbol_complete(
61
+ sym_table, "ccc", "ability",
62
+ decl=(22, 5),
63
+ defns=[(22, 5)],
64
+ uses=[(36, 5)]
65
+ )
66
+
67
+ #TODO: Fix the following test, 'bb' is not in the main table
68
+ # # Test global variable 'bb'
69
+ # self.assert_symbol_complete(
70
+ # sym_table, "bb", "variable",
71
+ # decl=(26, 17),
72
+ # defns=[(26, 17), (28, 9)]
73
+ # )
74
+
75
+ # Test sub-table for ability 'ccc'
76
+ ccc_table = self.assert_sub_table_exists(sym_table, "ccc",'ability')
77
+
78
+ # Test sub-table for if statement inside 'ccc'
79
+ if_table = self.assert_sub_table_exists(ccc_table, "IfStmt",'variable')
80
+
81
+ # Test local variable 'p' inside if statement
82
+ self.assert_symbol_complete(
83
+ if_table, "p", "variable",
84
+ decl=(29, 9),
85
+ defns=[(29, 9)]
86
+ )
87
+
88
+ def test_symbol_table_structure(self) -> None:
89
+ """Test the overall structure of the symbol table."""
90
+ mod_targ = JacProgram().build(self.fixture_abs_path("sym_binder.jac"))
91
+ sym_table = mod_targ.sym_tab
92
+
93
+ # Verify main module table exists
94
+ self.assertIn("sym_binder", str(sym_table))
95
+
96
+ # Verify expected number of symbols in main table
97
+ main_symbols = ["aa", "n", "M1", "z", "Y", "ccc"]
98
+ # 'bb' is not here:need fix #TODO
99
+ for symbol_name in main_symbols:
100
+ self.assert_symbol_exists(sym_table, symbol_name)
101
+
102
+ # Verify sub-tables exist
103
+ sub_tables = sym_table.kid_scope
104
+ self.assertTrue(len(sub_tables) > 0, "No sub-tables found")
105
+
106
+ # Verify ability sub-table has nested if-statement table
107
+ ccc_table = self.assert_sub_table_exists(sym_table, "ccc", 'ability')
108
+ if_table = self.assert_sub_table_exists(ccc_table, "IfStmt", 'variable')
109
+
110
+ # Verify if-statement table contains local variable
111
+ self.assert_symbol_exists(if_table, "p")
@@ -16,19 +16,9 @@ class TestCFGBuildPass(TestCase):
16
16
  """Test basic blocks."""
17
17
  file_name = self.fixture_abs_path("cfg_gen.jac")
18
18
 
19
- from jaclang.compiler.passes.main.cfg_build_pass import CoalesceBBPass
19
+ from jaclang.compiler.passes.main.cfg_build_pass import cfg_dot_from_file
20
20
 
21
- with open(file_name, "r") as f:
22
- file_source = f.read()
23
-
24
- ir = (prog := JacProgram()).compile(use_str=file_source, file_path=file_name)
25
-
26
- cfg_pass = CoalesceBBPass(
27
- ir_in=ir,
28
- prog=prog,
29
- )
30
-
31
- dot = cfg_pass.printgraph_cfg()
21
+ dot = cfg_dot_from_file(file_name=file_name)
32
22
 
33
23
  expected_dot = (
34
24
  "digraph G {\n"
@@ -62,19 +52,9 @@ class TestCFGBuildPass(TestCase):
62
52
  """Test basic blocks."""
63
53
  file_name = self.fixture_abs_path("cfg_ability_test.jac")
64
54
 
65
- from jaclang.compiler.passes.main.cfg_build_pass import CoalesceBBPass
66
-
67
- with open(file_name, "r") as f:
68
- file_source = f.read()
55
+ from jaclang.compiler.passes.main.cfg_build_pass import cfg_dot_from_file
69
56
 
70
- ir = (prog := JacProgram()).compile(use_str=file_source, file_path=file_name)
71
-
72
- cfg_pass = CoalesceBBPass(
73
- ir_in=ir,
74
- prog=prog,
75
- )
76
-
77
- dot = cfg_pass.printgraph_cfg()
57
+ dot = cfg_dot_from_file(file_name=file_name)
78
58
 
79
59
  expected_dot = (
80
60
  "digraph G {\n"
@@ -93,3 +73,61 @@ class TestCFGBuildPass(TestCase):
93
73
  )
94
74
 
95
75
  self.assertEqual(dot, expected_dot)
76
+
77
+ def test_cfg_ability_with_has(self) -> None:
78
+ """Test basic blocks with ability and has."""
79
+ file_name = self.fixture_abs_path("cfg_has_var.jac")
80
+
81
+ from jaclang.compiler.passes.main.cfg_build_pass import cfg_dot_from_file
82
+
83
+ dot = cfg_dot_from_file(file_name=file_name)
84
+
85
+ expected_dot = (
86
+ "digraph G {\n"
87
+ ' 0 [label="BB0\\nobj Rock", shape=box];\n'
88
+ ' 1 [label="BB1\\nhas pellets : list ;", shape=box];\n'
89
+ ' 2 [label="BB2\\ncan count_pellets( ) -> int\\nreturn self . pellets . length ( ) ;", shape=box];\n'
90
+ ' 3 [label="BB3\\nrock = Rock ( pellets = [ 1 , 2 , 3 ] ) ;\\nprint ( \\"Number of pellets: \\" + rock . count_pellets ( ) . to_string ( ) ) ;", shape=box];\n'
91
+ " 0 -> 1;\n"
92
+ " 0 -> 2;\n"
93
+ "}\n"
94
+ )
95
+
96
+ self.assertEqual(dot, expected_dot)
97
+
98
+ def test_cfg_if_no_else(self) -> None:
99
+ """Test basic blocks with if without else."""
100
+ file_name = self.fixture_abs_path("cfg_if_no_else.jac")
101
+
102
+ from jaclang.compiler.passes.main.cfg_build_pass import cfg_dot_from_file
103
+
104
+ dot = cfg_dot_from_file(file_name=file_name)
105
+
106
+ expected_dot = (
107
+ "digraph G {\n"
108
+ ' 0 [label="BB0\\ncan test_if_without_else( x : int )\\nif ( x > 0 )", shape=box];\n'
109
+ ' 1 [label="BB1\\nprint ( \\"Positive\\" ) ;", shape=box];\n'
110
+ ' 2 [label="BB2\\nprint ( \\"Done\\" ) ;", shape=box];\n'
111
+ ' 3 [label="BB3\\ntest_if_without_else ( 5 ) ;\\ntest_if_without_else ( - 3 ) ;", shape=box];\n'
112
+ " 0 -> 1;\n"
113
+ " 0 -> 2;\n"
114
+ " 1 -> 2;\n"
115
+ "}\n"
116
+ )
117
+ self.assertEqual(dot, expected_dot)
118
+
119
+ def test_cfg_return_stmt(self) -> None:
120
+ """Test basic blocks with return statement."""
121
+ file_name = self.fixture_abs_path("cfg_return.jac")
122
+
123
+ from jaclang.compiler.passes.main.cfg_build_pass import cfg_dot_from_file
124
+
125
+ dot = cfg_dot_from_file(file_name=file_name)
126
+
127
+ expected_dot = (
128
+ "digraph G {\n"
129
+ ' 0 [label="BB0\\ncan test_return_direct( )\\nprint ( \\"Before return\\" ) ;\\nreturn ;\\nprint ( \\"After return\\" ) ;", shape=box];\n'
130
+ ' 1 [label="BB1\\ntest_return_direct ( ) ;", shape=box];\n'
131
+ "}\n"
132
+ )
133
+ self.assertEqual(dot, expected_dot)
@@ -0,0 +1,87 @@
1
+
2
+ """Tests for typechecker pass (the pyright implementation)."""
3
+
4
+ from tempfile import NamedTemporaryFile
5
+
6
+ from jaclang.utils.test import TestCase
7
+ from jaclang.compiler.passes.main import TypeCheckPass
8
+ from jaclang.compiler.program import JacProgram
9
+
10
+
11
+ class TypeCheckerPassTests(TestCase):
12
+ """Test class obviously."""
13
+
14
+ def test_explicit_type_annotation_in_assignment(self) -> None:
15
+ """Test explicit type annotation in assignment."""
16
+ program = JacProgram()
17
+ program.build(
18
+ self.fixture_abs_path("type_annotation_assignment.jac"), type_check=True
19
+ )
20
+ self.assertEqual(len(program.errors_had), 2)
21
+ self._assert_error_pretty_found("""
22
+ glob should_fail1: int = "foo";
23
+ ^^^^^^^^^^^^^^^^^^^^^^^^^
24
+ """, program.errors_had[0].pretty_print())
25
+
26
+ self._assert_error_pretty_found("""
27
+ glob should_fail2: str = 42;
28
+ ^^^^^^^^^^^^^^^^^^^^^^
29
+ """, program.errors_had[1].pretty_print())
30
+
31
+ def test_infer_type_of_assignment(self) -> None:
32
+ program = JacProgram()
33
+ mod = program.compile(self.fixture_abs_path("infer_type_assignment.jac"))
34
+ TypeCheckPass(ir_in=mod, prog=program)
35
+ self.assertEqual(len(program.errors_had), 1)
36
+
37
+ self._assert_error_pretty_found("""
38
+ assigning_to_str: str = some_int_inferred;
39
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40
+ """, program.errors_had[0].pretty_print())
41
+
42
+ def test_member_access_type_resolve(self) -> None:
43
+ program = JacProgram()
44
+ mod = program.compile(self.fixture_abs_path("member_access_type_resolve.jac"))
45
+ TypeCheckPass(ir_in=mod, prog=program)
46
+ self.assertEqual(len(program.errors_had), 1)
47
+ self._assert_error_pretty_found("""
48
+ s: str = f.bar.baz;
49
+ ^^^^^^^^^^^^^^^^^^^
50
+ """, program.errors_had[0].pretty_print())
51
+
52
+ def test_member_access_type_infered(self) -> None:
53
+ program = JacProgram()
54
+ mod = program.compile(self.fixture_abs_path("member_access_type_inferred.jac"))
55
+ TypeCheckPass(ir_in=mod, prog=program)
56
+ self.assertEqual(len(program.errors_had), 1)
57
+ self._assert_error_pretty_found("""
58
+ s = f.bar;
59
+ ^^^^^^^^^
60
+ """, program.errors_had[0].pretty_print())
61
+
62
+ def test_import_symbol_type_infer(self) -> None:
63
+ program = JacProgram()
64
+ mod = program.compile(self.fixture_abs_path("import_symbol_type_infer.jac"))
65
+ TypeCheckPass(ir_in=mod, prog=program)
66
+ self.assertEqual(len(program.errors_had), 1)
67
+ self._assert_error_pretty_found("""
68
+ i: int = m.sys.prefix;
69
+ ^^^^^^^^^^^^^^^^^^^^^
70
+ """, program.errors_had[0].pretty_print())
71
+
72
+ def test_from_import(self) -> None:
73
+ path = self.fixture_abs_path("checker_importer.jac")
74
+
75
+ program = JacProgram()
76
+ mod = program.compile(path)
77
+ TypeCheckPass(ir_in=mod, prog=program)
78
+ self.assertEqual(len(program.errors_had), 1)
79
+ self._assert_error_pretty_found("""
80
+ glob s: str = alias;
81
+ ^^^^^^^^^^^^^^
82
+ """, program.errors_had[0].pretty_print())
83
+
84
+
85
+ def _assert_error_pretty_found(self, needle: str, haystack: str) -> None:
86
+ for line in [line.strip() for line in needle.splitlines() if line.strip()]:
87
+ self.assertIn(line, haystack, f"Expected line '{line}' not found in:\n{haystack}")
@@ -50,43 +50,43 @@ class PyastGenPassTests(TestCaseMicroSuite, AstSyncTestMixin):
50
50
  # Function (full).
51
51
  sym_fn1 = code_gen.lookup("fn1")
52
52
  self.assertEqual(sym_fn1.semstr, "A function that takes two integers and returns nothing.")
53
- self.assertEqual(sym_fn1.fetch_sym_tab.lookup("bar").semstr, "The first integer parameter.")
53
+ self.assertEqual(sym_fn1.symbol_table.lookup("bar").semstr, "The first integer parameter.")
54
54
 
55
55
  # Function (Missing baz)
56
56
  sym_fn2 = code_gen.lookup("fn2")
57
57
  self.assertEqual(sym_fn2.semstr, "A function that takes one integer and returns nothing.")
58
- self.assertEqual(sym_fn2.fetch_sym_tab.lookup("bar").semstr, "The first integer parameter.")
59
- self.assertEqual(sym_fn2.fetch_sym_tab.lookup("baz").semstr, "")
58
+ self.assertEqual(sym_fn2.symbol_table.lookup("bar").semstr, "The first integer parameter.")
59
+ self.assertEqual(sym_fn2.symbol_table.lookup("baz").semstr, "")
60
60
 
61
61
  # Function (Without sem at all)
62
62
  sym_fn3 = code_gen.lookup("fn3")
63
63
  self.assertTrue(sym_fn3.semstr == "")
64
- self.assertEqual(sym_fn3.fetch_sym_tab.lookup("bar").semstr, "")
65
- self.assertEqual(sym_fn3.fetch_sym_tab.lookup("baz").semstr, "")
64
+ self.assertEqual(sym_fn3.symbol_table.lookup("bar").semstr, "")
65
+ self.assertEqual(sym_fn3.symbol_table.lookup("baz").semstr, "")
66
66
 
67
67
  # Architype (with body).
68
68
  sym_arch1 = code_gen.lookup("Arch1")
69
69
  self.assertEqual(sym_arch1.semstr, "An object that contains two integer properties.")
70
- self.assertEqual(sym_arch1.fetch_sym_tab.lookup("bar").semstr, "The first integer property.")
71
- self.assertEqual(sym_arch1.fetch_sym_tab.lookup("baz").semstr, "The second integer property.")
70
+ self.assertEqual(sym_arch1.symbol_table.lookup("bar").semstr, "The first integer property.")
71
+ self.assertEqual(sym_arch1.symbol_table.lookup("baz").semstr, "The second integer property.")
72
72
 
73
73
  # Architype (without body).
74
74
  sym_arch2 = code_gen.lookup("Arch2")
75
75
  self.assertEqual(sym_arch2.semstr, "An object that contains two integer properties.")
76
- self.assertEqual(sym_arch2.fetch_sym_tab.lookup("bar").semstr, "The first integer property.")
77
- self.assertEqual(sym_arch2.fetch_sym_tab.lookup("baz").semstr, "The second integer property.")
76
+ self.assertEqual(sym_arch2.symbol_table.lookup("bar").semstr, "The first integer property.")
77
+ self.assertEqual(sym_arch2.symbol_table.lookup("baz").semstr, "The second integer property.")
78
78
 
79
79
  # Enum (with body).
80
80
  sym_enum1 = code_gen.lookup("Enum1")
81
81
  self.assertEqual(sym_enum1.semstr, "An enumeration that defines two values: Bar and Baz.")
82
- self.assertEqual(sym_enum1.fetch_sym_tab.lookup("Bar").semstr, "The Bar value of the Enum1 enumeration.")
83
- self.assertEqual(sym_enum1.fetch_sym_tab.lookup("Baz").semstr, "The Baz value of the Enum1 enumeration.")
82
+ self.assertEqual(sym_enum1.symbol_table.lookup("Bar").semstr, "The Bar value of the Enum1 enumeration.")
83
+ self.assertEqual(sym_enum1.symbol_table.lookup("Baz").semstr, "The Baz value of the Enum1 enumeration.")
84
84
 
85
85
  # Enum (without body).
86
86
  sym_enum2 = code_gen.lookup("Enum2")
87
87
  self.assertEqual(sym_enum2.semstr, "An enumeration that defines two values: Bar and Baz.")
88
- self.assertEqual(sym_enum2.fetch_sym_tab.lookup("Bar").semstr, "The Bar value of the Enum2 enumeration.")
89
- self.assertEqual(sym_enum2.fetch_sym_tab.lookup("Baz").semstr, "The Baz value of the Enum2 enumeration.")
88
+ self.assertEqual(sym_enum2.symbol_table.lookup("Bar").semstr, "The Bar value of the Enum2 enumeration.")
89
+ self.assertEqual(sym_enum2.symbol_table.lookup("Baz").semstr, "The Baz value of the Enum2 enumeration.")
90
90
 
91
91
  if code_gen.gen.py_ast and isinstance(code_gen.gen.py_ast[0], ast3.Module):
92
92
  prog = compile(code_gen.gen.py_ast[0], filename="<ast>", mode="exec")
@@ -15,24 +15,24 @@ class SemDefMatchPassTests(TestCase):
15
15
  mod = out.mod.hub[self.fixture_abs_path("sem_def_match.jac")]
16
16
 
17
17
  self.assertEqual(mod.lookup("E").decl.name_of.semstr, "An enum representing some values.") # type: ignore
18
- self.assertEqual(mod.lookup("E").fetch_sym_tab.lookup("A").decl.name_of.semstr, "The first value of the enum E.") # type: ignore
19
- self.assertEqual(mod.lookup("E").fetch_sym_tab.lookup("B").decl.name_of.semstr, "The second value of the enum E.") # type: ignore
18
+ self.assertEqual(mod.lookup("E").symbol_table.lookup("A").decl.name_of.semstr, "The first value of the enum E.") # type: ignore
19
+ self.assertEqual(mod.lookup("E").symbol_table.lookup("B").decl.name_of.semstr, "The second value of the enum E.") # type: ignore
20
20
 
21
21
  self.assertEqual(mod.lookup("Person").decl.name_of.semstr, "A class representing a person.") # type: ignore
22
22
 
23
- person_scope = mod.lookup("Person").fetch_sym_tab # type: ignore
23
+ person_scope = mod.lookup("Person").symbol_table # type: ignore
24
24
  self.assertEqual(person_scope.lookup("name").decl.name_of.semstr, "The name of the person.") # type: ignore
25
25
  self.assertEqual(person_scope.lookup("yob").decl.name_of.semstr, "The year of birth of the person.") # type: ignore
26
26
 
27
27
  sym_calc_age = person_scope.lookup("calc_age") # type: ignore
28
28
  self.assertEqual(sym_calc_age.decl.name_of.semstr, "Calculate the age of the person.") # type: ignore
29
29
 
30
- calc_age_scope = sym_calc_age.fetch_sym_tab # type: ignore
30
+ calc_age_scope = sym_calc_age.symbol_table # type: ignore
31
31
  self.assertEqual(calc_age_scope.lookup("year").decl.name_of.semstr, "The year to calculate the age against.") # type: ignore
32
32
 
33
33
  self.assertEqual(mod.lookup("OuterClass").decl.name_of.semstr, "A class containing an inner class.") # type: ignore
34
- outer_scope = mod.lookup("OuterClass").fetch_sym_tab # type: ignore
34
+ outer_scope = mod.lookup("OuterClass").symbol_table # type: ignore
35
35
  self.assertEqual(outer_scope.lookup("InnerClass").decl.name_of.semstr, "An inner class within OuterClass.") # type: ignore
36
- inner_scope = outer_scope.lookup("InnerClass").fetch_sym_tab # type: ignore
36
+ inner_scope = outer_scope.lookup("InnerClass").symbol_table # type: ignore
37
37
  self.assertEqual(inner_scope.lookup("inner_value").decl.name_of.semstr, "A value specific to the inner class.") # type: ignore
38
38
 
@@ -0,0 +1,128 @@
1
+ """
2
+ Type checker pass.
3
+
4
+ This will perform type checking on the Jac program and accumulate any type
5
+ errors found during the process in the pass's had_errors, had_warnings list.
6
+
7
+ Reference:
8
+ Pyright: packages/pyright-internal/src/analyzer/checker.ts
9
+ craizy_type_expr branch: type_checker_pass.py
10
+ """
11
+
12
+ import ast as py_ast
13
+ import os
14
+
15
+ import jaclang.compiler.unitree as uni
16
+ from jaclang.compiler.passes import UniPass
17
+ from jaclang.compiler.type_system.type_evaluator import TypeEvaluator
18
+ from jaclang.runtimelib.utils import read_file_with_encoding
19
+
20
+ from .pyast_load_pass import PyastBuildPass
21
+ from .sym_tab_build_pass import SymTabBuildPass
22
+
23
+
24
+ class TypeCheckPass(UniPass):
25
+ """Type checker pass for JacLang."""
26
+
27
+ # NOTE: This is done in the binder pass of pyright, however I'm doing this
28
+ # here, cause this will be the entry point of the type checker and we're not
29
+ # relying on the binder pass at the moment and we can go back to binder pass
30
+ # in the future if we needed it.
31
+ _BUILTINS_STUB_FILE_PATH = os.path.join(
32
+ os.path.dirname(__file__),
33
+ "../../../vendor/typeshed/stdlib/builtins.pyi",
34
+ )
35
+
36
+ # Cache the builtins module once it parsed.
37
+ _BUILTINS_MODULE: uni.Module | None = None
38
+
39
+ def before_pass(self) -> None:
40
+ """Initialize the checker pass."""
41
+ self._load_builtins_stub_module()
42
+ self._insert_builtin_symbols()
43
+
44
+ assert TypeCheckPass._BUILTINS_MODULE is not None
45
+ self.evaluator = TypeEvaluator(
46
+ builtins_module=TypeCheckPass._BUILTINS_MODULE,
47
+ program=self.prog,
48
+ )
49
+
50
+ # --------------------------------------------------------------------------
51
+ # Internal helper functions
52
+ # --------------------------------------------------------------------------
53
+
54
+ def _binding_builtins(self) -> bool:
55
+ """Return true if we're binding the builtins stub file."""
56
+ return self.ir_in == TypeCheckPass._BUILTINS_MODULE
57
+
58
+ def _load_builtins_stub_module(self) -> None:
59
+ """Return the builtins stub module.
60
+
61
+ This will parse and cache the stub file and return the cached module on
62
+ subsequent calls.
63
+ """
64
+ if self._binding_builtins() or TypeCheckPass._BUILTINS_MODULE is not None:
65
+ return
66
+
67
+ if not os.path.exists(TypeCheckPass._BUILTINS_STUB_FILE_PATH):
68
+ raise FileNotFoundError(
69
+ f"Builtins stub file not found at {TypeCheckPass._BUILTINS_STUB_FILE_PATH}"
70
+ )
71
+
72
+ file_content = read_file_with_encoding(TypeCheckPass._BUILTINS_STUB_FILE_PATH)
73
+ uni_source = uni.Source(file_content, TypeCheckPass._BUILTINS_STUB_FILE_PATH)
74
+ mod = PyastBuildPass(
75
+ ir_in=uni.PythonModuleAst(
76
+ py_ast.parse(file_content),
77
+ orig_src=uni_source,
78
+ ),
79
+ prog=self.prog,
80
+ ).ir_out
81
+ SymTabBuildPass(ir_in=mod, prog=self.prog)
82
+ TypeCheckPass._BUILTINS_MODULE = mod
83
+
84
+ def _insert_builtin_symbols(self) -> None:
85
+ if self._binding_builtins():
86
+ return
87
+
88
+ # TODO: Insert these symbols.
89
+ # Reference: pyright Binder.bindModule()
90
+ #
91
+ # List taken from https://docs.python.org/3/reference/import.html#__name__
92
+ # '__name__', '__loader__', '__package__', '__spec__', '__path__',
93
+ # '__file__', '__cached__', '__dict__', '__annotations__',
94
+ # '__builtins__', '__doc__',
95
+ assert (
96
+ TypeCheckPass._BUILTINS_MODULE is not None
97
+ ), "Builtins module is not loaded"
98
+ if self.ir_in.parent_scope is not None:
99
+ self.log_info("Builtins module is already bound, skipping.")
100
+ return
101
+ # Review: If we ever assume a module cannot have a parent scope, this will
102
+ # break that contract.
103
+ self.ir_in.parent_scope = TypeCheckPass._BUILTINS_MODULE
104
+
105
+ # --------------------------------------------------------------------------
106
+ # Ast walker hooks
107
+ # --------------------------------------------------------------------------
108
+
109
+ def exit_assignment(self, node: uni.Assignment) -> None:
110
+ """Pyright: Checker.visitAssignment(node: AssignmentNode): boolean."""
111
+ # TODO: In pyright this logic is present at evaluateTypesForAssignmentStatement
112
+ # and we're calling getTypeForStatement from here, This can be moved into the
113
+ # other place or we can keep it here.
114
+ #
115
+ # Grep this in pyright TypeEvaluator.ts:
116
+ # `} else if (node.d.leftExpr.nodeType === ParseNodeType.Name) {`
117
+ #
118
+ if len(node.target) == 1 and (node.value is not None): # Simple assignment.
119
+ left_type = self.evaluator.get_type_of_expression(node.target[0])
120
+ right_type = self.evaluator.get_type_of_expression(node.value)
121
+ if not self.evaluator.assign_type(right_type, left_type):
122
+ self.log_error(f"Cannot assign {right_type} to {left_type}")
123
+ else:
124
+ pass # TODO: handle
125
+
126
+ def exit_atom_trailer(self, node: uni.AtomTrailer) -> None:
127
+ """Handle the atom trailer node."""
128
+ self.evaluator.get_type_of_expression(node)