jaclang 0.8.5__py3-none-any.whl → 0.8.7__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 (70) hide show
  1. jaclang/cli/cli.md +4 -3
  2. jaclang/cli/cli.py +63 -29
  3. jaclang/cli/cmdreg.py +1 -140
  4. jaclang/compiler/passes/main/__init__.py +2 -0
  5. jaclang/compiler/passes/main/cfg_build_pass.py +21 -1
  6. jaclang/compiler/passes/main/inheritance_pass.py +8 -1
  7. jaclang/compiler/passes/main/pyast_gen_pass.py +70 -11
  8. jaclang/compiler/passes/main/pyast_load_pass.py +14 -20
  9. jaclang/compiler/passes/main/sym_tab_build_pass.py +4 -0
  10. jaclang/compiler/passes/main/tests/fixtures/cfg_has_var.jac +12 -0
  11. jaclang/compiler/passes/main/tests/fixtures/cfg_if_no_else.jac +11 -0
  12. jaclang/compiler/passes/main/tests/fixtures/cfg_return.jac +9 -0
  13. jaclang/compiler/passes/main/tests/fixtures/checker_binary_op.jac +21 -0
  14. jaclang/compiler/passes/main/tests/fixtures/checker_call_expr_class.jac +12 -0
  15. jaclang/compiler/passes/main/tests/fixtures/checker_cyclic_symbol.jac +4 -0
  16. jaclang/compiler/passes/main/tests/fixtures/checker_expr_call.jac +9 -0
  17. jaclang/compiler/passes/main/tests/fixtures/checker_import_missing_module.jac +13 -0
  18. jaclang/compiler/passes/main/tests/fixtures/checker_imported.jac +2 -0
  19. jaclang/compiler/passes/main/tests/fixtures/checker_importer.jac +6 -0
  20. jaclang/compiler/passes/main/tests/fixtures/checker_magic_call.jac +17 -0
  21. jaclang/compiler/passes/main/tests/fixtures/checker_mod_path.jac +8 -0
  22. jaclang/compiler/passes/main/tests/fixtures/data_spatial_types.jac +1 -1
  23. jaclang/compiler/passes/main/tests/fixtures/import_symbol_type_infer.jac +11 -0
  24. jaclang/compiler/passes/main/tests/fixtures/infer_type_assignment.jac +5 -0
  25. jaclang/compiler/passes/main/tests/fixtures/member_access_type_inferred.jac +13 -0
  26. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +11 -0
  27. jaclang/compiler/passes/main/tests/fixtures/type_annotation_assignment.jac +8 -0
  28. jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +62 -24
  29. jaclang/compiler/passes/main/tests/test_checker_pass.py +161 -0
  30. jaclang/compiler/passes/main/type_checker_pass.py +147 -0
  31. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +1 -4
  32. jaclang/compiler/program.py +17 -3
  33. jaclang/compiler/type_system/__init__.py +1 -0
  34. jaclang/compiler/type_system/operations.py +104 -0
  35. jaclang/compiler/type_system/type_evaluator.py +560 -0
  36. jaclang/compiler/type_system/type_utils.py +41 -0
  37. jaclang/compiler/type_system/types.py +240 -0
  38. jaclang/compiler/unitree.py +15 -9
  39. jaclang/langserve/dev_engine.jac +645 -0
  40. jaclang/langserve/dev_server.jac +201 -0
  41. jaclang/langserve/engine.jac +135 -91
  42. jaclang/langserve/server.jac +21 -14
  43. jaclang/langserve/tests/server_test/test_lang_serve.py +2 -5
  44. jaclang/langserve/tests/test_dev_server.py +80 -0
  45. jaclang/langserve/tests/test_server.py +9 -2
  46. jaclang/langserve/utils.jac +44 -48
  47. jaclang/runtimelib/builtin.py +28 -39
  48. jaclang/runtimelib/importer.py +1 -1
  49. jaclang/runtimelib/machine.py +48 -64
  50. jaclang/runtimelib/memory.py +23 -5
  51. jaclang/runtimelib/tests/fixtures/savable_object.jac +10 -2
  52. jaclang/runtimelib/utils.py +13 -6
  53. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  54. jaclang/tests/fixtures/edges_walk.jac +1 -1
  55. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  56. jaclang/tests/fixtures/jac_run_py_bugs.py +18 -0
  57. jaclang/tests/fixtures/jac_run_py_import.py +13 -0
  58. jaclang/tests/fixtures/lambda_arg_annotation.jac +15 -0
  59. jaclang/tests/fixtures/lambda_self.jac +18 -0
  60. jaclang/tests/fixtures/py_run.jac +8 -0
  61. jaclang/tests/fixtures/py_run.py +23 -0
  62. jaclang/tests/fixtures/pyfunc.py +2 -0
  63. jaclang/tests/test_cli.py +103 -14
  64. jaclang/tests/test_language.py +10 -4
  65. jaclang/utils/lang_tools.py +3 -0
  66. jaclang/utils/module_resolver.py +1 -1
  67. {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/METADATA +4 -2
  68. {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/RECORD +70 -37
  69. {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/WHEEL +1 -1
  70. {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/entry_points.txt +0 -0
@@ -141,17 +141,17 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
141
141
 
142
142
  reserved_keywords = [v for _, v in TOKEN_MAP.items()]
143
143
 
144
- value = node.name if node.name not in reserved_keywords else f"<>{node.name}"
145
144
  name = uni.Name(
146
145
  orig_src=self.orig_src,
147
146
  name=Tok.NAME,
148
- value=value,
147
+ value=node.name,
149
148
  line=node.lineno,
150
149
  end_line=node.end_lineno if node.end_lineno else node.lineno,
151
150
  col_start=node.col_offset,
152
151
  col_end=node.col_offset + len(node.name),
153
152
  pos_start=0,
154
153
  pos_end=0,
154
+ is_kwesc=(node.name in reserved_keywords),
155
155
  )
156
156
  body = [self.convert(i) for i in node.body]
157
157
  valid = [i for i in body if isinstance(i, (uni.CodeBlockStmt))]
@@ -285,8 +285,8 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
285
285
  and body_stmt.signature.params
286
286
  ):
287
287
  for param in body_stmt.signature.params:
288
- if param.name.value == "self":
289
- param.type_tag = uni.SubTag[uni.Expr](name, kid=[name])
288
+ if param.name.value == "self" and param.type_tag:
289
+ param.type_tag.tag.value = name.value
290
290
  doc = (
291
291
  body[0].expr
292
292
  if isinstance(body[0], uni.ExprStmt)
@@ -748,17 +748,14 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
748
748
  attribute = uni.Name(
749
749
  orig_src=self.orig_src,
750
750
  name=Tok.NAME,
751
- value=(
752
- ("<>" + node.attr)
753
- if node.attr == "init"
754
- else "init" if node.attr == "__init__" else node.attr
755
- ),
751
+ value="init" if node.attr == "__init__" else node.attr,
756
752
  line=node.lineno,
757
753
  end_line=node.end_lineno if node.end_lineno else node.lineno,
758
754
  col_start=node.col_offset,
759
755
  col_end=node.col_offset + len(node.attr),
760
756
  pos_start=0,
761
757
  pos_end=0,
758
+ is_kwesc=node.attr == "init",
762
759
  )
763
760
  if isinstance(value, uni.Expr):
764
761
  return uni.AtomTrailer(
@@ -1680,18 +1677,17 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
1680
1677
  for _, v in TOKEN_MAP.items()
1681
1678
  if v not in ["float", "int", "str", "bool", "self"]
1682
1679
  ]
1683
-
1684
- value = node.id if node.id not in reserved_keywords else f"<>{node.id}"
1685
1680
  ret = uni.Name(
1686
1681
  orig_src=self.orig_src,
1687
1682
  name=Tok.NAME,
1688
- value=value,
1683
+ value=node.id,
1689
1684
  line=node.lineno,
1690
1685
  end_line=node.end_lineno if node.end_lineno else node.lineno,
1691
1686
  col_start=node.col_offset,
1692
1687
  col_end=node.col_offset + len(node.id),
1693
1688
  pos_start=0,
1694
1689
  pos_end=0,
1690
+ is_kwesc=(node.id in reserved_keywords),
1695
1691
  )
1696
1692
  return ret
1697
1693
 
@@ -1735,18 +1731,18 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
1735
1731
 
1736
1732
  names: list[uni.NameAtom] = []
1737
1733
  for name in node.names:
1738
- value = name if name not in reserved_keywords else f"<>{name}"
1739
1734
  names.append(
1740
1735
  uni.Name(
1741
1736
  orig_src=self.orig_src,
1742
1737
  name=Tok.NAME,
1743
- value=value,
1738
+ value=name,
1744
1739
  line=node.lineno,
1745
1740
  end_line=node.end_lineno if node.end_lineno else node.lineno,
1746
1741
  col_start=node.col_offset,
1747
1742
  col_end=node.col_offset + len(name),
1748
1743
  pos_start=0,
1749
1744
  pos_end=0,
1745
+ is_kwesc=(name in reserved_keywords),
1750
1746
  )
1751
1747
  )
1752
1748
  return uni.NonLocalStmt(target=names, kid=names)
@@ -2056,17 +2052,17 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
2056
2052
  if v not in ["float", "int", "str", "bool", "self"]
2057
2053
  ]
2058
2054
 
2059
- value = node.arg if node.arg not in reserved_keywords else f"<>{node.arg}"
2060
2055
  name = uni.Name(
2061
2056
  orig_src=self.orig_src,
2062
2057
  name=Tok.NAME,
2063
- value=value,
2058
+ value=node.arg,
2064
2059
  line=node.lineno,
2065
2060
  end_line=node.end_lineno if node.end_lineno else node.lineno,
2066
2061
  col_start=node.col_offset,
2067
2062
  col_end=node.col_offset + len(node.arg),
2068
2063
  pos_start=0,
2069
2064
  pos_end=0,
2065
+ is_kwesc=(node.arg in reserved_keywords),
2070
2066
  )
2071
2067
  ann_expr = (
2072
2068
  self.convert(node.annotation)
@@ -2343,19 +2339,17 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
2343
2339
  from jaclang.compiler import TOKEN_MAP
2344
2340
 
2345
2341
  reserved_keywords = [v for _, v in TOKEN_MAP.items()]
2346
- arg_value = (
2347
- node.arg if node.arg not in reserved_keywords else f"<>{node.arg}"
2348
- )
2349
2342
  arg = uni.Name(
2350
2343
  orig_src=self.orig_src,
2351
2344
  name=Tok.NAME,
2352
- value=arg_value,
2345
+ value=node.arg,
2353
2346
  line=node.lineno,
2354
2347
  end_line=node.end_lineno if node.end_lineno else node.lineno,
2355
2348
  col_start=node.col_offset,
2356
2349
  col_end=node.col_offset + len(node.arg if node.arg else "_"),
2357
2350
  pos_start=0,
2358
2351
  pos_end=0,
2352
+ is_kwesc=(node.arg in reserved_keywords),
2359
2353
  )
2360
2354
  value = self.convert(node.value)
2361
2355
  if isinstance(value, uni.Expr):
@@ -84,6 +84,10 @@ class SymTabBuildPass(UniPass):
84
84
  else:
85
85
  pass # Need to support pythonic import symbols with dots in it
86
86
 
87
+ def exit_module_item(self, node: uni.ModuleItem) -> None:
88
+ sym_node = node.alias or node.name
89
+ sym_node.sym_tab.def_insert(sym_node, single_decl="import")
90
+
87
91
  def enter_archetype(self, node: uni.Archetype) -> None:
88
92
  self.push_scope_and_link(node)
89
93
  assert node.parent_scope is not None
@@ -0,0 +1,12 @@
1
+ obj Rock {
2
+ has pellets:list;
3
+
4
+ def count_pellets() -> int {
5
+ return self.pellets.length();
6
+ }
7
+ }
8
+
9
+ with entry {
10
+ rock = Rock(pellets=[1, 2, 3]);
11
+ print("Number of pellets: " + rock.count_pellets().to_string());
12
+ }
@@ -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,21 @@
1
+ node B {
2
+ def __mul__(other: B) -> int {
3
+ return 0;
4
+ }
5
+ }
6
+
7
+ node A {
8
+ def __add__(other: A) -> B {
9
+ return B();
10
+ }
11
+ }
12
+
13
+ with entry {
14
+ a: A = A();
15
+
16
+ r1: B = a + a; # <-- Ok
17
+ r2: A = a + a; # <-- Error
18
+
19
+ r3: int = (a+a) * B(); # <-- Ok
20
+ r4: str = (a+a) * B(); # <-- Error
21
+ }
@@ -0,0 +1,12 @@
1
+
2
+ node Foo {
3
+ has i: int = 0;
4
+ }
5
+
6
+ with entry {
7
+ cls = Foo;
8
+ inst = cls(); # <-- Call expression target of class type
9
+
10
+ inst.i = 42; # <-- Ok
11
+ inst.i = 'str'; # <-- Error
12
+ }
@@ -0,0 +1,4 @@
1
+
2
+ with entry {
3
+ x = x;
4
+ }
@@ -0,0 +1,9 @@
1
+
2
+ def foo() -> int {
3
+ return 42;
4
+ }
5
+
6
+ with entry {
7
+ i: int = foo(); # <-- Ok
8
+ s: str = foo(); # <-- Error
9
+ }
@@ -0,0 +1,13 @@
1
+ import from scipy { stats, optimize }
2
+ import from utils.fake_helpers { helper_func }
3
+ import from non_existent_module { foo }
4
+ import nonexistent_module as nm;
5
+
6
+
7
+ with entry {
8
+ a: int = stats.norm.cdf(0);
9
+ d: float = optimize.minimize_scalar(lambda x: int: x ** 2).fun;
10
+ result = helper_func();
11
+ b: int = foo();
12
+ c = nm.some_func();
13
+ }
@@ -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
@@ -0,0 +1,17 @@
1
+
2
+ node Bar {}
3
+
4
+ node Foo {
5
+ def __call__() -> Bar {
6
+ return Bar();
7
+ }
8
+ }
9
+
10
+ def fn() -> Foo {
11
+ return Foo();
12
+ }
13
+
14
+ with entry{
15
+ b: Bar = fn()(); # <-- Ok
16
+ f: Foo = fn()(); # <-- Error
17
+ }
@@ -0,0 +1,8 @@
1
+
2
+
3
+ import jaclang.compiler.unitree as uni;
4
+
5
+
6
+ with entry{
7
+ a:int = uni.Module; # <-- Error
8
+ }
@@ -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,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";
@@ -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,161 @@
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
+ def test_call_expr(self) -> None:
85
+ path = self.fixture_abs_path("checker_expr_call.jac")
86
+ program = JacProgram()
87
+ mod = program.compile(path)
88
+ TypeCheckPass(ir_in=mod, prog=program)
89
+ self.assertEqual(len(program.errors_had), 1)
90
+ self._assert_error_pretty_found("""
91
+ s: str = foo();
92
+ ^^^^^^^^^^^^^^
93
+ """, program.errors_had[0].pretty_print())
94
+
95
+ def test_call_expr_magic(self) -> None:
96
+ path = self.fixture_abs_path("checker_magic_call.jac")
97
+ program = JacProgram()
98
+ mod = program.compile(path)
99
+ TypeCheckPass(ir_in=mod, prog=program)
100
+ self.assertEqual(len(program.errors_had), 1)
101
+ self._assert_error_pretty_found("""
102
+ b: Bar = fn()(); # <-- Ok
103
+ f: Foo = fn()(); # <-- Error
104
+ ^^^^^^^^^^^^^^^^
105
+ """, program.errors_had[0].pretty_print())
106
+
107
+ def test_binary_op(self) -> None:
108
+ program = JacProgram()
109
+ mod = program.compile(self.fixture_abs_path("checker_binary_op.jac"))
110
+ TypeCheckPass(ir_in=mod, prog=program)
111
+ self.assertEqual(len(program.errors_had), 2)
112
+ self._assert_error_pretty_found("""
113
+ r2: A = a + a; # <-- Error
114
+ ^^^^^^^^^^^^^
115
+ """, program.errors_had[0].pretty_print())
116
+ self._assert_error_pretty_found("""
117
+ r4: str = (a+a) * B(); # <-- Error
118
+ ^^^^^^^^^^^^^^^^^^^^^
119
+ """, program.errors_had[1].pretty_print())
120
+
121
+ def test_checker_call_expr_class(self) -> None:
122
+ path = self.fixture_abs_path("checker_call_expr_class.jac")
123
+ program = JacProgram()
124
+ mod = program.compile(path)
125
+ TypeCheckPass(ir_in=mod, prog=program)
126
+ self.assertEqual(len(program.errors_had), 1)
127
+ self._assert_error_pretty_found("""
128
+ inst.i = 'str'; # <-- Error
129
+ ^^^^^^^^^^^^^^
130
+ """, program.errors_had[0].pretty_print())
131
+
132
+ def test_checker_mod_path(self) -> None:
133
+ path = self.fixture_abs_path("checker_mod_path.jac")
134
+ program = JacProgram()
135
+ mod = program.compile(path)
136
+ TypeCheckPass(ir_in=mod, prog=program)
137
+ self.assertEqual(len(program.errors_had), 1)
138
+ self._assert_error_pretty_found("""
139
+ a:int = uni.Module; # <-- Error
140
+ ^^^^^^^^^^^^^^
141
+ """, program.errors_had[0].pretty_print())
142
+
143
+ def test_checker_import_missing_module(self) -> None:
144
+ path = self.fixture_abs_path("checker_import_missing_module.jac")
145
+ program = JacProgram()
146
+ mod = program.compile(path)
147
+ TypeCheckPass(ir_in=mod, prog=program)
148
+ self.assertEqual(len(program.errors_had), 0)
149
+
150
+ def test_cyclic_symbol(self) -> None:
151
+ path = self.fixture_abs_path("checker_cyclic_symbol.jac")
152
+ program = JacProgram()
153
+ mod = program.compile(path)
154
+ # This will result in a stack overflow if not handled properly.
155
+ # So the fact that it has 0 errors means it passed.
156
+ TypeCheckPass(ir_in=mod, prog=program)
157
+ self.assertEqual(len(program.errors_had), 0)
158
+
159
+ def _assert_error_pretty_found(self, needle: str, haystack: str) -> None:
160
+ for line in [line.strip() for line in needle.splitlines() if line.strip()]:
161
+ self.assertIn(line, haystack, f"Expected line '{line}' not found in:\n{haystack}")