jaclang 0.8.7__py3-none-any.whl → 0.8.8__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.py +13 -27
- jaclang/cli/cmdreg.py +44 -0
- jaclang/compiler/constant.py +0 -1
- jaclang/compiler/jac.lark +3 -6
- jaclang/compiler/larkparse/jac_parser.py +2 -2
- jaclang/compiler/parser.py +213 -34
- jaclang/compiler/passes/main/__init__.py +2 -4
- jaclang/compiler/passes/main/def_use_pass.py +0 -4
- jaclang/compiler/passes/main/predynamo_pass.py +221 -0
- jaclang/compiler/passes/main/pyast_gen_pass.py +70 -52
- jaclang/compiler/passes/main/pyast_load_pass.py +52 -20
- jaclang/compiler/passes/main/sym_tab_build_pass.py +1 -1
- jaclang/compiler/passes/main/tests/fixtures/checker/import_sym.jac +2 -0
- jaclang/compiler/passes/main/tests/fixtures/checker/import_sym_test.jac +6 -0
- jaclang/compiler/passes/main/tests/fixtures/checker/imported_sym.jac +5 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_arg_param_match.jac +37 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +18 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_cat_is_animal.jac +18 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_float.jac +7 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_param_types.jac +11 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_self_type.jac +9 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_sym_inherit.jac +42 -0
- jaclang/compiler/passes/main/tests/fixtures/predynamo_fix3.jac +43 -0
- jaclang/compiler/passes/main/tests/fixtures/predynamo_where_assign.jac +13 -0
- jaclang/compiler/passes/main/tests/fixtures/predynamo_where_return.jac +11 -0
- jaclang/compiler/passes/main/tests/test_checker_pass.py +191 -0
- jaclang/compiler/passes/main/tests/test_predynamo_pass.py +57 -0
- jaclang/compiler/passes/main/type_checker_pass.py +29 -73
- jaclang/compiler/passes/tool/doc_ir_gen_pass.py +204 -44
- jaclang/compiler/passes/tool/jac_formatter_pass.py +119 -69
- jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +3 -3
- jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +4 -5
- jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +171 -11
- jaclang/compiler/passes/transform.py +12 -8
- jaclang/compiler/program.py +14 -6
- jaclang/compiler/tests/fixtures/jac_import_py_files.py +4 -0
- jaclang/compiler/tests/fixtures/jac_module.jac +3 -0
- jaclang/compiler/tests/fixtures/multiple_syntax_errors.jac +10 -0
- jaclang/compiler/tests/fixtures/python_module.py +1 -0
- jaclang/compiler/tests/test_importer.py +39 -0
- jaclang/compiler/tests/test_parser.py +49 -0
- jaclang/compiler/type_system/type_evaluator.py +351 -67
- jaclang/compiler/type_system/type_utils.py +246 -0
- jaclang/compiler/type_system/types.py +58 -2
- jaclang/compiler/unitree.py +79 -94
- jaclang/langserve/engine.jac +138 -159
- jaclang/langserve/server.jac +25 -1
- jaclang/langserve/tests/fixtures/circle.jac +3 -3
- jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
- jaclang/langserve/tests/fixtures/circle_pure.test.jac +3 -3
- jaclang/langserve/tests/fixtures/completion_test_err.jac +10 -0
- jaclang/langserve/tests/server_test/circle_template.jac +80 -0
- jaclang/langserve/tests/server_test/glob_template.jac +4 -0
- jaclang/langserve/tests/server_test/test_lang_serve.py +154 -309
- jaclang/langserve/tests/server_test/utils.py +153 -116
- jaclang/langserve/tests/test_server.py +21 -84
- jaclang/langserve/utils.jac +12 -15
- jaclang/runtimelib/machine.py +7 -0
- jaclang/runtimelib/meta_importer.py +27 -1
- jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
- jaclang/runtimelib/tests/fixtures/savable_object.jac +2 -2
- jaclang/settings.py +18 -14
- jaclang/tests/fixtures/abc_check.jac +3 -3
- jaclang/tests/fixtures/arch_rel_import_creation.jac +12 -12
- jaclang/tests/fixtures/chandra_bugs2.jac +3 -3
- jaclang/tests/fixtures/create_dynamic_archetype.jac +13 -13
- jaclang/tests/fixtures/maxfail_run_test.jac +4 -4
- jaclang/tests/fixtures/params/param_syntax_err.jac +9 -0
- jaclang/tests/fixtures/params/test_complex_params.jac +42 -0
- jaclang/tests/fixtures/params/test_failing_kwonly.jac +207 -0
- jaclang/tests/fixtures/params/test_failing_posonly.jac +116 -0
- jaclang/tests/fixtures/params/test_failing_varargs.jac +300 -0
- jaclang/tests/fixtures/params/test_kwonly_params.jac +29 -0
- jaclang/tests/fixtures/py2jac_params.py +8 -0
- jaclang/tests/fixtures/run_test.jac +4 -4
- jaclang/tests/test_cli.py +37 -1
- jaclang/tests/test_language.py +74 -16
- jaclang/utils/helpers.py +47 -2
- jaclang/utils/module_resolver.py +10 -0
- jaclang/utils/test.py +8 -0
- jaclang/utils/treeprinter.py +0 -18
- {jaclang-0.8.7.dist-info → jaclang-0.8.8.dist-info}/METADATA +1 -2
- {jaclang-0.8.7.dist-info → jaclang-0.8.8.dist-info}/RECORD +85 -60
- {jaclang-0.8.7.dist-info → jaclang-0.8.8.dist-info}/WHEEL +1 -1
- jaclang/compiler/passes/main/inheritance_pass.py +0 -131
- jaclang/langserve/dev_engine.jac +0 -645
- jaclang/langserve/dev_server.jac +0 -201
- jaclang/langserve/tests/server_test/code_test.py +0 -0
- {jaclang-0.8.7.dist-info → jaclang-0.8.8.dist-info}/entry_points.txt +0 -0
|
@@ -6,20 +6,30 @@ PyrightReference:
|
|
|
6
6
|
packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import ast as py_ast
|
|
10
|
+
import os
|
|
9
11
|
from dataclasses import dataclass
|
|
10
12
|
from pathlib import Path
|
|
11
|
-
from typing import TYPE_CHECKING, cast
|
|
13
|
+
from typing import Callable, TYPE_CHECKING, cast
|
|
12
14
|
|
|
13
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
|
|
14
20
|
from jaclang.compiler.type_system import types
|
|
21
|
+
from jaclang.runtimelib.utils import read_file_with_encoding
|
|
15
22
|
|
|
16
23
|
if TYPE_CHECKING:
|
|
17
24
|
from jaclang.compiler.program import JacProgram
|
|
18
25
|
|
|
19
26
|
from . import operations
|
|
20
|
-
from .
|
|
27
|
+
from . import type_utils
|
|
21
28
|
from .types import TypeBase
|
|
22
29
|
|
|
30
|
+
# The callback type definition for the diagnostic messages.
|
|
31
|
+
DiagnosticCallback = Callable[[uni.UniNode, str, bool], None]
|
|
32
|
+
|
|
23
33
|
|
|
24
34
|
@dataclass
|
|
25
35
|
class PrefetchedTypes:
|
|
@@ -35,6 +45,7 @@ class PrefetchedTypes:
|
|
|
35
45
|
tuple_class: TypeBase | None = None
|
|
36
46
|
bool_class: TypeBase | None = None
|
|
37
47
|
int_class: TypeBase | None = None
|
|
48
|
+
float_class: TypeBase | None = None
|
|
38
49
|
str_class: TypeBase | None = None
|
|
39
50
|
dict_class: TypeBase | None = None
|
|
40
51
|
module_type_class: TypeBase | None = None
|
|
@@ -60,10 +71,35 @@ class SymbolResolutionStackEntry:
|
|
|
60
71
|
partial_type: TypeBase | None = None
|
|
61
72
|
|
|
62
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
|
+
|
|
63
87
|
class TypeEvaluator:
|
|
64
88
|
"""Type evaluator for JacLang."""
|
|
65
89
|
|
|
66
|
-
|
|
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:
|
|
67
103
|
"""Initialize the type evaluator with prefetched types.
|
|
68
104
|
|
|
69
105
|
Implementation Note:
|
|
@@ -74,10 +110,68 @@ class TypeEvaluator:
|
|
|
74
110
|
in some place then it will not be available in the evaluator, So we
|
|
75
111
|
are prefetching the builtins at the constructor level once.
|
|
76
112
|
"""
|
|
77
|
-
self.symbol_resolution_stack: list[SymbolResolutionStackEntry] = []
|
|
78
|
-
self.builtins_module = builtins_module
|
|
79
113
|
self.program = program
|
|
114
|
+
self.symbol_resolution_stack: list[SymbolResolutionStackEntry] = []
|
|
115
|
+
self.builtins_module = self._load_builtins_stub_module()
|
|
80
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)
|
|
81
175
|
|
|
82
176
|
# -------------------------------------------------------------------------
|
|
83
177
|
# Symbol resolution stack
|
|
@@ -229,15 +323,25 @@ class TypeEvaluator:
|
|
|
229
323
|
if node.name_spec.type is not None:
|
|
230
324
|
return cast(types.ClassType, node.name_spec.type)
|
|
231
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
|
+
|
|
232
332
|
cls_type = types.ClassType(
|
|
233
333
|
types.ClassType.ClassDetailsShared(
|
|
234
334
|
class_name=node.name_spec.sym_name,
|
|
235
335
|
symbol_table=node,
|
|
236
|
-
|
|
336
|
+
base_classes=base_classes,
|
|
337
|
+
is_builtin_class=is_builtin_class,
|
|
237
338
|
),
|
|
238
339
|
flags=types.TypeFlags.Instantiable,
|
|
239
340
|
)
|
|
240
341
|
|
|
342
|
+
# Compute the MRO for the class.
|
|
343
|
+
type_utils.compute_mro_linearization(cls_type)
|
|
344
|
+
|
|
241
345
|
# Cache the type, pyright is doing invalidateTypeCacheIfCanceled()
|
|
242
346
|
# we're not doing that any time sooner.
|
|
243
347
|
node.name_spec.type = cls_type
|
|
@@ -252,17 +356,61 @@ class TypeEvaluator:
|
|
|
252
356
|
node.name_spec.type = types.UnknownType()
|
|
253
357
|
return node.name_spec.type
|
|
254
358
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
+
)
|
|
258
409
|
|
|
259
|
-
return_type = self._convert_to_instance(
|
|
260
|
-
self.get_type_of_expression(node.signature.return_type)
|
|
261
|
-
)
|
|
262
410
|
func_type = types.FunctionType(
|
|
263
411
|
func_name=node.name_spec.sym_name,
|
|
264
412
|
return_type=return_type,
|
|
265
|
-
parameters=
|
|
413
|
+
parameters=parameters,
|
|
266
414
|
)
|
|
267
415
|
|
|
268
416
|
node.name_spec.type = func_type
|
|
@@ -285,6 +433,11 @@ class TypeEvaluator:
|
|
|
285
433
|
assert self.prefetch.int_class is not None
|
|
286
434
|
return self.prefetch.int_class
|
|
287
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
|
+
|
|
288
441
|
# Pyright equivalent function name = getTypeOfExpression();
|
|
289
442
|
def get_type_of_expression(self, node: uni.Expr) -> TypeBase:
|
|
290
443
|
"""Return the effective type of the expression."""
|
|
@@ -311,9 +464,6 @@ class TypeEvaluator:
|
|
|
311
464
|
# NOTE: For now if we don't have the type info, we assume it's compatible.
|
|
312
465
|
# For strict mode we should disallow usage of unknown unless explicitly ignored.
|
|
313
466
|
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
467
|
|
|
318
468
|
if src_type == dest_type:
|
|
319
469
|
return True
|
|
@@ -323,8 +473,7 @@ class TypeEvaluator:
|
|
|
323
473
|
assert isinstance(src_type, types.ClassType)
|
|
324
474
|
return self._assign_class(src_type, dest_type)
|
|
325
475
|
|
|
326
|
-
|
|
327
|
-
return src_type == dest_type
|
|
476
|
+
return False
|
|
328
477
|
|
|
329
478
|
# TODO: This should take an argument list as parameter.
|
|
330
479
|
def get_type_of_magic_method_call(
|
|
@@ -356,39 +505,23 @@ class TypeEvaluator:
|
|
|
356
505
|
if src_type.shared == dest_type.shared:
|
|
357
506
|
return True
|
|
358
507
|
|
|
359
|
-
#
|
|
360
|
-
|
|
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
|
|
361
512
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
#
|
|
366
|
-
|
|
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
|
-
)
|
|
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
|
|
386
518
|
|
|
387
|
-
|
|
388
|
-
""
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
|
392
525
|
|
|
393
526
|
# This function is a combination of the bellow pyright functions.
|
|
394
527
|
# - getDeclaredTypeOfSymbol
|
|
@@ -414,6 +547,11 @@ class TypeEvaluator:
|
|
|
414
547
|
case uni.Ability():
|
|
415
548
|
return self.get_type_of_ability(node)
|
|
416
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
|
+
|
|
417
555
|
# This actually defined in the function getTypeForDeclaration();
|
|
418
556
|
# Pyright has DeclarationType.Variable.
|
|
419
557
|
case uni.Name():
|
|
@@ -451,6 +589,9 @@ class TypeEvaluator:
|
|
|
451
589
|
case uni.Int():
|
|
452
590
|
return self._convert_to_instance(self.get_type_of_int(expr))
|
|
453
591
|
|
|
592
|
+
case uni.Float():
|
|
593
|
+
return self._convert_to_instance(self.get_type_of_float(expr))
|
|
594
|
+
|
|
454
595
|
case uni.AtomTrailer():
|
|
455
596
|
# NOTE: Pyright is using CFG to figure out the member type by narrowing the base
|
|
456
597
|
# type and filtering the members. We're not doing that anytime sooner.
|
|
@@ -496,25 +637,18 @@ class TypeEvaluator:
|
|
|
496
637
|
return self.get_type_of_expression(expr.value)
|
|
497
638
|
|
|
498
639
|
case uni.FuncCall():
|
|
499
|
-
|
|
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
|
|
640
|
+
return self.validate_call_args(expr)
|
|
513
641
|
|
|
514
642
|
case uni.BinaryExpr():
|
|
515
643
|
return operations.get_type_of_binary_operation(self, expr)
|
|
516
644
|
|
|
517
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
|
+
|
|
518
652
|
if symbol := expr.sym_tab.lookup(expr.value, deep=True):
|
|
519
653
|
expr.sym = symbol
|
|
520
654
|
return self.get_type_of_symbol(symbol)
|
|
@@ -522,6 +656,48 @@ class TypeEvaluator:
|
|
|
522
656
|
# TODO: More expressions.
|
|
523
657
|
return types.UnknownType()
|
|
524
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
|
+
|
|
525
701
|
def _convert_to_instance(self, jtype: TypeBase) -> TypeBase:
|
|
526
702
|
"""Convert a class type to an instance type."""
|
|
527
703
|
# TODO: Grep pyright "Handle type[x] as a special case." They handle `type[x]` as a special case:
|
|
@@ -536,7 +712,7 @@ class TypeEvaluator:
|
|
|
536
712
|
|
|
537
713
|
def _lookup_class_member(
|
|
538
714
|
self, base_type: types.ClassType, member: str
|
|
539
|
-
) -> ClassMember | None:
|
|
715
|
+
) -> type_utils.ClassMember | None:
|
|
540
716
|
"""Lookup the class member type."""
|
|
541
717
|
assert self.prefetch.int_class is not None
|
|
542
718
|
# FIXME: Pyright's way: Implement class member iterator (based on mro and the multiple inheritance)
|
|
@@ -544,13 +720,14 @@ class TypeEvaluator:
|
|
|
544
720
|
|
|
545
721
|
# NOTE: This is a simple implementation to make it work and more robust implementation will
|
|
546
722
|
# be done in a future PR.
|
|
547
|
-
|
|
548
|
-
|
|
723
|
+
for cls in base_type.shared.mro:
|
|
724
|
+
if sym := cls.lookup_member_symbol(member):
|
|
725
|
+
return type_utils.ClassMember(sym, cls)
|
|
549
726
|
return None
|
|
550
727
|
|
|
551
728
|
def _lookup_object_member(
|
|
552
729
|
self, base_type: types.ClassType, member: str
|
|
553
|
-
) -> ClassMember | None:
|
|
730
|
+
) -> type_utils.ClassMember | None:
|
|
554
731
|
"""Lookup the object member type."""
|
|
555
732
|
assert self.prefetch.int_class is not None
|
|
556
733
|
if base_type.is_class_instance():
|
|
@@ -558,3 +735,110 @@ class TypeEvaluator:
|
|
|
558
735
|
# TODO: We need to implement Member lookup flags and set SkipInstanceMember to 0.
|
|
559
736
|
return self._lookup_class_member(base_type, member)
|
|
560
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
|
+
)
|