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.
- jaclang/cli/cli.md +4 -3
- jaclang/cli/cli.py +63 -29
- jaclang/cli/cmdreg.py +1 -140
- jaclang/compiler/passes/main/__init__.py +2 -0
- jaclang/compiler/passes/main/cfg_build_pass.py +21 -1
- jaclang/compiler/passes/main/inheritance_pass.py +8 -1
- jaclang/compiler/passes/main/pyast_gen_pass.py +70 -11
- jaclang/compiler/passes/main/pyast_load_pass.py +14 -20
- jaclang/compiler/passes/main/sym_tab_build_pass.py +4 -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_binary_op.jac +21 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_call_expr_class.jac +12 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_cyclic_symbol.jac +4 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_expr_call.jac +9 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_import_missing_module.jac +13 -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/checker_magic_call.jac +17 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_mod_path.jac +8 -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/type_annotation_assignment.jac +8 -0
- jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +62 -24
- jaclang/compiler/passes/main/tests/test_checker_pass.py +161 -0
- jaclang/compiler/passes/main/type_checker_pass.py +147 -0
- jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +1 -4
- jaclang/compiler/program.py +17 -3
- jaclang/compiler/type_system/__init__.py +1 -0
- jaclang/compiler/type_system/operations.py +104 -0
- jaclang/compiler/type_system/type_evaluator.py +560 -0
- jaclang/compiler/type_system/type_utils.py +41 -0
- jaclang/compiler/type_system/types.py +240 -0
- jaclang/compiler/unitree.py +15 -9
- jaclang/langserve/dev_engine.jac +645 -0
- jaclang/langserve/dev_server.jac +201 -0
- jaclang/langserve/engine.jac +135 -91
- jaclang/langserve/server.jac +21 -14
- jaclang/langserve/tests/server_test/test_lang_serve.py +2 -5
- jaclang/langserve/tests/test_dev_server.py +80 -0
- jaclang/langserve/tests/test_server.py +9 -2
- jaclang/langserve/utils.jac +44 -48
- jaclang/runtimelib/builtin.py +28 -39
- jaclang/runtimelib/importer.py +1 -1
- 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 +13 -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/jac_run_py_bugs.py +18 -0
- jaclang/tests/fixtures/jac_run_py_import.py +13 -0
- jaclang/tests/fixtures/lambda_arg_annotation.jac +15 -0
- jaclang/tests/fixtures/lambda_self.jac +18 -0
- 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/test_cli.py +103 -14
- jaclang/tests/test_language.py +10 -4
- jaclang/utils/lang_tools.py +3 -0
- jaclang/utils/module_resolver.py +1 -1
- {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/METADATA +4 -2
- {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/RECORD +70 -37
- {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/WHEEL +1 -1
- {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)
|
jaclang/compiler/program.py
CHANGED
|
@@ -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,
|
|
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(
|
|
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()
|