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,560 @@
1
+ """
2
+ Type system evaluator for JacLang.
3
+
4
+ PyrightReference:
5
+ packages/pyright-internal/src/analyzer/typeEvaluator.ts
6
+ packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts
7
+ """
8
+
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import TYPE_CHECKING, cast
12
+
13
+ import jaclang.compiler.unitree as uni
14
+ from jaclang.compiler.type_system import types
15
+
16
+ if TYPE_CHECKING:
17
+ from jaclang.compiler.program import JacProgram
18
+
19
+ from . import operations
20
+ from .type_utils import ClassMember
21
+ from .types import TypeBase
22
+
23
+
24
+ @dataclass
25
+ class PrefetchedTypes:
26
+ """Types whose definitions are prefetched and cached by the type evaluator."""
27
+
28
+ none_type_class: TypeBase | None = None
29
+ object_class: TypeBase | None = None
30
+ type_class: TypeBase | None = None
31
+ union_type_class: TypeBase | None = None
32
+ awaitable_class: TypeBase | None = None
33
+ function_class: TypeBase | None = None
34
+ method_class: TypeBase | None = None
35
+ tuple_class: TypeBase | None = None
36
+ bool_class: TypeBase | None = None
37
+ int_class: TypeBase | None = None
38
+ str_class: TypeBase | None = None
39
+ dict_class: TypeBase | None = None
40
+ module_type_class: TypeBase | None = None
41
+ typed_dict_class: TypeBase | None = None
42
+ typed_dict_private_class: TypeBase | None = None
43
+ supports_keys_and_get_item_class: TypeBase | None = None
44
+ mapping_class: TypeBase | None = None
45
+ template_class: TypeBase | None = None
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
+
63
+ class TypeEvaluator:
64
+ """Type evaluator for JacLang."""
65
+
66
+ def __init__(self, builtins_module: uni.Module, program: "JacProgram") -> None:
67
+ """Initialize the type evaluator with prefetched types.
68
+
69
+ Implementation Note:
70
+ --------------------
71
+ Pyright is prefetching the builtins when an evaluation is requested
72
+ on a node and from that node it does lookup for the builtins scope
73
+ and does the prefetch once, however if we forgot to call prefetch
74
+ in some place then it will not be available in the evaluator, So we
75
+ are prefetching the builtins at the constructor level once.
76
+ """
77
+ self.symbol_resolution_stack: list[SymbolResolutionStackEntry] = []
78
+ self.builtins_module = builtins_module
79
+ self.program = program
80
+ self.prefetch = self._prefetch_types()
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
+
115
+ # Pyright equivalent function name = getEffectiveTypeOfSymbol.
116
+ def get_type_of_symbol(self, symbol: uni.Symbol) -> TypeBase:
117
+ """Return the effective type of the 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()
126
+
127
+ # NOTE: This function doesn't exists in pyright, however it exists as a helper function
128
+ # for the following functions.
129
+ def _import_module_from_path(self, path: str) -> uni.Module:
130
+ """Import a module from the given path."""
131
+ # Get the module, if it's not loaded yet, compile and get it.
132
+ #
133
+ # TODO:
134
+ # We're not typechecking inside the module itself however we
135
+ # need to check if the module path is site-package or not and
136
+ # do typecheck inside as well.
137
+ mod: uni.Module
138
+ if path in self.program.mod.hub:
139
+ mod = self.program.mod.hub[path]
140
+ else:
141
+ mod = self.program.compile(path, no_cgen=True, type_check=False)
142
+ # FIXME: Inherit from builtin symbol table logic is currently implemented
143
+ # here and checker pass, however it should be in one location, since we're
144
+ # doing type_check=False, it doesn't set parent_scope to builtins, this
145
+ # needs to be done properly. The way jaclang handles symbol table is different
146
+ # than pyright, so we cannot strictly follow them, however as long as a new
147
+ # module has a parent scope as builtin scope, we're aligned with pyright.
148
+ if mod.parent_scope is None and mod is not self.builtins_module:
149
+ mod.parent_scope = self.builtins_module
150
+ return mod
151
+
152
+ def get_type_of_module(self, node: uni.ModulePath) -> types.TypeBase:
153
+ """Return the effective type of the module."""
154
+ if node.name_spec.type is not None:
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
159
+
160
+ mod: uni.Module = self._import_module_from_path(node.resolve_relative_path())
161
+ mod_type = types.ModuleType(
162
+ mod_name=node.name_spec.sym_name,
163
+ file_uri=Path(node.resolve_relative_path()).resolve(),
164
+ symbol_table=mod,
165
+ )
166
+
167
+ node.name_spec.type = mod_type
168
+ return mod_type
169
+
170
+ def get_type_of_module_item(self, node: uni.ModuleItem) -> types.TypeBase:
171
+ """Return the effective type of the module item."""
172
+ # Module item can be both a module or a member of a module.
173
+ # import from .. { mod } # <-- Here mod is not a member but a module itself.
174
+ # import from mod { item } # <-- Here item is not a module but a member of mod.
175
+ if node.name_spec.type is not None:
176
+ return node.name_spec.type
177
+
178
+ import_node = node.parent_of_type(uni.Import)
179
+ if import_node.from_loc:
180
+
181
+ from_path = Path(import_node.from_loc.resolve_relative_path())
182
+ is_dir = from_path.is_dir() or (from_path.stem == "__init__")
183
+
184
+ # import from .. { mod }
185
+ if is_dir:
186
+ mod_dir = from_path.parent if not from_path.is_dir() else from_path
187
+ # FIXME: Implement module resolution properly.
188
+ for ext in (".jac", ".py", ".pyi"):
189
+ if (path := (mod_dir / (node.name.value + ext)).resolve()).exists():
190
+ mod = self._import_module_from_path(str(path))
191
+ mod_type = types.ModuleType(
192
+ mod_name=node.name_spec.sym_name,
193
+ file_uri=path,
194
+ symbol_table=mod,
195
+ )
196
+ # Cache the type.
197
+ node.name_spec.type = mod_type
198
+
199
+ # FIXME: goto definition works on imported symbol by checking if it's a MODULE
200
+ # type and in that case it'll call resolve_relative_path on the parent node of
201
+ # the symbol's definition node (a module path), So the goto definition to work
202
+ # properly the category should be module on a module path, If we set like this
203
+ # below should work but because of the above assumption (should be a mod path)
204
+ # it won't, This needs to be discussed.
205
+ #
206
+ # node.name_spec._sym_category = uni.SymbolType.MODULE
207
+ return node.name_spec.type
208
+
209
+ # import from mod { item }
210
+ else:
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
217
+ if sym := mod_type.symbol_table.lookup(node.name.value, deep=True):
218
+ node.name.sym = sym
219
+ if node.alias:
220
+ node.alias.sym = sym
221
+ node.name_spec.type = self.get_type_of_symbol(sym)
222
+ return node.name_spec.type
223
+
224
+ return types.UnknownType()
225
+
226
+ def get_type_of_class(self, node: uni.Archetype) -> types.ClassType:
227
+ """Return the effective type of the class."""
228
+ # Is this type already cached?
229
+ if node.name_spec.type is not None:
230
+ return cast(types.ClassType, node.name_spec.type)
231
+
232
+ cls_type = types.ClassType(
233
+ types.ClassType.ClassDetailsShared(
234
+ class_name=node.name_spec.sym_name,
235
+ symbol_table=node,
236
+ # TODO: Resolve the base class expression and pass them here.
237
+ ),
238
+ flags=types.TypeFlags.Instantiable,
239
+ )
240
+
241
+ # Cache the type, pyright is doing invalidateTypeCacheIfCanceled()
242
+ # we're not doing that any time sooner.
243
+ node.name_spec.type = cls_type
244
+ return cls_type
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
+
271
+ def get_type_of_string(self, node: uni.String | uni.MultiString) -> TypeBase:
272
+ """Return the effective type of the string."""
273
+ # FIXME: Strings are a type of LiteralString type:
274
+ # "foo" is not `str` but Literal["foo"], however for now we'll
275
+ # not considering that and make it work and will implement that
276
+ # later.
277
+ #
278
+ # see: getTypeOfString() in pyright (it requires parsing the sub
279
+ # file of the typing module).
280
+ assert self.prefetch.str_class is not None
281
+ return self.prefetch.str_class
282
+
283
+ def get_type_of_int(self, node: uni.Int) -> TypeBase:
284
+ """Return the effective type of the int."""
285
+ assert self.prefetch.int_class is not None
286
+ return self.prefetch.int_class
287
+
288
+ # Pyright equivalent function name = getTypeOfExpression();
289
+ def get_type_of_expression(self, node: uni.Expr) -> TypeBase:
290
+ """Return the effective type of the expression."""
291
+ # If it's alreay "cached" return it.
292
+ if node.type is not None:
293
+ return node.type
294
+
295
+ result = self._get_type_of_expression_core(node)
296
+ # If the context has an expected type, pyright does a compatibility and set
297
+ # a diagnostics here, I don't understand why that might be necessary here.
298
+
299
+ node.type = result # Cache the result
300
+ return result
301
+
302
+ # Comments from pyright:
303
+ # // Determines if the source type can be assigned to the dest type.
304
+ # // If constraint are provided, type variables within the destType are
305
+ # // matched against existing type variables in the map. If a type variable
306
+ # // in the dest type is not in the type map already, it is assigned a type
307
+ # // and added to the map.
308
+ def assign_type(self, src_type: TypeBase, dest_type: TypeBase) -> bool:
309
+ """Assign the source type to the destination type."""
310
+ if types.TypeCategory.Unknown in (src_type.category, dest_type.category):
311
+ # NOTE: For now if we don't have the type info, we assume it's compatible.
312
+ # For strict mode we should disallow usage of unknown unless explicitly ignored.
313
+ return True
314
+ # FIXME: This logic is not valid, just here as a stub.
315
+ if types.TypeCategory.Unknown in (src_type.category, dest_type.category):
316
+ return True
317
+
318
+ if src_type == dest_type:
319
+ return True
320
+
321
+ if dest_type.is_class_instance() and src_type.is_class_instance():
322
+ assert isinstance(dest_type, types.ClassType)
323
+ assert isinstance(src_type, types.ClassType)
324
+ return self._assign_class(src_type, dest_type)
325
+
326
+ # FIXME: This is temporary.
327
+ return src_type == dest_type
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
+
352
+ def _assign_class(
353
+ self, src_type: types.ClassType, dest_type: types.ClassType
354
+ ) -> bool:
355
+ """Assign the source class type to the destination class type."""
356
+ if src_type.shared == dest_type.shared:
357
+ return True
358
+
359
+ # TODO: Search base classes and everything else pyright is doing.
360
+ return False
361
+
362
+ def _prefetch_types(self) -> "PrefetchedTypes":
363
+ """Return the prefetched types for the type evaluator."""
364
+ return PrefetchedTypes(
365
+ # TODO: Pyright first try load NoneType from typeshed and if it cannot
366
+ # then it set to unknown type.
367
+ none_type_class=types.UnknownType(),
368
+ object_class=self._get_builtin_type("object"),
369
+ type_class=self._get_builtin_type("type"),
370
+ # union_type_class=
371
+ # awaitable_class=
372
+ # function_class=
373
+ # method_class=
374
+ tuple_class=self._get_builtin_type("tuple"),
375
+ bool_class=self._get_builtin_type("bool"),
376
+ int_class=self._get_builtin_type("int"),
377
+ str_class=self._get_builtin_type("str"),
378
+ dict_class=self._get_builtin_type("dict"),
379
+ # module_type_class=
380
+ # typed_dict_class=
381
+ # typed_dict_private_class=
382
+ # supports_keys_and_get_item_class=
383
+ # mapping_class=
384
+ # template_class=
385
+ )
386
+
387
+ def _get_builtin_type(self, name: str) -> TypeBase:
388
+ """Return the built-in type with the given name."""
389
+ if (symbol := self.builtins_module.lookup(name)) is not None:
390
+ return self.get_type_of_symbol(symbol)
391
+ return types.UnknownType()
392
+
393
+ # This function is a combination of the bellow pyright functions.
394
+ # - getDeclaredTypeOfSymbol
395
+ # - getTypeForDeclaration
396
+ #
397
+ # Implementation Note:
398
+ # Pyright is actually have some duplicate logic for handling declared
399
+ # type and inferred type, we're going to unify them (if it's required
400
+ # in the future, we can refactor this).
401
+ def _get_type_of_symbol(self, symbol: uni.Symbol) -> TypeBase:
402
+ """Return the declared type of the symbol."""
403
+ node = symbol.decl.name_of
404
+ match node:
405
+ case uni.ModulePath():
406
+ return self.get_type_of_module(node)
407
+
408
+ case uni.ModuleItem():
409
+ return self.get_type_of_module_item(node)
410
+
411
+ case uni.Archetype():
412
+ return self.get_type_of_class(node)
413
+
414
+ case uni.Ability():
415
+ return self.get_type_of_ability(node)
416
+
417
+ # This actually defined in the function getTypeForDeclaration();
418
+ # Pyright has DeclarationType.Variable.
419
+ case uni.Name():
420
+ if isinstance(node.parent, uni.Assignment):
421
+ if node.parent.type_tag is not None:
422
+ annotation_type = self.get_type_of_expression(
423
+ node.parent.type_tag.tag
424
+ )
425
+ return self._convert_to_instance(annotation_type)
426
+
427
+ else: # Assignment without a type annotation.
428
+ if node.parent.value is not None:
429
+ return self.get_type_of_expression(node.parent.value)
430
+
431
+ case uni.HasVar():
432
+ if node.type_tag is not None:
433
+ annotation_type = self.get_type_of_expression(node.type_tag.tag)
434
+ return self._convert_to_instance(annotation_type)
435
+ else:
436
+ if node.value is not None:
437
+ return self.get_type_of_expression(node.value)
438
+
439
+ # TODO: Implement for functions, parameters, explicit type
440
+ # annotations in assignment etc.
441
+ return types.UnknownType()
442
+
443
+ # Pyright equivalent function name = getTypeOfExpressionCore();
444
+ def _get_type_of_expression_core(self, expr: uni.Expr) -> TypeBase:
445
+ """Core function to get the type of the expression."""
446
+ match expr:
447
+
448
+ case uni.String() | uni.MultiString():
449
+ return self._convert_to_instance(self.get_type_of_string(expr))
450
+
451
+ case uni.Int():
452
+ return self._convert_to_instance(self.get_type_of_int(expr))
453
+
454
+ case uni.AtomTrailer():
455
+ # NOTE: Pyright is using CFG to figure out the member type by narrowing the base
456
+ # type and filtering the members. We're not doing that anytime sooner.
457
+ base_type = self.get_type_of_expression(expr.target)
458
+
459
+ if expr.is_attr: # <expr>.member
460
+ assert isinstance(expr.right, uni.Name)
461
+
462
+ if isinstance(base_type, types.ModuleType):
463
+ # getTypeOfMemberAccessWithBaseType()
464
+ if sym := base_type.symbol_table.lookup(
465
+ expr.right.value, deep=True
466
+ ):
467
+ expr.right.sym = sym
468
+ return self.get_type_of_symbol(sym)
469
+ return types.UnknownType()
470
+
471
+ elif base_type.is_instantiable_class():
472
+ assert isinstance(base_type, types.ClassType)
473
+ if member := self._lookup_class_member(
474
+ base_type, expr.right.value
475
+ ):
476
+ expr.right.sym = member.symbol
477
+ return self.get_type_of_symbol(member.symbol)
478
+ return types.UnknownType()
479
+
480
+ elif base_type.is_class_instance():
481
+ assert isinstance(base_type, types.ClassType)
482
+ if member := self._lookup_object_member(
483
+ base_type, expr.right.value
484
+ ):
485
+ expr.right.sym = member.symbol
486
+ return self.get_type_of_symbol(member.symbol)
487
+ return types.UnknownType()
488
+
489
+ elif expr.is_null_ok: # <expr>?.member
490
+ pass # TODO:
491
+
492
+ else: # <expr>[<expr>]
493
+ pass # TODO:
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
+
517
+ case uni.Name():
518
+ if symbol := expr.sym_tab.lookup(expr.value, deep=True):
519
+ expr.sym = symbol
520
+ return self.get_type_of_symbol(symbol)
521
+
522
+ # TODO: More expressions.
523
+ return types.UnknownType()
524
+
525
+ def _convert_to_instance(self, jtype: TypeBase) -> TypeBase:
526
+ """Convert a class type to an instance type."""
527
+ # TODO: Grep pyright "Handle type[x] as a special case." They handle `type[x]` as a special case:
528
+ #
529
+ # foo: int = 42; # <-- Here `int` is instantiable class and, become instance after this method.
530
+ # foo: type[int] = int # <-- Here `type[int]`, this should be `int` that's instantiable.
531
+ #
532
+ if jtype.is_instantiable_class():
533
+ assert isinstance(jtype, types.ClassType)
534
+ return jtype.clone_as_instance()
535
+ return jtype
536
+
537
+ def _lookup_class_member(
538
+ self, base_type: types.ClassType, member: str
539
+ ) -> ClassMember | None:
540
+ """Lookup the class member type."""
541
+ assert self.prefetch.int_class is not None
542
+ # FIXME: Pyright's way: Implement class member iterator (based on mro and the multiple inheritance)
543
+ # return the first found member from the iterator.
544
+
545
+ # NOTE: This is a simple implementation to make it work and more robust implementation will
546
+ # be done in a future PR.
547
+ if sym := base_type.lookup_member_symbol(member):
548
+ return ClassMember(sym, base_type)
549
+ return None
550
+
551
+ def _lookup_object_member(
552
+ self, base_type: types.ClassType, member: str
553
+ ) -> ClassMember | None:
554
+ """Lookup the object member type."""
555
+ assert self.prefetch.int_class is not None
556
+ if base_type.is_class_instance():
557
+ assert isinstance(base_type, types.ClassType)
558
+ # TODO: We need to implement Member lookup flags and set SkipInstanceMember to 0.
559
+ return self._lookup_class_member(base_type, member)
560
+ return None
@@ -0,0 +1,41 @@
1
+ """Functions that operate on Type objects.
2
+
3
+ PyrightReference: packages/pyright-internal/src/analyzer/typeUtils.ts
4
+ """
5
+
6
+ from jaclang.compiler.unitree import Symbol
7
+
8
+ from . import types
9
+
10
+
11
+ class ClassMember:
12
+ """Represents a member of a class."""
13
+
14
+ def __init__(self, symbol: Symbol, class_type: types.ClassType) -> None:
15
+ """Initialize obviously."""
16
+ self.symbol = symbol
17
+ self.class_type = class_type
18
+
19
+ # True if it is an instance or class member; it can be both a class and
20
+ # an instance member in cases where a class variable is overridden
21
+ # by an instance variable
22
+ self.is_instance_member = True
23
+ self.is_class_member = False
24
+
25
+ # Is the member in __slots__?
26
+ self.is_slots_member = False
27
+
28
+ # True if explicitly declared as "ClassVar" and therefore is
29
+ # a type violation if it is overwritten by an instance variable
30
+ self.is_class_var = False
31
+
32
+ # True if the member is read-only, such as with named tuples
33
+ # or frozen dataclasses.
34
+ self.is_read_only = False
35
+
36
+ # True if member has declared type, false if inferred
37
+ self.is_type_declared = False
38
+
39
+ # True if member lookup skipped an undeclared (inferred) type
40
+ # in a subclass before finding a declared type in a base class
41
+ self.skipped_undeclared_type = False