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.
- jaclang/cli/cli.md +1 -0
- jaclang/cli/cli.py +109 -37
- jaclang/compiler/jac.lark +3 -3
- jaclang/compiler/larkparse/jac_parser.py +2 -2
- jaclang/compiler/parser.py +14 -21
- jaclang/compiler/passes/main/__init__.py +5 -1
- jaclang/compiler/passes/main/binder_pass.py +594 -0
- jaclang/compiler/passes/main/cfg_build_pass.py +21 -1
- jaclang/compiler/passes/main/import_pass.py +8 -256
- jaclang/compiler/passes/main/inheritance_pass.py +10 -3
- jaclang/compiler/passes/main/pyast_gen_pass.py +92 -77
- jaclang/compiler/passes/main/pyast_load_pass.py +24 -13
- jaclang/compiler/passes/main/sem_def_match_pass.py +1 -1
- jaclang/compiler/passes/main/sym_tab_build_pass.py +4 -0
- jaclang/compiler/passes/main/tests/fixtures/M1.jac +3 -0
- jaclang/compiler/passes/main/tests/fixtures/cfg_has_var.jac +12 -0
- jaclang/compiler/passes/main/tests/fixtures/cfg_if_no_else.jac +11 -0
- jaclang/compiler/passes/main/tests/fixtures/cfg_return.jac +9 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_imported.jac +2 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_importer.jac +6 -0
- jaclang/compiler/passes/main/tests/fixtures/data_spatial_types.jac +1 -1
- jaclang/compiler/passes/main/tests/fixtures/import_symbol_type_infer.jac +11 -0
- jaclang/compiler/passes/main/tests/fixtures/infer_type_assignment.jac +5 -0
- jaclang/compiler/passes/main/tests/fixtures/member_access_type_inferred.jac +13 -0
- jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +11 -0
- jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +47 -0
- jaclang/compiler/passes/main/tests/fixtures/type_annotation_assignment.jac +8 -0
- jaclang/compiler/passes/main/tests/test_binder_pass.py +111 -0
- jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +62 -24
- jaclang/compiler/passes/main/tests/test_checker_pass.py +87 -0
- jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +13 -13
- jaclang/compiler/passes/main/tests/test_sem_def_match_pass.py +6 -6
- jaclang/compiler/passes/main/type_checker_pass.py +128 -0
- jaclang/compiler/passes/tool/doc_ir_gen_pass.py +2 -0
- jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +3 -0
- jaclang/compiler/program.py +32 -11
- jaclang/compiler/tests/test_sr_errors.py +32 -0
- jaclang/compiler/type_system/__init__.py +1 -0
- jaclang/compiler/type_system/type_evaluator.py +421 -0
- jaclang/compiler/type_system/type_utils.py +41 -0
- jaclang/compiler/type_system/types.py +240 -0
- jaclang/compiler/unitree.py +36 -24
- jaclang/langserve/dev_engine.jac +645 -0
- jaclang/langserve/dev_server.jac +201 -0
- jaclang/langserve/engine.jac +24 -5
- jaclang/langserve/tests/server_test/test_lang_serve.py +2 -2
- jaclang/langserve/tests/test_dev_server.py +80 -0
- jaclang/langserve/tests/test_server.py +13 -0
- jaclang/runtimelib/builtin.py +28 -39
- jaclang/runtimelib/importer.py +34 -63
- jaclang/runtimelib/machine.py +48 -64
- jaclang/runtimelib/memory.py +23 -5
- jaclang/runtimelib/tests/fixtures/savable_object.jac +10 -2
- jaclang/runtimelib/utils.py +42 -6
- jaclang/tests/fixtures/edge_node_walk.jac +1 -1
- jaclang/tests/fixtures/edges_walk.jac +1 -1
- jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
- jaclang/tests/fixtures/py_run.jac +8 -0
- jaclang/tests/fixtures/py_run.py +23 -0
- jaclang/tests/fixtures/pyfunc.py +2 -0
- jaclang/tests/fixtures/pyfunc_fmt.py +60 -0
- jaclang/tests/fixtures/pyfunc_fstr.py +25 -0
- jaclang/tests/fixtures/pyfunc_kwesc.py +33 -0
- jaclang/tests/fixtures/python_run_test.py +19 -0
- jaclang/tests/test_cli.py +107 -0
- jaclang/tests/test_language.py +106 -5
- jaclang/utils/lang_tools.py +6 -3
- jaclang/utils/module_resolver.py +90 -0
- jaclang/utils/symtable_test_helpers.py +125 -0
- jaclang/utils/test.py +3 -4
- jaclang/vendor/interegular/__init__.py +34 -0
- jaclang/vendor/interegular/comparator.py +163 -0
- jaclang/vendor/interegular/fsm.py +1015 -0
- jaclang/vendor/interegular/patterns.py +732 -0
- jaclang/vendor/interegular/py.typed +0 -0
- jaclang/vendor/interegular/utils/__init__.py +15 -0
- jaclang/vendor/interegular/utils/simple_parser.py +165 -0
- jaclang/vendor/interegular-0.3.3.dist-info/INSTALLER +1 -0
- jaclang/vendor/interegular-0.3.3.dist-info/LICENSE.txt +21 -0
- jaclang/vendor/interegular-0.3.3.dist-info/METADATA +64 -0
- jaclang/vendor/interegular-0.3.3.dist-info/RECORD +20 -0
- jaclang/vendor/interegular-0.3.3.dist-info/REQUESTED +0 -0
- jaclang/vendor/interegular-0.3.3.dist-info/WHEEL +5 -0
- jaclang/vendor/interegular-0.3.3.dist-info/top_level.txt +1 -0
- {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/METADATA +2 -1
- {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/RECORD +88 -43
- {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/WHEEL +0 -0
- {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/entry_points.txt +0 -0
|
@@ -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,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,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
|
|
19
|
+
from jaclang.compiler.passes.main.cfg_build_pass import cfg_dot_from_file
|
|
20
20
|
|
|
21
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
59
|
-
self.assertEqual(sym_fn2.
|
|
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.
|
|
65
|
-
self.assertEqual(sym_fn3.
|
|
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.
|
|
71
|
-
self.assertEqual(sym_arch1.
|
|
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.
|
|
77
|
-
self.assertEqual(sym_arch2.
|
|
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.
|
|
83
|
-
self.assertEqual(sym_enum1.
|
|
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.
|
|
89
|
-
self.assertEqual(sym_enum2.
|
|
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").
|
|
19
|
-
self.assertEqual(mod.lookup("E").
|
|
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").
|
|
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.
|
|
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").
|
|
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").
|
|
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)
|