jaclang 0.8.5__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 (52) hide show
  1. jaclang/cli/cli.md +1 -0
  2. jaclang/cli/cli.py +38 -18
  3. jaclang/compiler/passes/main/__init__.py +2 -0
  4. jaclang/compiler/passes/main/cfg_build_pass.py +21 -1
  5. jaclang/compiler/passes/main/inheritance_pass.py +8 -1
  6. jaclang/compiler/passes/main/pyast_gen_pass.py +57 -8
  7. jaclang/compiler/passes/main/sym_tab_build_pass.py +4 -0
  8. jaclang/compiler/passes/main/tests/fixtures/cfg_has_var.jac +12 -0
  9. jaclang/compiler/passes/main/tests/fixtures/cfg_if_no_else.jac +11 -0
  10. jaclang/compiler/passes/main/tests/fixtures/cfg_return.jac +9 -0
  11. jaclang/compiler/passes/main/tests/fixtures/checker_imported.jac +2 -0
  12. jaclang/compiler/passes/main/tests/fixtures/checker_importer.jac +6 -0
  13. jaclang/compiler/passes/main/tests/fixtures/data_spatial_types.jac +1 -1
  14. jaclang/compiler/passes/main/tests/fixtures/import_symbol_type_infer.jac +11 -0
  15. jaclang/compiler/passes/main/tests/fixtures/infer_type_assignment.jac +5 -0
  16. jaclang/compiler/passes/main/tests/fixtures/member_access_type_inferred.jac +13 -0
  17. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +11 -0
  18. jaclang/compiler/passes/main/tests/fixtures/type_annotation_assignment.jac +8 -0
  19. jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +62 -24
  20. jaclang/compiler/passes/main/tests/test_checker_pass.py +87 -0
  21. jaclang/compiler/passes/main/type_checker_pass.py +128 -0
  22. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +1 -4
  23. jaclang/compiler/program.py +17 -3
  24. jaclang/compiler/type_system/__init__.py +1 -0
  25. jaclang/compiler/type_system/type_evaluator.py +421 -0
  26. jaclang/compiler/type_system/type_utils.py +41 -0
  27. jaclang/compiler/type_system/types.py +240 -0
  28. jaclang/compiler/unitree.py +15 -9
  29. jaclang/langserve/dev_engine.jac +645 -0
  30. jaclang/langserve/dev_server.jac +201 -0
  31. jaclang/langserve/engine.jac +1 -1
  32. jaclang/langserve/tests/server_test/test_lang_serve.py +2 -2
  33. jaclang/langserve/tests/test_dev_server.py +80 -0
  34. jaclang/runtimelib/builtin.py +28 -39
  35. jaclang/runtimelib/importer.py +1 -1
  36. jaclang/runtimelib/machine.py +48 -64
  37. jaclang/runtimelib/memory.py +23 -5
  38. jaclang/runtimelib/tests/fixtures/savable_object.jac +10 -2
  39. jaclang/runtimelib/utils.py +13 -6
  40. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  41. jaclang/tests/fixtures/edges_walk.jac +1 -1
  42. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  43. jaclang/tests/fixtures/py_run.jac +8 -0
  44. jaclang/tests/fixtures/py_run.py +23 -0
  45. jaclang/tests/fixtures/pyfunc.py +2 -0
  46. jaclang/tests/test_cli.py +40 -0
  47. jaclang/tests/test_language.py +10 -4
  48. jaclang/utils/lang_tools.py +3 -0
  49. {jaclang-0.8.5.dist-info → jaclang-0.8.6.dist-info}/METADATA +2 -1
  50. {jaclang-0.8.5.dist-info → jaclang-0.8.6.dist-info}/RECORD +52 -31
  51. {jaclang-0.8.5.dist-info → jaclang-0.8.6.dist-info}/WHEEL +0 -0
  52. {jaclang-0.8.5.dist-info → jaclang-0.8.6.dist-info}/entry_points.txt +0 -0
jaclang/cli/cli.md CHANGED
@@ -36,6 +36,7 @@ Parameters to execute the tool command:
36
36
  - `sym.`: Generates a dot graph representation of the symbol table for the specified .jac file.
37
37
  - `ast`: Displays the Abstract Syntax Tree (AST) of the specified .jac file.
38
38
  - `ast.`: Generates a dot graph representation of the AST for the specified .jac file.
39
+ - `cfg.`: Generates a dot graph representatuin of the CFG(Control Flow Graph) for a specific .jac file
39
40
  - `pyast`: Generates the Python AST for a .py file or the relevant Python AST for the generated Python code from a .jac file.
40
41
  - `py`: Displays the relevant generated Python code for the respective Jac code in a .jac file.
41
42
  - `file_path`: Path to the .jac or .py file.
jaclang/cli/cli.py CHANGED
@@ -16,17 +16,12 @@ from jaclang.compiler.passes.main import PyastBuildPass
16
16
  from jaclang.compiler.program import JacProgram
17
17
  from jaclang.runtimelib.builtin import printgraph
18
18
  from jaclang.runtimelib.constructs import WalkerArchetype
19
- from jaclang.runtimelib.machine import (
20
- ExecutionContext,
21
- JacMachine as Jac,
22
- JacMachineInterface as JacInterface,
23
- )
19
+ from jaclang.runtimelib.machine import ExecutionContext, JacMachine as Jac
24
20
  from jaclang.runtimelib.utils import read_file_with_encoding
25
21
  from jaclang.utils.helpers import debugger as db
26
22
  from jaclang.utils.lang_tools import AstTool
27
23
 
28
-
29
- JacInterface.create_cmd()
24
+ Jac.create_cmd()
30
25
  Jac.setup()
31
26
 
32
27
 
@@ -140,6 +135,7 @@ def run(
140
135
  # if no session specified, check if it was defined when starting the command shell
141
136
  # otherwise default to jaclang.session
142
137
  base, mod, mach = proc_file_sess(filename, session)
138
+ lng = filename.split(".")[-1]
143
139
  Jac.set_base_path(base)
144
140
 
145
141
  if filename.endswith((".jac", ".py")):
@@ -148,6 +144,7 @@ def run(
148
144
  target=mod,
149
145
  base_path=base,
150
146
  override_name="__main__" if main else None,
147
+ lng=lng,
151
148
  )
152
149
  except Exception as e:
153
150
  print(f"Error running {filename}: {e}", file=sys.stderr)
@@ -159,6 +156,7 @@ def run(
159
156
  target=mod,
160
157
  base_path=base,
161
158
  override_name="__main__" if main else None,
159
+ lng=lng,
162
160
  )
163
161
  except Exception as e:
164
162
  print(f"Error running {filename}: {e}", file=sys.stderr)
@@ -219,7 +217,7 @@ def get_object(filename: str, id: str, session: str = "", main: bool = True) ->
219
217
 
220
218
 
221
219
  @cmd_registry.register
222
- def build(filename: str) -> None:
220
+ def build(filename: str, typecheck: bool = False) -> None:
223
221
  """Build the specified .jac file.
224
222
 
225
223
  Compiles a Jac source file into a Jac Intermediate Representation (.jir) file,
@@ -227,21 +225,25 @@ def build(filename: str) -> None:
227
225
 
228
226
  Args:
229
227
  filename: Path to the .jac file to build
230
- typecheck: Perform type checking during build (default: True)
228
+ typecheck: Perform type checking during build (default: False)
231
229
 
232
230
  Examples:
233
231
  jac build myprogram.jac
234
- jac build myprogram.jac --no-typecheck
232
+ jac build myprogram.jac --typecheck
235
233
  """
236
- if filename.endswith(".jac"):
237
- (out := JacProgram()).compile(file_path=filename)
238
- errs = len(out.errors_had)
239
- warnings = len(out.warnings_had)
240
- print(f"Errors: {errs}, Warnings: {warnings}")
241
- with open(filename[:-4] + ".jir", "wb") as f:
242
- pickle.dump(out, f)
243
- else:
234
+ if not filename.endswith(".jac"):
244
235
  print("Not a .jac file.", file=sys.stderr)
236
+ exit(1)
237
+ (out := JacProgram()).compile(file_path=filename, type_check=typecheck)
238
+ errs = len(out.errors_had)
239
+ warnings = len(out.warnings_had)
240
+ print(f"Errors: {errs}, Warnings: {warnings}")
241
+
242
+ for alrt in out.errors_had + out.warnings_had:
243
+ print(alrt.pretty_print(), file=sys.stderr)
244
+
245
+ with open(filename[:-4] + ".jir", "wb") as f:
246
+ pickle.dump(out, f)
245
247
 
246
248
 
247
249
  @cmd_registry.register
@@ -325,6 +327,24 @@ def lsp() -> None:
325
327
  run_lang_server()
326
328
 
327
329
 
330
+ @cmd_registry.register
331
+ def lsp_dev() -> None:
332
+ """Run Jac Language Server Protocol in Developer Mode.
333
+
334
+ Starts the experimental Jac Language Server with enhanced features
335
+ for development and testing. Used by editor extensions in developer mode.
336
+
337
+ Args:
338
+ This command takes no parameters.
339
+
340
+ Examples:
341
+ jac lsp_dev
342
+ """
343
+ from jaclang.langserve.dev_server import run_lang_server
344
+
345
+ run_lang_server()
346
+
347
+
328
348
  @cmd_registry.register
329
349
  def enter(
330
350
  filename: str,
@@ -9,6 +9,7 @@ from .def_use_pass import DefUsePass # noqa: I100
9
9
  from .sem_def_match_pass import SemDefMatchPass # noqa: I100
10
10
  from .import_pass import JacImportDepsPass # noqa: I100
11
11
  from .def_impl_match_pass import DeclImplMatchPass # noqa: I100
12
+ from .type_checker_pass import TypeCheckPass # noqa: I100
12
13
  from .pyast_load_pass import PyastBuildPass # type: ignore # noqa: I100
13
14
  from .pyast_gen_pass import PyastGenPass # noqa: I100
14
15
  from .pybc_gen_pass import PyBytecodeGenPass # noqa: I100
@@ -25,6 +26,7 @@ __all__ = [
25
26
  "JacImportDepsPass",
26
27
  "PyImportDepsPass",
27
28
  "BinderPass",
29
+ "TypeCheckPass",
28
30
  "SymTabBuildPass",
29
31
  "SymTabLinkPass",
30
32
  "DeclImplMatchPass",
@@ -152,7 +152,7 @@ class CFGBuildPass(UniPass):
152
152
  """Exit BasicBlockStmt nodes."""
153
153
  if isinstance(node, uni.UniCFGNode) and not isinstance(node, uni.Semi):
154
154
  self.first_exit = True
155
- if not node.bb_out:
155
+ if not node.bb_out and not isinstance(node, (uni.ReturnStmt, uni.ArchHas)):
156
156
  self.to_connect.append(node)
157
157
  if (
158
158
  isinstance(node, (uni.InForStmt, uni.IterForStmt))
@@ -176,6 +176,8 @@ class CFGBuildPass(UniPass):
176
176
  if from_node in self.to_connect:
177
177
  self.to_connect.remove(from_node)
178
178
  self.ability_stack.pop()
179
+ elif isinstance(node, (uni.IfStmt, uni.ElseIf)) and not node.else_body:
180
+ self.to_connect.append(node)
179
181
 
180
182
  def after_pass(self) -> None:
181
183
  """After pass."""
@@ -299,3 +301,21 @@ class CoalesceBBPass(UniPass):
299
301
 
300
302
  dot += "}\n"
301
303
  return dot
304
+
305
+
306
+ def cfg_dot_from_file(file_name: str) -> str:
307
+ """Print the control flow graph."""
308
+ from jaclang.compiler.program import JacProgram
309
+
310
+ with open(file_name, "r") as f:
311
+ file_source = f.read()
312
+
313
+ ir = (prog := JacProgram()).compile(use_str=file_source, file_path=file_name)
314
+
315
+ cfg_pass = CoalesceBBPass(
316
+ ir_in=ir,
317
+ prog=prog,
318
+ )
319
+
320
+ dot = cfg_pass.printgraph_cfg()
321
+ return dot
@@ -62,9 +62,16 @@ class InheritancePass(Transform[uni.Module, uni.Module]):
62
62
 
63
63
  base_class_symbol_table = base_class_symbol.symbol_table
64
64
 
65
+ # FIXME: If the base class symbol is imported from another module, the symbol table
66
+ # will be None. The imported symbols were ignored and introduced in the typecheck
67
+ # for imported module items. This needs to be investigated to ensure that even imported
68
+ # classes should have a symbol table (unless the module is not parsed and decided not to).
69
+ if base_class_symbol_table is None:
70
+ return
71
+
65
72
  if self.is_missing_py_symbol_table(base_class_symbol, base_class_symbol_table):
66
73
  return
67
- assert base_class_symbol_table is not None
74
+
68
75
  node.sym_tab.inherit_sym_tab(base_class_symbol_table)
69
76
 
70
77
  def inherit_from_atom_trailer(
@@ -187,6 +187,15 @@ class PyastGenPass(UniPass):
187
187
  ),
188
188
  )
189
189
 
190
+ def needs_mtllm(self) -> None:
191
+ """Ensure byLLM is imported only once."""
192
+ self._add_preamble_once(
193
+ self.needs_mtllm.__name__,
194
+ ast3.Import(
195
+ names=[self.sync(ast3.alias(name="byllm"), jac_node=self.ir_out)]
196
+ ),
197
+ )
198
+
190
199
  def needs_enum(self) -> None:
191
200
  """Ensure Enum utilities are imported only once."""
192
201
  self._add_preamble_once(
@@ -735,17 +744,25 @@ class PyastGenPass(UniPass):
735
744
  self, model: ast3.expr, caller: ast3.expr, args: ast3.Dict
736
745
  ) -> ast3.Call:
737
746
  """Reusable method to codegen call_llm(model, caller, args)."""
738
- return self.sync(
747
+ self.needs_mtllm()
748
+ mtir_cls_ast = self.sync(
749
+ ast3.Attribute(
750
+ value=self.sync(ast3.Name(id="byllm", ctx=ast3.Load())),
751
+ attr="MTIR",
752
+ ctx=ast3.Load(),
753
+ )
754
+ )
755
+ mtir_ast = self.sync(
739
756
  ast3.Call(
740
- func=self.jaclib_obj("call_llm"),
757
+ func=self.sync(
758
+ ast3.Attribute(
759
+ value=mtir_cls_ast,
760
+ attr="factory",
761
+ ctx=ast3.Load(),
762
+ )
763
+ ),
741
764
  args=[],
742
765
  keywords=[
743
- self.sync(
744
- ast3.keyword(
745
- arg="model",
746
- value=model,
747
- )
748
- ),
749
766
  self.sync(
750
767
  ast3.keyword(
751
768
  arg="caller",
@@ -758,6 +775,38 @@ class PyastGenPass(UniPass):
758
775
  value=args,
759
776
  )
760
777
  ),
778
+ self.sync(
779
+ ast3.keyword(
780
+ arg="call_params",
781
+ value=self.sync(
782
+ ast3.Attribute(
783
+ value=model,
784
+ attr="call_params",
785
+ ctx=ast3.Load(),
786
+ ),
787
+ ),
788
+ )
789
+ ),
790
+ ],
791
+ )
792
+ )
793
+ return self.sync(
794
+ ast3.Call(
795
+ func=self.jaclib_obj("call_llm"),
796
+ args=[],
797
+ keywords=[
798
+ self.sync(
799
+ ast3.keyword(
800
+ arg="model",
801
+ value=model,
802
+ )
803
+ ),
804
+ self.sync(
805
+ ast3.keyword(
806
+ arg="mtir",
807
+ value=mtir_ast,
808
+ )
809
+ ),
761
810
  ],
762
811
  )
763
812
  )
@@ -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,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,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,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}")