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.

Files changed (32) hide show
  1. jaclang/cli/cli.md +3 -3
  2. jaclang/cli/cli.py +25 -11
  3. jaclang/cli/cmdreg.py +1 -140
  4. jaclang/compiler/passes/main/pyast_gen_pass.py +13 -3
  5. jaclang/compiler/passes/main/pyast_load_pass.py +14 -20
  6. jaclang/compiler/passes/main/tests/fixtures/checker_binary_op.jac +21 -0
  7. jaclang/compiler/passes/main/tests/fixtures/checker_call_expr_class.jac +12 -0
  8. jaclang/compiler/passes/main/tests/fixtures/checker_cyclic_symbol.jac +4 -0
  9. jaclang/compiler/passes/main/tests/fixtures/checker_expr_call.jac +9 -0
  10. jaclang/compiler/passes/main/tests/fixtures/checker_import_missing_module.jac +13 -0
  11. jaclang/compiler/passes/main/tests/fixtures/checker_magic_call.jac +17 -0
  12. jaclang/compiler/passes/main/tests/fixtures/checker_mod_path.jac +8 -0
  13. jaclang/compiler/passes/main/tests/test_checker_pass.py +74 -0
  14. jaclang/compiler/passes/main/type_checker_pass.py +24 -5
  15. jaclang/compiler/type_system/operations.py +104 -0
  16. jaclang/compiler/type_system/type_evaluator.py +141 -2
  17. jaclang/langserve/engine.jac +135 -91
  18. jaclang/langserve/server.jac +21 -14
  19. jaclang/langserve/tests/server_test/test_lang_serve.py +2 -5
  20. jaclang/langserve/tests/test_dev_server.py +1 -1
  21. jaclang/langserve/tests/test_server.py +9 -2
  22. jaclang/langserve/utils.jac +44 -48
  23. jaclang/tests/fixtures/jac_run_py_bugs.py +18 -0
  24. jaclang/tests/fixtures/jac_run_py_import.py +13 -0
  25. jaclang/tests/fixtures/lambda_arg_annotation.jac +15 -0
  26. jaclang/tests/fixtures/lambda_self.jac +18 -0
  27. jaclang/tests/test_cli.py +66 -17
  28. jaclang/utils/module_resolver.py +1 -1
  29. {jaclang-0.8.6.dist-info → jaclang-0.8.7.dist-info}/METADATA +3 -2
  30. {jaclang-0.8.6.dist-info → jaclang-0.8.7.dist-info}/RECORD +32 -20
  31. {jaclang-0.8.6.dist-info → jaclang-0.8.7.dist-info}/WHEEL +1 -1
  32. {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
- return self._get_type_of_symbol(symbol)
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.ModuleType:
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.