jaclang 0.8.8__py3-none-any.whl → 0.8.10__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 (114) hide show
  1. jaclang/cli/cli.py +194 -10
  2. jaclang/cli/cmdreg.py +144 -8
  3. jaclang/compiler/__init__.py +6 -1
  4. jaclang/compiler/codeinfo.py +16 -1
  5. jaclang/compiler/constant.py +33 -8
  6. jaclang/compiler/jac.lark +154 -62
  7. jaclang/compiler/larkparse/jac_parser.py +2 -2
  8. jaclang/compiler/parser.py +656 -149
  9. jaclang/compiler/passes/__init__.py +2 -1
  10. jaclang/compiler/passes/ast_gen/__init__.py +5 -0
  11. jaclang/compiler/passes/ast_gen/base_ast_gen_pass.py +54 -0
  12. jaclang/compiler/passes/ast_gen/jsx_processor.py +344 -0
  13. jaclang/compiler/passes/ecmascript/__init__.py +25 -0
  14. jaclang/compiler/passes/ecmascript/es_unparse.py +576 -0
  15. jaclang/compiler/passes/ecmascript/esast_gen_pass.py +2068 -0
  16. jaclang/compiler/passes/ecmascript/estree.py +972 -0
  17. jaclang/compiler/passes/ecmascript/tests/__init__.py +1 -0
  18. jaclang/compiler/passes/ecmascript/tests/fixtures/advanced_language_features.jac +170 -0
  19. jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.impl.jac +30 -0
  20. jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.jac +14 -0
  21. jaclang/compiler/passes/ecmascript/tests/fixtures/client_jsx.jac +89 -0
  22. jaclang/compiler/passes/ecmascript/tests/fixtures/core_language_features.jac +195 -0
  23. jaclang/compiler/passes/ecmascript/tests/test_esast_gen_pass.py +167 -0
  24. jaclang/compiler/passes/ecmascript/tests/test_js_generation.py +239 -0
  25. jaclang/compiler/passes/main/__init__.py +0 -3
  26. jaclang/compiler/passes/main/annex_pass.py +23 -1
  27. jaclang/compiler/passes/main/def_use_pass.py +1 -0
  28. jaclang/compiler/passes/main/pyast_gen_pass.py +413 -255
  29. jaclang/compiler/passes/main/pyast_load_pass.py +48 -11
  30. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +2 -0
  31. jaclang/compiler/passes/main/sym_tab_build_pass.py +18 -1
  32. jaclang/compiler/passes/main/tests/fixtures/autoimpl.cl.jac +7 -0
  33. jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +3 -0
  34. jaclang/compiler/passes/main/tests/fixtures/checker_class_construct.jac +33 -0
  35. jaclang/compiler/passes/main/tests/fixtures/defuse_modpath.jac +7 -0
  36. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +2 -1
  37. jaclang/compiler/passes/main/tests/test_checker_pass.py +31 -3
  38. jaclang/compiler/passes/main/tests/test_def_use_pass.py +12 -0
  39. jaclang/compiler/passes/main/tests/test_import_pass.py +23 -4
  40. jaclang/compiler/passes/main/tests/test_predynamo_pass.py +13 -14
  41. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +25 -0
  42. jaclang/compiler/passes/main/type_checker_pass.py +7 -0
  43. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +219 -20
  44. jaclang/compiler/passes/tool/fuse_comments_pass.py +1 -10
  45. jaclang/compiler/passes/tool/jac_formatter_pass.py +2 -2
  46. jaclang/compiler/passes/tool/tests/fixtures/import_fmt.jac +7 -1
  47. jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +135 -29
  48. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +4 -1
  49. jaclang/compiler/passes/transform.py +9 -1
  50. jaclang/compiler/passes/uni_pass.py +5 -7
  51. jaclang/compiler/program.py +27 -26
  52. jaclang/compiler/tests/test_client_codegen.py +113 -0
  53. jaclang/compiler/tests/test_importer.py +12 -10
  54. jaclang/compiler/tests/test_parser.py +249 -3
  55. jaclang/compiler/type_system/type_evaluator.jac +1078 -0
  56. jaclang/compiler/type_system/type_utils.py +1 -1
  57. jaclang/compiler/type_system/types.py +6 -0
  58. jaclang/compiler/unitree.py +438 -82
  59. jaclang/langserve/engine.jac +224 -288
  60. jaclang/langserve/sem_manager.jac +12 -8
  61. jaclang/langserve/server.jac +48 -48
  62. jaclang/langserve/tests/fixtures/greet.py +17 -0
  63. jaclang/langserve/tests/fixtures/md_path.jac +22 -0
  64. jaclang/langserve/tests/fixtures/user.jac +15 -0
  65. jaclang/langserve/tests/test_server.py +66 -371
  66. jaclang/lib.py +17 -0
  67. jaclang/runtimelib/archetype.py +25 -25
  68. jaclang/runtimelib/client_bundle.py +169 -0
  69. jaclang/runtimelib/client_runtime.jac +586 -0
  70. jaclang/runtimelib/constructs.py +4 -2
  71. jaclang/runtimelib/machine.py +308 -139
  72. jaclang/runtimelib/meta_importer.py +111 -22
  73. jaclang/runtimelib/mtp.py +15 -0
  74. jaclang/runtimelib/server.py +1089 -0
  75. jaclang/runtimelib/tests/fixtures/client_app.jac +18 -0
  76. jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
  77. jaclang/runtimelib/tests/fixtures/savable_object.jac +4 -5
  78. jaclang/runtimelib/tests/fixtures/serve_api.jac +75 -0
  79. jaclang/runtimelib/tests/test_client_bundle.py +55 -0
  80. jaclang/runtimelib/tests/test_client_render.py +63 -0
  81. jaclang/runtimelib/tests/test_serve.py +1069 -0
  82. jaclang/settings.py +0 -3
  83. jaclang/tests/fixtures/attr_pattern_case.jac +18 -0
  84. jaclang/tests/fixtures/funccall_genexpr.jac +7 -0
  85. jaclang/tests/fixtures/funccall_genexpr.py +5 -0
  86. jaclang/tests/fixtures/iife_functions.jac +142 -0
  87. jaclang/tests/fixtures/iife_functions_client.jac +143 -0
  88. jaclang/tests/fixtures/multistatement_lambda.jac +116 -0
  89. jaclang/tests/fixtures/multistatement_lambda_client.jac +113 -0
  90. jaclang/tests/fixtures/needs_import_dup.jac +6 -4
  91. jaclang/tests/fixtures/py2jac_empty.py +0 -0
  92. jaclang/tests/fixtures/py_run.py +7 -5
  93. jaclang/tests/fixtures/pyfunc_fstr.py +2 -2
  94. jaclang/tests/fixtures/simple_lambda_test.jac +12 -0
  95. jaclang/tests/test_cli.py +134 -18
  96. jaclang/tests/test_language.py +120 -32
  97. jaclang/tests/test_reference.py +20 -3
  98. jaclang/utils/NonGPT.py +375 -0
  99. jaclang/utils/helpers.py +64 -20
  100. jaclang/utils/lang_tools.py +31 -4
  101. jaclang/utils/tests/test_lang_tools.py +5 -16
  102. jaclang/utils/treeprinter.py +8 -3
  103. {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/METADATA +3 -3
  104. {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/RECORD +106 -71
  105. jaclang/compiler/passes/main/binder_pass.py +0 -594
  106. jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +0 -47
  107. jaclang/compiler/passes/main/tests/test_binder_pass.py +0 -111
  108. jaclang/compiler/type_system/type_evaluator.py +0 -844
  109. jaclang/langserve/tests/session.jac +0 -294
  110. jaclang/langserve/tests/test_dev_server.py +0 -80
  111. jaclang/runtimelib/importer.py +0 -351
  112. jaclang/tests/test_typecheck.py +0 -542
  113. {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/WHEEL +0 -0
  114. {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/entry_points.txt +0 -0
@@ -1,844 +0,0 @@
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
- import ast as py_ast
10
- import os
11
- from dataclasses import dataclass
12
- from pathlib import Path
13
- from typing import Callable, TYPE_CHECKING, cast
14
-
15
- import jaclang.compiler.unitree as uni
16
- from jaclang.compiler import TOKEN_MAP
17
- from jaclang.compiler.constant import Tokens as Tok
18
- from jaclang.compiler.passes.main.pyast_load_pass import PyastBuildPass
19
- from jaclang.compiler.passes.main.sym_tab_build_pass import SymTabBuildPass
20
- from jaclang.compiler.type_system import types
21
- from jaclang.runtimelib.utils import read_file_with_encoding
22
-
23
- if TYPE_CHECKING:
24
- from jaclang.compiler.program import JacProgram
25
-
26
- from . import operations
27
- from . import type_utils
28
- from .types import TypeBase
29
-
30
- # The callback type definition for the diagnostic messages.
31
- DiagnosticCallback = Callable[[uni.UniNode, str, bool], None]
32
-
33
-
34
- @dataclass
35
- class PrefetchedTypes:
36
- """Types whose definitions are prefetched and cached by the type evaluator."""
37
-
38
- none_type_class: TypeBase | None = None
39
- object_class: TypeBase | None = None
40
- type_class: TypeBase | None = None
41
- union_type_class: TypeBase | None = None
42
- awaitable_class: TypeBase | None = None
43
- function_class: TypeBase | None = None
44
- method_class: TypeBase | None = None
45
- tuple_class: TypeBase | None = None
46
- bool_class: TypeBase | None = None
47
- int_class: TypeBase | None = None
48
- float_class: TypeBase | None = None
49
- str_class: TypeBase | None = None
50
- dict_class: TypeBase | None = None
51
- module_type_class: TypeBase | None = None
52
- typed_dict_class: TypeBase | None = None
53
- typed_dict_private_class: TypeBase | None = None
54
- supports_keys_and_get_item_class: TypeBase | None = None
55
- mapping_class: TypeBase | None = None
56
- template_class: TypeBase | None = None
57
-
58
-
59
- @dataclass
60
- class SymbolResolutionStackEntry:
61
- """Represents a single entry in the symbol resolution stack."""
62
-
63
- symbol: uni.Symbol
64
-
65
- # Initially true, it's set to false if a recursion
66
- # is detected.
67
- is_result_valid: bool = True
68
-
69
- # Some limited forms of recursion are allowed. In these
70
- # cases, a partially-constructed type can be registered.
71
- partial_type: TypeBase | None = None
72
-
73
-
74
- @dataclass
75
- class MatchArgsToParamsResult:
76
- """Result of matching arguments to parameters."""
77
-
78
- # FIXME: This class implementation is modified from pyright to make it
79
- # simple and work for now, however this needs to be revisited and
80
- # implemented properly.
81
- arg_params: dict[uni.Expr | uni.KWPair, types.Parameter | None]
82
-
83
- overload: types.FunctionType | None = None
84
- argument_errors: bool = False
85
-
86
-
87
- class TypeEvaluator:
88
- """Type evaluator for JacLang."""
89
-
90
- # NOTE: This is done in the binder pass of pyright, however I'm doing this
91
- # here, cause this will be the entry point of the type checker and we're not
92
- # relying on the binder pass at the moment and we can go back to binder pass
93
- # in the future if we needed it.
94
- _BUILTINS_STUB_FILE_PATH = os.path.join(
95
- os.path.dirname(__file__),
96
- "../../vendor/typeshed/stdlib/builtins.pyi",
97
- )
98
-
99
- def __init__(
100
- self,
101
- program: "JacProgram",
102
- ) -> None:
103
- """Initialize the type evaluator with prefetched types.
104
-
105
- Implementation Note:
106
- --------------------
107
- Pyright is prefetching the builtins when an evaluation is requested
108
- on a node and from that node it does lookup for the builtins scope
109
- and does the prefetch once, however if we forgot to call prefetch
110
- in some place then it will not be available in the evaluator, So we
111
- are prefetching the builtins at the constructor level once.
112
- """
113
- self.program = program
114
- self.symbol_resolution_stack: list[SymbolResolutionStackEntry] = []
115
- self.builtins_module = self._load_builtins_stub_module()
116
- self.prefetch = self._prefetch_types()
117
- self.diagnostic_callback: DiagnosticCallback | None = None
118
-
119
- def _load_builtins_stub_module(self) -> uni.Module:
120
- """Load and return the builtins stub module."""
121
- if not os.path.exists(TypeEvaluator._BUILTINS_STUB_FILE_PATH):
122
- raise FileNotFoundError(
123
- f"Builtins stub file not found at {TypeEvaluator._BUILTINS_STUB_FILE_PATH}"
124
- )
125
- file_content = read_file_with_encoding(TypeEvaluator._BUILTINS_STUB_FILE_PATH)
126
- uni_source = uni.Source(file_content, TypeEvaluator._BUILTINS_STUB_FILE_PATH)
127
- mod = PyastBuildPass(
128
- ir_in=uni.PythonModuleAst(
129
- py_ast.parse(file_content),
130
- orig_src=uni_source,
131
- ),
132
- prog=self.program,
133
- ).ir_out
134
- SymTabBuildPass(ir_in=mod, prog=self.program)
135
- return mod
136
-
137
- def _get_builtin_type(self, name: str) -> TypeBase:
138
- """Return the built-in type with the given name."""
139
- if (symbol := self.builtins_module.lookup(name)) is not None:
140
- return self.get_type_of_symbol(symbol)
141
- return types.UnknownType()
142
-
143
- def _prefetch_types(self) -> "PrefetchedTypes":
144
- """Return the prefetched types for the type evaluator."""
145
- return PrefetchedTypes(
146
- # TODO: Pyright first try load NoneType from typeshed and if it cannot
147
- # then it set to unknown type.
148
- none_type_class=types.UnknownType(),
149
- object_class=self._get_builtin_type("object"),
150
- type_class=self._get_builtin_type("type"),
151
- # union_type_class=
152
- # awaitable_class=
153
- # function_class=
154
- # method_class=
155
- tuple_class=self._get_builtin_type("tuple"),
156
- bool_class=self._get_builtin_type("bool"),
157
- int_class=self._get_builtin_type("int"),
158
- float_class=self._get_builtin_type("float"),
159
- str_class=self._get_builtin_type("str"),
160
- dict_class=self._get_builtin_type("dict"),
161
- # module_type_class=
162
- # typed_dict_class=
163
- # typed_dict_private_class=
164
- # supports_keys_and_get_item_class=
165
- # mapping_class=
166
- # template_class=
167
- )
168
-
169
- def add_diagnostic(
170
- self, node: uni.UniNode, message: str, warning: bool = False
171
- ) -> None:
172
- """Add a diagnostic message to the program."""
173
- if self.diagnostic_callback:
174
- self.diagnostic_callback(node, message, warning)
175
-
176
- # -------------------------------------------------------------------------
177
- # Symbol resolution stack
178
- # -------------------------------------------------------------------------
179
-
180
- def get_index_of_symbol_resolution(self, symbol: uni.Symbol) -> int | None:
181
- """Get the index of a symbol in the resolution stack."""
182
- for i, entry in enumerate(self.symbol_resolution_stack):
183
- if entry.symbol == symbol:
184
- return i
185
- return None
186
-
187
- def push_symbol_resolution(self, symbol: uni.Symbol) -> bool:
188
- """
189
- Push a symbol onto the resolution stack.
190
-
191
- Return False if recursion detected and in that case it won't push the symbol.
192
- """
193
- idx = self.get_index_of_symbol_resolution(symbol)
194
- if idx is not None:
195
- # Mark all of the entries between these two as invalid.
196
- for i in range(idx, len(self.symbol_resolution_stack)):
197
- entry = self.symbol_resolution_stack[i]
198
- entry.is_result_valid = False
199
- return False
200
- self.symbol_resolution_stack.append(SymbolResolutionStackEntry(symbol=symbol))
201
- return True
202
-
203
- def pop_symbol_resolution(self, symbol: uni.Symbol) -> bool:
204
- """Pop a symbol from the resolution stack."""
205
- popped_entry = self.symbol_resolution_stack.pop()
206
- assert popped_entry.symbol == symbol
207
- return popped_entry.is_result_valid
208
-
209
- # Pyright equivalent function name = getEffectiveTypeOfSymbol.
210
- def get_type_of_symbol(self, symbol: uni.Symbol) -> TypeBase:
211
- """Return the effective type of the symbol."""
212
- if self.push_symbol_resolution(symbol):
213
- try:
214
- return self._get_type_of_symbol(symbol)
215
- finally:
216
- self.pop_symbol_resolution(symbol)
217
-
218
- # If we reached here that means we have a cyclic symbolic reference.
219
- return types.UnknownType()
220
-
221
- # NOTE: This function doesn't exists in pyright, however it exists as a helper function
222
- # for the following functions.
223
- def _import_module_from_path(self, path: str) -> uni.Module:
224
- """Import a module from the given path."""
225
- # Get the module, if it's not loaded yet, compile and get it.
226
- #
227
- # TODO:
228
- # We're not typechecking inside the module itself however we
229
- # need to check if the module path is site-package or not and
230
- # do typecheck inside as well.
231
- mod: uni.Module
232
- if path in self.program.mod.hub:
233
- mod = self.program.mod.hub[path]
234
- else:
235
- mod = self.program.compile(path, no_cgen=True, type_check=False)
236
- # FIXME: Inherit from builtin symbol table logic is currently implemented
237
- # here and checker pass, however it should be in one location, since we're
238
- # doing type_check=False, it doesn't set parent_scope to builtins, this
239
- # needs to be done properly. The way jaclang handles symbol table is different
240
- # than pyright, so we cannot strictly follow them, however as long as a new
241
- # module has a parent scope as builtin scope, we're aligned with pyright.
242
- if mod.parent_scope is None and mod is not self.builtins_module:
243
- mod.parent_scope = self.builtins_module
244
- return mod
245
-
246
- def get_type_of_module(self, node: uni.ModulePath) -> types.TypeBase:
247
- """Return the effective type of the module."""
248
- if node.name_spec.type is not None:
249
- return cast(types.ModuleType, node.name_spec.type)
250
- if not Path(node.resolve_relative_path()).exists():
251
- node.name_spec.type = types.UnknownType()
252
- return node.name_spec.type
253
-
254
- mod: uni.Module = self._import_module_from_path(node.resolve_relative_path())
255
- mod_type = types.ModuleType(
256
- mod_name=node.name_spec.sym_name,
257
- file_uri=Path(node.resolve_relative_path()).resolve(),
258
- symbol_table=mod,
259
- )
260
-
261
- node.name_spec.type = mod_type
262
- return mod_type
263
-
264
- def get_type_of_module_item(self, node: uni.ModuleItem) -> types.TypeBase:
265
- """Return the effective type of the module item."""
266
- # Module item can be both a module or a member of a module.
267
- # import from .. { mod } # <-- Here mod is not a member but a module itself.
268
- # import from mod { item } # <-- Here item is not a module but a member of mod.
269
- if node.name_spec.type is not None:
270
- return node.name_spec.type
271
-
272
- import_node = node.parent_of_type(uni.Import)
273
- if import_node.from_loc:
274
-
275
- from_path = Path(import_node.from_loc.resolve_relative_path())
276
- is_dir = from_path.is_dir() or (from_path.stem == "__init__")
277
-
278
- # import from .. { mod }
279
- if is_dir:
280
- mod_dir = from_path.parent if not from_path.is_dir() else from_path
281
- # FIXME: Implement module resolution properly.
282
- for ext in (".jac", ".py", ".pyi"):
283
- if (path := (mod_dir / (node.name.value + ext)).resolve()).exists():
284
- mod = self._import_module_from_path(str(path))
285
- mod_type = types.ModuleType(
286
- mod_name=node.name_spec.sym_name,
287
- file_uri=path,
288
- symbol_table=mod,
289
- )
290
- # Cache the type.
291
- node.name_spec.type = mod_type
292
-
293
- # FIXME: goto definition works on imported symbol by checking if it's a MODULE
294
- # type and in that case it'll call resolve_relative_path on the parent node of
295
- # the symbol's definition node (a module path), So the goto definition to work
296
- # properly the category should be module on a module path, If we set like this
297
- # below should work but because of the above assumption (should be a mod path)
298
- # it won't, This needs to be discussed.
299
- #
300
- # node.name_spec._sym_category = uni.SymbolType.MODULE
301
- return node.name_spec.type
302
-
303
- # import from mod { item }
304
- else:
305
- mod_type = self.get_type_of_module(import_node.from_loc)
306
- if not isinstance(mod_type, types.ModuleType):
307
- node.name_spec.type = types.UnknownType()
308
- # TODO: Add diagnostic that from_loc is not accessible.
309
- # Eg: 'Import "scipy" could not be resolved'
310
- return node.name_spec.type
311
- if sym := mod_type.symbol_table.lookup(node.name.value, deep=True):
312
- node.name.sym = sym
313
- if node.alias:
314
- node.alias.sym = sym
315
- node.name_spec.type = self.get_type_of_symbol(sym)
316
- return node.name_spec.type
317
-
318
- return types.UnknownType()
319
-
320
- def get_type_of_class(self, node: uni.Archetype) -> types.ClassType:
321
- """Return the effective type of the class."""
322
- # Is this type already cached?
323
- if node.name_spec.type is not None:
324
- return cast(types.ClassType, node.name_spec.type)
325
-
326
- base_classes: list[TypeBase] = []
327
- for base_class in node.base_classes or []:
328
- base_class_type = self.get_type_of_expression(base_class)
329
- base_classes.append(base_class_type)
330
- is_builtin_class = node.find_parent_of_type(uni.Module) == self.builtins_module
331
-
332
- cls_type = types.ClassType(
333
- types.ClassType.ClassDetailsShared(
334
- class_name=node.name_spec.sym_name,
335
- symbol_table=node,
336
- base_classes=base_classes,
337
- is_builtin_class=is_builtin_class,
338
- ),
339
- flags=types.TypeFlags.Instantiable,
340
- )
341
-
342
- # Compute the MRO for the class.
343
- type_utils.compute_mro_linearization(cls_type)
344
-
345
- # Cache the type, pyright is doing invalidateTypeCacheIfCanceled()
346
- # we're not doing that any time sooner.
347
- node.name_spec.type = cls_type
348
- return cls_type
349
-
350
- def get_type_of_ability(self, node: uni.Ability) -> TypeBase:
351
- """Return the effective type of an ability."""
352
- if node.name_spec.type is not None:
353
- return node.name_spec.type
354
-
355
- if not isinstance(node.signature, uni.FuncSignature):
356
- node.name_spec.type = types.UnknownType()
357
- return node.name_spec.type
358
-
359
- return_type: TypeBase
360
- if isinstance(node.signature.return_type, uni.Expr):
361
- return_type = self._convert_to_instance(
362
- self.get_type_of_expression(node.signature.return_type)
363
- )
364
- else:
365
- return_type = types.UnknownType()
366
-
367
- # Define helper function for parameter conversion.
368
- def _get_param_category(param: uni.ParamVar) -> types.ParameterCategory:
369
- if param.is_vararg:
370
- return types.ParameterCategory.ArgsList
371
- if param.is_kwargs:
372
- return types.ParameterCategory.KwargsDict
373
- return types.ParameterCategory.Positional
374
-
375
- # Define helper function for parameter kind conversion.
376
- def _convert_param_kind(kind: uni.ParamKind) -> types.ParamKind:
377
- match kind:
378
- case uni.ParamKind.POSONLY:
379
- return types.ParamKind.POSONLY
380
- case uni.ParamKind.NORMAL:
381
- return types.ParamKind.NORMAL
382
- case uni.ParamKind.VARARG:
383
- return types.ParamKind.VARARG
384
- case uni.ParamKind.KWONLY:
385
- return types.ParamKind.KWONLY
386
- case uni.ParamKind.KWARG:
387
- return types.ParamKind.KWARG
388
- return types.ParamKind.NORMAL
389
-
390
- parameters: list[types.Parameter] = []
391
- for idx, param in enumerate(node.signature.get_parameters()):
392
- # TODO: Set parameter category for *args, and **kwargs
393
- param_type: TypeBase | None = None
394
-
395
- if param.type_tag:
396
- param_type_cls = self.get_type_of_expression(param.type_tag.tag)
397
- param_type = self._convert_to_instance(param_type_cls)
398
-
399
- parameters.append(
400
- types.Parameter(
401
- name=param.name.value,
402
- category=_get_param_category(param),
403
- param_type=param_type,
404
- default_value=param.value,
405
- is_self=(idx == 0 and self._is_expr_self(param.name)),
406
- param_kind=_convert_param_kind(param.param_kind),
407
- )
408
- )
409
-
410
- func_type = types.FunctionType(
411
- func_name=node.name_spec.sym_name,
412
- return_type=return_type,
413
- parameters=parameters,
414
- )
415
-
416
- node.name_spec.type = func_type
417
- return func_type
418
-
419
- def get_type_of_string(self, node: uni.String | uni.MultiString) -> TypeBase:
420
- """Return the effective type of the string."""
421
- # FIXME: Strings are a type of LiteralString type:
422
- # "foo" is not `str` but Literal["foo"], however for now we'll
423
- # not considering that and make it work and will implement that
424
- # later.
425
- #
426
- # see: getTypeOfString() in pyright (it requires parsing the sub
427
- # file of the typing module).
428
- assert self.prefetch.str_class is not None
429
- return self.prefetch.str_class
430
-
431
- def get_type_of_int(self, node: uni.Int) -> TypeBase:
432
- """Return the effective type of the int."""
433
- assert self.prefetch.int_class is not None
434
- return self.prefetch.int_class
435
-
436
- def get_type_of_float(self, node: uni.Float) -> TypeBase:
437
- """Return the effective type of the float."""
438
- assert self.prefetch.float_class is not None
439
- return self.prefetch.float_class
440
-
441
- # Pyright equivalent function name = getTypeOfExpression();
442
- def get_type_of_expression(self, node: uni.Expr) -> TypeBase:
443
- """Return the effective type of the expression."""
444
- # If it's alreay "cached" return it.
445
- if node.type is not None:
446
- return node.type
447
-
448
- result = self._get_type_of_expression_core(node)
449
- # If the context has an expected type, pyright does a compatibility and set
450
- # a diagnostics here, I don't understand why that might be necessary here.
451
-
452
- node.type = result # Cache the result
453
- return result
454
-
455
- # Comments from pyright:
456
- # // Determines if the source type can be assigned to the dest type.
457
- # // If constraint are provided, type variables within the destType are
458
- # // matched against existing type variables in the map. If a type variable
459
- # // in the dest type is not in the type map already, it is assigned a type
460
- # // and added to the map.
461
- def assign_type(self, src_type: TypeBase, dest_type: TypeBase) -> bool:
462
- """Assign the source type to the destination type."""
463
- if types.TypeCategory.Unknown in (src_type.category, dest_type.category):
464
- # NOTE: For now if we don't have the type info, we assume it's compatible.
465
- # For strict mode we should disallow usage of unknown unless explicitly ignored.
466
- return True
467
-
468
- if src_type == dest_type:
469
- return True
470
-
471
- if dest_type.is_class_instance() and src_type.is_class_instance():
472
- assert isinstance(dest_type, types.ClassType)
473
- assert isinstance(src_type, types.ClassType)
474
- return self._assign_class(src_type, dest_type)
475
-
476
- return False
477
-
478
- # TODO: This should take an argument list as parameter.
479
- def get_type_of_magic_method_call(
480
- self, obj_type: TypeBase, method_name: str
481
- ) -> TypeBase | None:
482
- """Return the effective return type of a magic method call."""
483
- if obj_type.category == types.TypeCategory.Class:
484
- # TODO: getTypeOfBoundMember() <-- Implement this if needed, for the simple case
485
- # we'll directly call member lookup.
486
- #
487
- # WE'RE DAVIATING FROM PYRIGHT FOR THIS METHOD HEAVILY HOWEVER THIS CAN BE RE-WRITTEN IF NEEDED.
488
- #
489
- assert isinstance(obj_type, types.ClassType) # <-- To make typecheck happy.
490
- if member := self._lookup_class_member(obj_type, method_name):
491
- member_ty = self.get_type_of_symbol(member.symbol)
492
- if isinstance(member_ty, types.FunctionType):
493
- return member_ty.return_type
494
- # If we reached here, magic method is not a function.
495
- # 1. recursively check __call__() on the type, TODO
496
- # 2. if any or unknown, return getUnknownTypeForCallable() TODO
497
- # 3. return undefined.
498
- return None
499
- return None
500
-
501
- def _assign_class(
502
- self, src_type: types.ClassType, dest_type: types.ClassType
503
- ) -> bool:
504
- """Assign the source class type to the destination class type."""
505
- if src_type.shared == dest_type.shared:
506
- return True
507
-
508
- # Check if src class is a subclass of dest class.
509
- for base_cls in src_type.shared.mro:
510
- if base_cls.shared == dest_type.shared:
511
- return True
512
-
513
- # Everything is assignable to an object.
514
- if dest_type.is_builtin("object"):
515
- # TODO: Invariance not handled yet
516
- # invariant contexts to avoid list[int] <: list[object] errors.
517
- return True
518
-
519
- # Integers can be used where floats are expected.
520
- if src_type.is_builtin("int") and dest_type.is_builtin("float"):
521
- return True
522
-
523
- # TODO: Search base classes and everything else pyright is doing.
524
- return False
525
-
526
- # This function is a combination of the bellow pyright functions.
527
- # - getDeclaredTypeOfSymbol
528
- # - getTypeForDeclaration
529
- #
530
- # Implementation Note:
531
- # Pyright is actually have some duplicate logic for handling declared
532
- # type and inferred type, we're going to unify them (if it's required
533
- # in the future, we can refactor this).
534
- def _get_type_of_symbol(self, symbol: uni.Symbol) -> TypeBase:
535
- """Return the declared type of the symbol."""
536
- node = symbol.decl.name_of
537
- match node:
538
- case uni.ModulePath():
539
- return self.get_type_of_module(node)
540
-
541
- case uni.ModuleItem():
542
- return self.get_type_of_module_item(node)
543
-
544
- case uni.Archetype():
545
- return self.get_type_of_class(node)
546
-
547
- case uni.Ability():
548
- return self.get_type_of_ability(node)
549
-
550
- case uni.ParamVar():
551
- if node.type_tag:
552
- annotation_type = self.get_type_of_expression(node.type_tag.tag)
553
- return self._convert_to_instance(annotation_type)
554
-
555
- # This actually defined in the function getTypeForDeclaration();
556
- # Pyright has DeclarationType.Variable.
557
- case uni.Name():
558
- if isinstance(node.parent, uni.Assignment):
559
- if node.parent.type_tag is not None:
560
- annotation_type = self.get_type_of_expression(
561
- node.parent.type_tag.tag
562
- )
563
- return self._convert_to_instance(annotation_type)
564
-
565
- else: # Assignment without a type annotation.
566
- if node.parent.value is not None:
567
- return self.get_type_of_expression(node.parent.value)
568
-
569
- case uni.HasVar():
570
- if node.type_tag is not None:
571
- annotation_type = self.get_type_of_expression(node.type_tag.tag)
572
- return self._convert_to_instance(annotation_type)
573
- else:
574
- if node.value is not None:
575
- return self.get_type_of_expression(node.value)
576
-
577
- # TODO: Implement for functions, parameters, explicit type
578
- # annotations in assignment etc.
579
- return types.UnknownType()
580
-
581
- # Pyright equivalent function name = getTypeOfExpressionCore();
582
- def _get_type_of_expression_core(self, expr: uni.Expr) -> TypeBase:
583
- """Core function to get the type of the expression."""
584
- match expr:
585
-
586
- case uni.String() | uni.MultiString():
587
- return self._convert_to_instance(self.get_type_of_string(expr))
588
-
589
- case uni.Int():
590
- return self._convert_to_instance(self.get_type_of_int(expr))
591
-
592
- case uni.Float():
593
- return self._convert_to_instance(self.get_type_of_float(expr))
594
-
595
- case uni.AtomTrailer():
596
- # NOTE: Pyright is using CFG to figure out the member type by narrowing the base
597
- # type and filtering the members. We're not doing that anytime sooner.
598
- base_type = self.get_type_of_expression(expr.target)
599
-
600
- if expr.is_attr: # <expr>.member
601
- assert isinstance(expr.right, uni.Name)
602
-
603
- if isinstance(base_type, types.ModuleType):
604
- # getTypeOfMemberAccessWithBaseType()
605
- if sym := base_type.symbol_table.lookup(
606
- expr.right.value, deep=True
607
- ):
608
- expr.right.sym = sym
609
- return self.get_type_of_symbol(sym)
610
- return types.UnknownType()
611
-
612
- elif base_type.is_instantiable_class():
613
- assert isinstance(base_type, types.ClassType)
614
- if member := self._lookup_class_member(
615
- base_type, expr.right.value
616
- ):
617
- expr.right.sym = member.symbol
618
- return self.get_type_of_symbol(member.symbol)
619
- return types.UnknownType()
620
-
621
- elif base_type.is_class_instance():
622
- assert isinstance(base_type, types.ClassType)
623
- if member := self._lookup_object_member(
624
- base_type, expr.right.value
625
- ):
626
- expr.right.sym = member.symbol
627
- return self.get_type_of_symbol(member.symbol)
628
- return types.UnknownType()
629
-
630
- elif expr.is_null_ok: # <expr>?.member
631
- pass # TODO:
632
-
633
- else: # <expr>[<expr>]
634
- pass # TODO:
635
-
636
- case uni.AtomUnit():
637
- return self.get_type_of_expression(expr.value)
638
-
639
- case uni.FuncCall():
640
- return self.validate_call_args(expr)
641
-
642
- case uni.BinaryExpr():
643
- return operations.get_type_of_binary_operation(self, expr)
644
-
645
- case uni.Name():
646
- # NOTE: For self's type pyright is getting the first parameter of a method and
647
- # the name can be anything not just self, however we don't have the first parameter
648
- # and self is a keyword, we need to do it in this way.
649
- if self._is_expr_self(expr):
650
- return self._get_type_of_self(expr)
651
-
652
- if symbol := expr.sym_tab.lookup(expr.value, deep=True):
653
- expr.sym = symbol
654
- return self.get_type_of_symbol(symbol)
655
-
656
- # TODO: More expressions.
657
- return types.UnknownType()
658
-
659
- # -----------------------------------------------------------------------------
660
- # Helper functions
661
- # -----------------------------------------------------------------------------
662
-
663
- def _is_expr_self(self, expr: uni.Expr) -> bool:
664
- """Check if the expression is Name that is 'self' and in the method context."""
665
- if (
666
- isinstance(expr, uni.Name)
667
- and (expr.value == TOKEN_MAP[Tok.KW_SELF])
668
- and (fn := self._get_enclosing_method(expr))
669
- and (not fn.is_static)
670
- ):
671
- return True
672
- return False
673
-
674
- def _get_enclosing_function(self, node: uni.UniNode) -> uni.Ability | None:
675
- """Get the enclosing function (ability) of the given node."""
676
- if (impl := node.find_parent_of_type(uni.ImplDef)) and (
677
- isinstance(impl.decl_link, uni.Ability)
678
- ):
679
- return impl.decl_link
680
- return node.find_parent_of_type(uni.Ability)
681
-
682
- def _get_enclosing_method(self, node: uni.UniNode) -> uni.Ability | None:
683
- """Get the enclosing method (ability) of the given node."""
684
- enclosing_fn = self._get_enclosing_function(node)
685
- while enclosing_fn and (not enclosing_fn.is_method):
686
- enclosing_fn = self._get_enclosing_function(enclosing_fn)
687
- if enclosing_fn and enclosing_fn.is_method:
688
- return enclosing_fn
689
- return None
690
-
691
- def _get_type_of_self(self, node: uni.Name) -> TypeBase:
692
- """Return the effective type of self."""
693
- if method := self._get_enclosing_method(node):
694
- cls = method.method_owner
695
- if isinstance(cls, uni.Archetype):
696
- return self.get_type_of_class(cls).clone_as_instance()
697
- if isinstance(cls, uni.Enum):
698
- pass # TODO: Implement type from enum.
699
- return types.UnknownType()
700
-
701
- def _convert_to_instance(self, jtype: TypeBase) -> TypeBase:
702
- """Convert a class type to an instance type."""
703
- # TODO: Grep pyright "Handle type[x] as a special case." They handle `type[x]` as a special case:
704
- #
705
- # foo: int = 42; # <-- Here `int` is instantiable class and, become instance after this method.
706
- # foo: type[int] = int # <-- Here `type[int]`, this should be `int` that's instantiable.
707
- #
708
- if jtype.is_instantiable_class():
709
- assert isinstance(jtype, types.ClassType)
710
- return jtype.clone_as_instance()
711
- return jtype
712
-
713
- def _lookup_class_member(
714
- self, base_type: types.ClassType, member: str
715
- ) -> type_utils.ClassMember | None:
716
- """Lookup the class member type."""
717
- assert self.prefetch.int_class is not None
718
- # FIXME: Pyright's way: Implement class member iterator (based on mro and the multiple inheritance)
719
- # return the first found member from the iterator.
720
-
721
- # NOTE: This is a simple implementation to make it work and more robust implementation will
722
- # be done in a future PR.
723
- for cls in base_type.shared.mro:
724
- if sym := cls.lookup_member_symbol(member):
725
- return type_utils.ClassMember(sym, cls)
726
- return None
727
-
728
- def _lookup_object_member(
729
- self, base_type: types.ClassType, member: str
730
- ) -> type_utils.ClassMember | None:
731
- """Lookup the object member type."""
732
- assert self.prefetch.int_class is not None
733
- if base_type.is_class_instance():
734
- assert isinstance(base_type, types.ClassType)
735
- # TODO: We need to implement Member lookup flags and set SkipInstanceMember to 0.
736
- return self._lookup_class_member(base_type, member)
737
- return None
738
-
739
- def match_args_to_params(
740
- self, expr: uni.FuncCall, func_type: types.FunctionType
741
- ) -> MatchArgsToParamsResult:
742
- """
743
- Match arguments passed to a function to the corresponding parameters in that function.
744
-
745
- This matching is done based on positions and keywords. Type evaluation and
746
- validation is left to the caller.
747
- This logic is based on PEP 3102: https://www.python.org/dev/peps/pep-3102/
748
- """
749
- arg_params: dict[uni.Expr | uni.KWPair, types.Parameter | None] = {}
750
- argument_errors = False
751
-
752
- params_to_match = func_type.parameters.copy()
753
-
754
- # Skip `self` for method calls.
755
- if len(func_type.parameters) >= 1 and func_type.parameters[0].is_self:
756
- params_to_match.pop(0)
757
-
758
- # Create a tracker for parameter assignment.
759
- param_tracker = type_utils.ParamAssignmentTracker(params_to_match)
760
-
761
- # We iterate over the arguments and match with the parameter, the param_tracker will
762
- # keep track of the matched parameters and unmatched required parameters.
763
- #
764
- # Tracker: p1, p2, /, p3, p4, *args, | p6, **kwargs
765
- # ^ ^ ^ ^ ^^ ^ | ^ ^^
766
- # | | | | | \__ \__ | | | \________
767
- # Args: a1, a2, a3, p4=a4, a5, a6, *a7, | p6=a8, p_kw1=a9, p_kw2=a10
768
- # '--------------------------------' | '------------------------'
769
- # We match positional with | We match named arguments with
770
- # tracked parameter index. | param name lookup.
771
- #
772
- for arg in expr.params:
773
- try:
774
- if isinstance(arg, uni.KWPair):
775
- # Match parameter based on name lookup.
776
- matching_param = param_tracker.match_named_argument(arg)
777
- arg_params[arg] = matching_param
778
- else: # Match parameter based on the position of the argument.
779
- matching_param = param_tracker.match_positional_argument(arg)
780
- arg_params[arg] = matching_param
781
- except Exception as e:
782
- self.add_diagnostic(arg, str(e))
783
- argument_errors = True
784
-
785
- if unmatched_params := param_tracker.get_unmatched_required_params():
786
- names = ", ".join(f"'{p.name}'" for p in unmatched_params)
787
- argument_errors = True
788
- self.add_diagnostic(
789
- expr,
790
- f"Not all required parameters were provided in the function call: {names}",
791
- )
792
-
793
- return MatchArgsToParamsResult(
794
- arg_params=arg_params, argument_errors=argument_errors
795
- )
796
-
797
- def validate_call_args(self, expr: uni.FuncCall) -> TypeBase:
798
- """
799
- Validate that the arguments can be assigned to the call's parameter list.
800
-
801
- Specializes the call based on arg types, and returns the specialized
802
- type of the return value. If it detects an error along the way, it emits
803
- a diagnostic and sets argumentErrors to true.
804
- """
805
- caller_type = self.get_type_of_expression(expr.target)
806
- if isinstance(caller_type, types.FunctionType):
807
- arg_param_match = self.match_args_to_params(expr, caller_type)
808
- if not arg_param_match.argument_errors:
809
- self.validate_arg_types(arg_param_match)
810
- return caller_type.return_type or types.UnknownType()
811
-
812
- if (
813
- isinstance(caller_type, types.ClassType)
814
- and caller_type.is_instantiable_class()
815
- ):
816
- # TODO: validate args for __init__()
817
- return caller_type.clone_as_instance()
818
-
819
- if caller_type.is_class_instance():
820
- # TODO: validate args.
821
- magic_call_ret = self.get_type_of_magic_method_call(caller_type, "__call__")
822
- if magic_call_ret:
823
- return magic_call_ret
824
-
825
- return types.UnknownType()
826
-
827
- def validate_arg_types(
828
- self,
829
- args: MatchArgsToParamsResult,
830
- ) -> None:
831
- """Validate that the argument types can be assigned to the parameter types."""
832
- for arg, param in args.arg_params.items():
833
- if param is None or param.param_type is None:
834
- continue
835
- if isinstance(arg, uni.KWPair):
836
- arg_type = self.get_type_of_expression(arg.value)
837
- else:
838
- arg_type = self.get_type_of_expression(arg)
839
-
840
- if not self.assign_type(arg_type, param.param_type):
841
- self.add_diagnostic(
842
- arg,
843
- f"Cannot assign {arg_type} to parameter '{param.name}' of type {param.param_type}",
844
- )