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
@@ -0,0 +1,147 @@
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
+ # REVIEW: Making the evaluator a static (singleton) variable to make sure only one
40
+ # instance is used across mulitple compilation units. This can also be attached to an
41
+ # attribute of JacProgram, however the evaluator is a temproary object that we dont
42
+ # want bound to the program for long term, Also the program is the one that will be
43
+ # dumped in the compiled bundle.
44
+ _EVALUATOR: TypeEvaluator | None = None
45
+
46
+ def before_pass(self) -> None:
47
+ """Initialize the checker pass."""
48
+ self._load_builtins_stub_module()
49
+ self._insert_builtin_symbols()
50
+
51
+ @property
52
+ def evaluator(self) -> TypeEvaluator:
53
+ """Return the type evaluator."""
54
+ if TypeCheckPass._EVALUATOR is None:
55
+ assert TypeCheckPass._BUILTINS_MODULE is not None
56
+ TypeCheckPass._EVALUATOR = TypeEvaluator(
57
+ builtins_module=TypeCheckPass._BUILTINS_MODULE,
58
+ program=self.prog,
59
+ )
60
+ return TypeCheckPass._EVALUATOR
61
+
62
+ # --------------------------------------------------------------------------
63
+ # Internal helper functions
64
+ # --------------------------------------------------------------------------
65
+
66
+ def _binding_builtins(self) -> bool:
67
+ """Return true if we're binding the builtins stub file."""
68
+ return self.ir_in == TypeCheckPass._BUILTINS_MODULE
69
+
70
+ def _load_builtins_stub_module(self) -> None:
71
+ """Return the builtins stub module.
72
+
73
+ This will parse and cache the stub file and return the cached module on
74
+ subsequent calls.
75
+ """
76
+ if self._binding_builtins() or TypeCheckPass._BUILTINS_MODULE is not None:
77
+ return
78
+
79
+ if not os.path.exists(TypeCheckPass._BUILTINS_STUB_FILE_PATH):
80
+ raise FileNotFoundError(
81
+ f"Builtins stub file not found at {TypeCheckPass._BUILTINS_STUB_FILE_PATH}"
82
+ )
83
+
84
+ file_content = read_file_with_encoding(TypeCheckPass._BUILTINS_STUB_FILE_PATH)
85
+ uni_source = uni.Source(file_content, TypeCheckPass._BUILTINS_STUB_FILE_PATH)
86
+ mod = PyastBuildPass(
87
+ ir_in=uni.PythonModuleAst(
88
+ py_ast.parse(file_content),
89
+ orig_src=uni_source,
90
+ ),
91
+ prog=self.prog,
92
+ ).ir_out
93
+ SymTabBuildPass(ir_in=mod, prog=self.prog)
94
+ TypeCheckPass._BUILTINS_MODULE = mod
95
+
96
+ def _insert_builtin_symbols(self) -> None:
97
+ if self._binding_builtins():
98
+ return
99
+
100
+ # TODO: Insert these symbols.
101
+ # Reference: pyright Binder.bindModule()
102
+ #
103
+ # List taken from https://docs.python.org/3/reference/import.html#__name__
104
+ # '__name__', '__loader__', '__package__', '__spec__', '__path__',
105
+ # '__file__', '__cached__', '__dict__', '__annotations__',
106
+ # '__builtins__', '__doc__',
107
+ assert (
108
+ TypeCheckPass._BUILTINS_MODULE is not None
109
+ ), "Builtins module is not loaded"
110
+ if self.ir_in.parent_scope is not None:
111
+ self.log_info("Builtins module is already bound, skipping.")
112
+ return
113
+ # Review: If we ever assume a module cannot have a parent scope, this will
114
+ # break that contract.
115
+ self.ir_in.parent_scope = TypeCheckPass._BUILTINS_MODULE
116
+
117
+ # --------------------------------------------------------------------------
118
+ # Ast walker hooks
119
+ # --------------------------------------------------------------------------
120
+
121
+ def exit_assignment(self, node: uni.Assignment) -> None:
122
+ """Pyright: Checker.visitAssignment(node: AssignmentNode): boolean."""
123
+ # TODO: In pyright this logic is present at evaluateTypesForAssignmentStatement
124
+ # and we're calling getTypeForStatement from here, This can be moved into the
125
+ # other place or we can keep it here.
126
+ #
127
+ # Grep this in pyright TypeEvaluator.ts:
128
+ # `} else if (node.d.leftExpr.nodeType === ParseNodeType.Name) {`
129
+ #
130
+ if len(node.target) == 1 and (node.value is not None): # Simple assignment.
131
+ left_type = self.evaluator.get_type_of_expression(node.target[0])
132
+ right_type = self.evaluator.get_type_of_expression(node.value)
133
+ if not self.evaluator.assign_type(right_type, left_type):
134
+ self.log_error(f"Cannot assign {right_type} to {left_type}")
135
+ else:
136
+ pass # TODO: handle
137
+
138
+ def exit_atom_trailer(self, node: uni.AtomTrailer) -> None:
139
+ """Handle the atom trailer node."""
140
+ self.evaluator.get_type_of_expression(node)
141
+
142
+ def exit_func_call(self, node: uni.FuncCall) -> None:
143
+ """Handle the function call node."""
144
+ # TODO:
145
+ # 1. Function Existence & Callable Validation
146
+ # 2. Argument Matching(count, types, names)
147
+ self.evaluator.get_type_of_expression(node)
@@ -53,7 +53,4 @@ with entry {
53
53
  }
54
54
 
55
55
 
56
- def generate_joke -> dict[str, str] by llm(
57
- incl_info={"jokes_example" : self.jokes },
58
- temperature=0.0
59
- );
56
+ def generate_joke -> dict[str, str] by llm();
@@ -26,6 +26,7 @@ from jaclang.compiler.passes.main import (
26
26
  SymTabBuildPass,
27
27
  SymTabLinkPass,
28
28
  Transform,
29
+ TypeCheckPass,
29
30
  )
30
31
  from jaclang.compiler.passes.tool import (
31
32
  DocIRGenPass,
@@ -46,6 +47,9 @@ ir_gen_sched = [
46
47
  CFGBuildPass,
47
48
  InheritancePass,
48
49
  ]
50
+ type_check_sched: list = [
51
+ TypeCheckPass,
52
+ ]
49
53
  py_code_gen = [
50
54
  PyastGenPass,
51
55
  PyJacAstLinkPass,
@@ -102,12 +106,20 @@ class JacProgram:
102
106
  return mod
103
107
 
104
108
  def compile(
105
- self, file_path: str, use_str: str | None = None, no_cgen: bool = False
109
+ self,
110
+ file_path: str,
111
+ use_str: str | None = None,
112
+ # TODO: Create a compilation options class and put the bellow
113
+ # options in it.
114
+ no_cgen: bool = False,
115
+ type_check: bool = False,
106
116
  ) -> uni.Module:
107
117
  """Convert a Jac file to an AST."""
108
118
  keep_str = use_str or read_file_with_encoding(file_path)
109
119
  mod_targ = self.parse_str(keep_str, file_path)
110
120
  self.run_schedule(mod=mod_targ, passes=ir_gen_sched)
121
+ if type_check:
122
+ self.run_schedule(mod=mod_targ, passes=type_check_sched)
111
123
  if not no_cgen:
112
124
  self.run_schedule(mod=mod_targ, passes=py_code_gen)
113
125
  return mod_targ
@@ -119,9 +131,11 @@ class JacProgram:
119
131
  BinderPass(ir_in=mod_targ, prog=self)
120
132
  return mod_targ
121
133
 
122
- def build(self, file_path: str, use_str: str | None = None) -> uni.Module:
134
+ def build(
135
+ self, file_path: str, use_str: str | None = None, type_check: bool = False
136
+ ) -> uni.Module:
123
137
  """Convert a Jac file to an AST."""
124
- mod_targ = self.compile(file_path, use_str)
138
+ mod_targ = self.compile(file_path, use_str, type_check=type_check)
125
139
  JacImportDepsPass(ir_in=mod_targ, prog=self)
126
140
  for mod in self.mod.hub.values():
127
141
  SymTabLinkPass(ir_in=mod, prog=self)
@@ -0,0 +1 @@
1
+ """Jaclang type checking."""
@@ -0,0 +1,104 @@
1
+ # flake8: noqa: I300
2
+ """Provides type evaluation logic for unary, binary, augmented assignment and ternary operators.
3
+
4
+ PyrightReference: packages/pyright-internal/src/analyzer/operations.ts
5
+ """
6
+
7
+ from typing import TYPE_CHECKING
8
+
9
+ import jaclang.compiler.unitree as uni
10
+ from jaclang.compiler.constant import Tokens as Tok
11
+
12
+ from . import types as jtypes
13
+
14
+ if TYPE_CHECKING:
15
+ # Type evaluator is the one depends on this module and not the way around.
16
+ # however this module needs a reference to the type evaluator.
17
+ from .type_evaluator import TypeEvaluator
18
+
19
+
20
+ # Maps binary operators to the magic methods that implement them.
21
+ BINARY_OPERATOR_MAP: dict[str, tuple[str, str]] = {
22
+ Tok.PLUS: ("__add__", "__radd__"),
23
+ Tok.MINUS: ("__sub__", "__rsub__"),
24
+ Tok.STAR_MUL: ("__mul__", "__rmul__"),
25
+ Tok.FLOOR_DIV: ("__floordiv__", "__rfloordiv__"),
26
+ Tok.DIV: ("__truediv__", "__rtruediv__"),
27
+ Tok.MOD: ("__mod__", "__rmod__"),
28
+ Tok.STAR_POW: ("__pow__", "__rpow__"),
29
+ Tok.DECOR_OP: ("__matmul__", "__rmatmul__"),
30
+ Tok.BW_AND: ("__and__", "__rand__"),
31
+ Tok.BW_OR: ("__or__", "__ror__"),
32
+ Tok.BW_XOR: ("__xor__", "__rxor__"),
33
+ Tok.LSHIFT: ("__lshift__", "__rlshift__"),
34
+ Tok.RSHIFT: ("__rshift__", "__rrshift__"),
35
+ Tok.EE: ("__eq__", "__eq__"),
36
+ Tok.NE: ("__ne__", "__ne__"),
37
+ Tok.LT: ("__lt__", "__gt__"),
38
+ Tok.LTE: ("__le__", "__ge__"),
39
+ Tok.GT: ("__gt__", "__lt__"),
40
+ Tok.GTE: ("__ge__", "__le__"),
41
+ }
42
+
43
+
44
+ # Mirror of the `export function getTypeOfBinaryOperation` function in pyright.
45
+ def get_type_of_binary_operation(
46
+ evaluator: "TypeEvaluator", expr: uni.BinaryExpr
47
+ ) -> jtypes.TypeBase:
48
+ """Return the binary operator's jtype."""
49
+ left_type = evaluator.get_type_of_expression(expr.left)
50
+ right_type = evaluator.get_type_of_expression(expr.right)
51
+
52
+ # TODO: Check how pyright is dealing with chaining operation (a < b < c < d) and
53
+ # it handles here with the condition `if operatorSupportsChaining()`.:
54
+
55
+ # TODO:
56
+ # Is this a "|" operator used in a context where it is supposed to be
57
+ # interpreted as a union operator?
58
+
59
+ # pyright is using another function however I don't see the need of it yet, so imma use
60
+ # the logic here, if needed define `validateBinaryOperation` for re-usability.
61
+
62
+ # TODO: Handle and, or
63
+ #
64
+ # <left> and <right> <--- This is equlavent to the bellow
65
+ #
66
+ # def left_and_right(left, right):
67
+ # if bool(left):
68
+ # return left
69
+ # return right
70
+ #
71
+ # And the type will be Union[left.type, right.type]
72
+ #
73
+
74
+ # NOTE: in will call `__contains__` magic method in custom type and should return `bool`
75
+ # however it can technically return anything otherthan `bool` and pyright is handing that way
76
+ # I don't see `__str__` method return anything other than string should be valid either.
77
+ if (expr.op in (Tok.KW_IS, Tok.KW_ISN, Tok.KW_IN, Tok.KW_NIN)) and (
78
+ evaluator.prefetch.bool_class is not None
79
+ ):
80
+ evaluator._convert_to_instance(evaluator.prefetch.bool_class)
81
+
82
+ # TODO: `expr.op` can be of 3 types, Token, connect, disconnect.
83
+ if isinstance(expr.op, uni.Token) and (expr.op.name in BINARY_OPERATOR_MAP):
84
+ # TODO: validateArithmeticOperation() has more cases and special conditions that we may
85
+ # need to implement in the future however in this simple case we're implementing the
86
+ # bare minimal checking.
87
+ magic, rmagic = BINARY_OPERATOR_MAP[expr.op.name]
88
+
89
+ # TODO: Handle overloaded call check:
90
+ # Grep this in pyright typeEvaluator.ts:
91
+ # callResult.overloadsUsedForCall.forEach((overload) => {
92
+ # overloadsUsedForCall.push(overload);
93
+ # ...
94
+ # });
95
+
96
+ # FIXME: We need to have validateCallArgs() method and do a check here before returning.
97
+ return (
98
+ evaluator.get_type_of_magic_method_call(left_type, magic)
99
+ or evaluator.get_type_of_magic_method_call(right_type, rmagic)
100
+ or jtypes.UnknownType()
101
+ )
102
+
103
+ # TODO: Handle for connect and disconnect operators.
104
+ return jtypes.UnknownType()