jaclang 0.8.6__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 +3 -3
- jaclang/cli/cli.py +25 -11
- jaclang/cli/cmdreg.py +1 -140
- jaclang/compiler/passes/main/pyast_gen_pass.py +13 -3
- jaclang/compiler/passes/main/pyast_load_pass.py +14 -20
- 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_magic_call.jac +17 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_mod_path.jac +8 -0
- jaclang/compiler/passes/main/tests/test_checker_pass.py +74 -0
- jaclang/compiler/passes/main/type_checker_pass.py +24 -5
- jaclang/compiler/type_system/operations.py +104 -0
- jaclang/compiler/type_system/type_evaluator.py +141 -2
- 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 +1 -1
- jaclang/langserve/tests/test_server.py +9 -2
- jaclang/langserve/utils.jac +44 -48
- 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/test_cli.py +66 -17
- jaclang/utils/module_resolver.py +1 -1
- {jaclang-0.8.6.dist-info → jaclang-0.8.7.dist-info}/METADATA +3 -2
- {jaclang-0.8.6.dist-info → jaclang-0.8.7.dist-info}/RECORD +32 -20
- {jaclang-0.8.6.dist-info → jaclang-0.8.7.dist-info}/WHEEL +1 -1
- {jaclang-0.8.6.dist-info → jaclang-0.8.7.dist-info}/entry_points.txt +0 -0
|
@@ -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()
|
|
@@ -16,6 +16,7 @@ from jaclang.compiler.type_system import types
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
from jaclang.compiler.program import JacProgram
|
|
18
18
|
|
|
19
|
+
from . import operations
|
|
19
20
|
from .type_utils import ClassMember
|
|
20
21
|
from .types import TypeBase
|
|
21
22
|
|
|
@@ -44,6 +45,21 @@ class PrefetchedTypes:
|
|
|
44
45
|
template_class: TypeBase | None = None
|
|
45
46
|
|
|
46
47
|
|
|
48
|
+
@dataclass
|
|
49
|
+
class SymbolResolutionStackEntry:
|
|
50
|
+
"""Represents a single entry in the symbol resolution stack."""
|
|
51
|
+
|
|
52
|
+
symbol: uni.Symbol
|
|
53
|
+
|
|
54
|
+
# Initially true, it's set to false if a recursion
|
|
55
|
+
# is detected.
|
|
56
|
+
is_result_valid: bool = True
|
|
57
|
+
|
|
58
|
+
# Some limited forms of recursion are allowed. In these
|
|
59
|
+
# cases, a partially-constructed type can be registered.
|
|
60
|
+
partial_type: TypeBase | None = None
|
|
61
|
+
|
|
62
|
+
|
|
47
63
|
class TypeEvaluator:
|
|
48
64
|
"""Type evaluator for JacLang."""
|
|
49
65
|
|
|
@@ -58,14 +74,55 @@ class TypeEvaluator:
|
|
|
58
74
|
in some place then it will not be available in the evaluator, So we
|
|
59
75
|
are prefetching the builtins at the constructor level once.
|
|
60
76
|
"""
|
|
77
|
+
self.symbol_resolution_stack: list[SymbolResolutionStackEntry] = []
|
|
61
78
|
self.builtins_module = builtins_module
|
|
62
79
|
self.program = program
|
|
63
80
|
self.prefetch = self._prefetch_types()
|
|
64
81
|
|
|
82
|
+
# -------------------------------------------------------------------------
|
|
83
|
+
# Symbol resolution stack
|
|
84
|
+
# -------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
def get_index_of_symbol_resolution(self, symbol: uni.Symbol) -> int | None:
|
|
87
|
+
"""Get the index of a symbol in the resolution stack."""
|
|
88
|
+
for i, entry in enumerate(self.symbol_resolution_stack):
|
|
89
|
+
if entry.symbol == symbol:
|
|
90
|
+
return i
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
def push_symbol_resolution(self, symbol: uni.Symbol) -> bool:
|
|
94
|
+
"""
|
|
95
|
+
Push a symbol onto the resolution stack.
|
|
96
|
+
|
|
97
|
+
Return False if recursion detected and in that case it won't push the symbol.
|
|
98
|
+
"""
|
|
99
|
+
idx = self.get_index_of_symbol_resolution(symbol)
|
|
100
|
+
if idx is not None:
|
|
101
|
+
# Mark all of the entries between these two as invalid.
|
|
102
|
+
for i in range(idx, len(self.symbol_resolution_stack)):
|
|
103
|
+
entry = self.symbol_resolution_stack[i]
|
|
104
|
+
entry.is_result_valid = False
|
|
105
|
+
return False
|
|
106
|
+
self.symbol_resolution_stack.append(SymbolResolutionStackEntry(symbol=symbol))
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
def pop_symbol_resolution(self, symbol: uni.Symbol) -> bool:
|
|
110
|
+
"""Pop a symbol from the resolution stack."""
|
|
111
|
+
popped_entry = self.symbol_resolution_stack.pop()
|
|
112
|
+
assert popped_entry.symbol == symbol
|
|
113
|
+
return popped_entry.is_result_valid
|
|
114
|
+
|
|
65
115
|
# Pyright equivalent function name = getEffectiveTypeOfSymbol.
|
|
66
116
|
def get_type_of_symbol(self, symbol: uni.Symbol) -> TypeBase:
|
|
67
117
|
"""Return the effective type of the symbol."""
|
|
68
|
-
|
|
118
|
+
if self.push_symbol_resolution(symbol):
|
|
119
|
+
try:
|
|
120
|
+
return self._get_type_of_symbol(symbol)
|
|
121
|
+
finally:
|
|
122
|
+
self.pop_symbol_resolution(symbol)
|
|
123
|
+
|
|
124
|
+
# If we reached here that means we have a cyclic symbolic reference.
|
|
125
|
+
return types.UnknownType()
|
|
69
126
|
|
|
70
127
|
# NOTE: This function doesn't exists in pyright, however it exists as a helper function
|
|
71
128
|
# for the following functions.
|
|
@@ -92,10 +149,13 @@ class TypeEvaluator:
|
|
|
92
149
|
mod.parent_scope = self.builtins_module
|
|
93
150
|
return mod
|
|
94
151
|
|
|
95
|
-
def get_type_of_module(self, node: uni.ModulePath) -> types.
|
|
152
|
+
def get_type_of_module(self, node: uni.ModulePath) -> types.TypeBase:
|
|
96
153
|
"""Return the effective type of the module."""
|
|
97
154
|
if node.name_spec.type is not None:
|
|
98
155
|
return cast(types.ModuleType, node.name_spec.type)
|
|
156
|
+
if not Path(node.resolve_relative_path()).exists():
|
|
157
|
+
node.name_spec.type = types.UnknownType()
|
|
158
|
+
return node.name_spec.type
|
|
99
159
|
|
|
100
160
|
mod: uni.Module = self._import_module_from_path(node.resolve_relative_path())
|
|
101
161
|
mod_type = types.ModuleType(
|
|
@@ -149,6 +209,11 @@ class TypeEvaluator:
|
|
|
149
209
|
# import from mod { item }
|
|
150
210
|
else:
|
|
151
211
|
mod_type = self.get_type_of_module(import_node.from_loc)
|
|
212
|
+
if not isinstance(mod_type, types.ModuleType):
|
|
213
|
+
node.name_spec.type = types.UnknownType()
|
|
214
|
+
# TODO: Add diagnostic that from_loc is not accessible.
|
|
215
|
+
# Eg: 'Import "scipy" could not be resolved'
|
|
216
|
+
return node.name_spec.type
|
|
152
217
|
if sym := mod_type.symbol_table.lookup(node.name.value, deep=True):
|
|
153
218
|
node.name.sym = sym
|
|
154
219
|
if node.alias:
|
|
@@ -178,6 +243,31 @@ class TypeEvaluator:
|
|
|
178
243
|
node.name_spec.type = cls_type
|
|
179
244
|
return cls_type
|
|
180
245
|
|
|
246
|
+
def get_type_of_ability(self, node: uni.Ability) -> TypeBase:
|
|
247
|
+
"""Return the effective type of an ability."""
|
|
248
|
+
if node.name_spec.type is not None:
|
|
249
|
+
return node.name_spec.type
|
|
250
|
+
|
|
251
|
+
if not isinstance(node.signature, uni.FuncSignature):
|
|
252
|
+
node.name_spec.type = types.UnknownType()
|
|
253
|
+
return node.name_spec.type
|
|
254
|
+
|
|
255
|
+
if not isinstance(node.signature.return_type, uni.Expr):
|
|
256
|
+
node.name_spec.type = types.UnknownType()
|
|
257
|
+
return node.name_spec.type
|
|
258
|
+
|
|
259
|
+
return_type = self._convert_to_instance(
|
|
260
|
+
self.get_type_of_expression(node.signature.return_type)
|
|
261
|
+
)
|
|
262
|
+
func_type = types.FunctionType(
|
|
263
|
+
func_name=node.name_spec.sym_name,
|
|
264
|
+
return_type=return_type,
|
|
265
|
+
parameters=[], # TODO:
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
node.name_spec.type = func_type
|
|
269
|
+
return func_type
|
|
270
|
+
|
|
181
271
|
def get_type_of_string(self, node: uni.String | uni.MultiString) -> TypeBase:
|
|
182
272
|
"""Return the effective type of the string."""
|
|
183
273
|
# FIXME: Strings are a type of LiteralString type:
|
|
@@ -236,6 +326,29 @@ class TypeEvaluator:
|
|
|
236
326
|
# FIXME: This is temporary.
|
|
237
327
|
return src_type == dest_type
|
|
238
328
|
|
|
329
|
+
# TODO: This should take an argument list as parameter.
|
|
330
|
+
def get_type_of_magic_method_call(
|
|
331
|
+
self, obj_type: TypeBase, method_name: str
|
|
332
|
+
) -> TypeBase | None:
|
|
333
|
+
"""Return the effective return type of a magic method call."""
|
|
334
|
+
if obj_type.category == types.TypeCategory.Class:
|
|
335
|
+
# TODO: getTypeOfBoundMember() <-- Implement this if needed, for the simple case
|
|
336
|
+
# we'll directly call member lookup.
|
|
337
|
+
#
|
|
338
|
+
# WE'RE DAVIATING FROM PYRIGHT FOR THIS METHOD HEAVILY HOWEVER THIS CAN BE RE-WRITTEN IF NEEDED.
|
|
339
|
+
#
|
|
340
|
+
assert isinstance(obj_type, types.ClassType) # <-- To make typecheck happy.
|
|
341
|
+
if member := self._lookup_class_member(obj_type, method_name):
|
|
342
|
+
member_ty = self.get_type_of_symbol(member.symbol)
|
|
343
|
+
if isinstance(member_ty, types.FunctionType):
|
|
344
|
+
return member_ty.return_type
|
|
345
|
+
# If we reached here, magic method is not a function.
|
|
346
|
+
# 1. recursively check __call__() on the type, TODO
|
|
347
|
+
# 2. if any or unknown, return getUnknownTypeForCallable() TODO
|
|
348
|
+
# 3. return undefined.
|
|
349
|
+
return None
|
|
350
|
+
return None
|
|
351
|
+
|
|
239
352
|
def _assign_class(
|
|
240
353
|
self, src_type: types.ClassType, dest_type: types.ClassType
|
|
241
354
|
) -> bool:
|
|
@@ -298,6 +411,9 @@ class TypeEvaluator:
|
|
|
298
411
|
case uni.Archetype():
|
|
299
412
|
return self.get_type_of_class(node)
|
|
300
413
|
|
|
414
|
+
case uni.Ability():
|
|
415
|
+
return self.get_type_of_ability(node)
|
|
416
|
+
|
|
301
417
|
# This actually defined in the function getTypeForDeclaration();
|
|
302
418
|
# Pyright has DeclarationType.Variable.
|
|
303
419
|
case uni.Name():
|
|
@@ -376,8 +492,31 @@ class TypeEvaluator:
|
|
|
376
492
|
else: # <expr>[<expr>]
|
|
377
493
|
pass # TODO:
|
|
378
494
|
|
|
495
|
+
case uni.AtomUnit():
|
|
496
|
+
return self.get_type_of_expression(expr.value)
|
|
497
|
+
|
|
498
|
+
case uni.FuncCall():
|
|
499
|
+
caller_type = self.get_type_of_expression(expr.target)
|
|
500
|
+
if isinstance(caller_type, types.FunctionType):
|
|
501
|
+
return caller_type.return_type or types.UnknownType()
|
|
502
|
+
if (
|
|
503
|
+
isinstance(caller_type, types.ClassType)
|
|
504
|
+
and caller_type.is_instantiable_class()
|
|
505
|
+
):
|
|
506
|
+
return caller_type.clone_as_instance()
|
|
507
|
+
if caller_type.is_class_instance():
|
|
508
|
+
magic_call_ret = self.get_type_of_magic_method_call(
|
|
509
|
+
caller_type, "__call__"
|
|
510
|
+
)
|
|
511
|
+
if magic_call_ret:
|
|
512
|
+
return magic_call_ret
|
|
513
|
+
|
|
514
|
+
case uni.BinaryExpr():
|
|
515
|
+
return operations.get_type_of_binary_operation(self, expr)
|
|
516
|
+
|
|
379
517
|
case uni.Name():
|
|
380
518
|
if symbol := expr.sym_tab.lookup(expr.value, deep=True):
|
|
519
|
+
expr.sym = symbol
|
|
381
520
|
return self.get_type_of_symbol(symbol)
|
|
382
521
|
|
|
383
522
|
# TODO: More expressions.
|