jaclang 0.8.5__py3-none-any.whl → 0.8.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jaclang might be problematic. Click here for more details.
- jaclang/cli/cli.md +1 -0
- jaclang/cli/cli.py +38 -18
- jaclang/compiler/passes/main/__init__.py +2 -0
- jaclang/compiler/passes/main/cfg_build_pass.py +21 -1
- jaclang/compiler/passes/main/inheritance_pass.py +8 -1
- jaclang/compiler/passes/main/pyast_gen_pass.py +57 -8
- jaclang/compiler/passes/main/sym_tab_build_pass.py +4 -0
- jaclang/compiler/passes/main/tests/fixtures/cfg_has_var.jac +12 -0
- jaclang/compiler/passes/main/tests/fixtures/cfg_if_no_else.jac +11 -0
- jaclang/compiler/passes/main/tests/fixtures/cfg_return.jac +9 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_imported.jac +2 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_importer.jac +6 -0
- jaclang/compiler/passes/main/tests/fixtures/data_spatial_types.jac +1 -1
- jaclang/compiler/passes/main/tests/fixtures/import_symbol_type_infer.jac +11 -0
- jaclang/compiler/passes/main/tests/fixtures/infer_type_assignment.jac +5 -0
- jaclang/compiler/passes/main/tests/fixtures/member_access_type_inferred.jac +13 -0
- jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +11 -0
- jaclang/compiler/passes/main/tests/fixtures/type_annotation_assignment.jac +8 -0
- jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +62 -24
- jaclang/compiler/passes/main/tests/test_checker_pass.py +87 -0
- jaclang/compiler/passes/main/type_checker_pass.py +128 -0
- jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +1 -4
- jaclang/compiler/program.py +17 -3
- jaclang/compiler/type_system/__init__.py +1 -0
- jaclang/compiler/type_system/type_evaluator.py +421 -0
- jaclang/compiler/type_system/type_utils.py +41 -0
- jaclang/compiler/type_system/types.py +240 -0
- jaclang/compiler/unitree.py +15 -9
- jaclang/langserve/dev_engine.jac +645 -0
- jaclang/langserve/dev_server.jac +201 -0
- jaclang/langserve/engine.jac +1 -1
- jaclang/langserve/tests/server_test/test_lang_serve.py +2 -2
- jaclang/langserve/tests/test_dev_server.py +80 -0
- jaclang/runtimelib/builtin.py +28 -39
- jaclang/runtimelib/importer.py +1 -1
- jaclang/runtimelib/machine.py +48 -64
- jaclang/runtimelib/memory.py +23 -5
- jaclang/runtimelib/tests/fixtures/savable_object.jac +10 -2
- jaclang/runtimelib/utils.py +13 -6
- jaclang/tests/fixtures/edge_node_walk.jac +1 -1
- jaclang/tests/fixtures/edges_walk.jac +1 -1
- jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
- jaclang/tests/fixtures/py_run.jac +8 -0
- jaclang/tests/fixtures/py_run.py +23 -0
- jaclang/tests/fixtures/pyfunc.py +2 -0
- jaclang/tests/test_cli.py +40 -0
- jaclang/tests/test_language.py +10 -4
- jaclang/utils/lang_tools.py +3 -0
- {jaclang-0.8.5.dist-info → jaclang-0.8.6.dist-info}/METADATA +2 -1
- {jaclang-0.8.5.dist-info → jaclang-0.8.6.dist-info}/RECORD +52 -31
- {jaclang-0.8.5.dist-info → jaclang-0.8.6.dist-info}/WHEEL +0 -0
- {jaclang-0.8.5.dist-info → jaclang-0.8.6.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type checker pass.
|
|
3
|
+
|
|
4
|
+
This will perform type checking on the Jac program and accumulate any type
|
|
5
|
+
errors found during the process in the pass's had_errors, had_warnings list.
|
|
6
|
+
|
|
7
|
+
Reference:
|
|
8
|
+
Pyright: packages/pyright-internal/src/analyzer/checker.ts
|
|
9
|
+
craizy_type_expr branch: type_checker_pass.py
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import ast as py_ast
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
import jaclang.compiler.unitree as uni
|
|
16
|
+
from jaclang.compiler.passes import UniPass
|
|
17
|
+
from jaclang.compiler.type_system.type_evaluator import TypeEvaluator
|
|
18
|
+
from jaclang.runtimelib.utils import read_file_with_encoding
|
|
19
|
+
|
|
20
|
+
from .pyast_load_pass import PyastBuildPass
|
|
21
|
+
from .sym_tab_build_pass import SymTabBuildPass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TypeCheckPass(UniPass):
|
|
25
|
+
"""Type checker pass for JacLang."""
|
|
26
|
+
|
|
27
|
+
# NOTE: This is done in the binder pass of pyright, however I'm doing this
|
|
28
|
+
# here, cause this will be the entry point of the type checker and we're not
|
|
29
|
+
# relying on the binder pass at the moment and we can go back to binder pass
|
|
30
|
+
# in the future if we needed it.
|
|
31
|
+
_BUILTINS_STUB_FILE_PATH = os.path.join(
|
|
32
|
+
os.path.dirname(__file__),
|
|
33
|
+
"../../../vendor/typeshed/stdlib/builtins.pyi",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Cache the builtins module once it parsed.
|
|
37
|
+
_BUILTINS_MODULE: uni.Module | None = None
|
|
38
|
+
|
|
39
|
+
def before_pass(self) -> None:
|
|
40
|
+
"""Initialize the checker pass."""
|
|
41
|
+
self._load_builtins_stub_module()
|
|
42
|
+
self._insert_builtin_symbols()
|
|
43
|
+
|
|
44
|
+
assert TypeCheckPass._BUILTINS_MODULE is not None
|
|
45
|
+
self.evaluator = TypeEvaluator(
|
|
46
|
+
builtins_module=TypeCheckPass._BUILTINS_MODULE,
|
|
47
|
+
program=self.prog,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# --------------------------------------------------------------------------
|
|
51
|
+
# Internal helper functions
|
|
52
|
+
# --------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
def _binding_builtins(self) -> bool:
|
|
55
|
+
"""Return true if we're binding the builtins stub file."""
|
|
56
|
+
return self.ir_in == TypeCheckPass._BUILTINS_MODULE
|
|
57
|
+
|
|
58
|
+
def _load_builtins_stub_module(self) -> None:
|
|
59
|
+
"""Return the builtins stub module.
|
|
60
|
+
|
|
61
|
+
This will parse and cache the stub file and return the cached module on
|
|
62
|
+
subsequent calls.
|
|
63
|
+
"""
|
|
64
|
+
if self._binding_builtins() or TypeCheckPass._BUILTINS_MODULE is not None:
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
if not os.path.exists(TypeCheckPass._BUILTINS_STUB_FILE_PATH):
|
|
68
|
+
raise FileNotFoundError(
|
|
69
|
+
f"Builtins stub file not found at {TypeCheckPass._BUILTINS_STUB_FILE_PATH}"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
file_content = read_file_with_encoding(TypeCheckPass._BUILTINS_STUB_FILE_PATH)
|
|
73
|
+
uni_source = uni.Source(file_content, TypeCheckPass._BUILTINS_STUB_FILE_PATH)
|
|
74
|
+
mod = PyastBuildPass(
|
|
75
|
+
ir_in=uni.PythonModuleAst(
|
|
76
|
+
py_ast.parse(file_content),
|
|
77
|
+
orig_src=uni_source,
|
|
78
|
+
),
|
|
79
|
+
prog=self.prog,
|
|
80
|
+
).ir_out
|
|
81
|
+
SymTabBuildPass(ir_in=mod, prog=self.prog)
|
|
82
|
+
TypeCheckPass._BUILTINS_MODULE = mod
|
|
83
|
+
|
|
84
|
+
def _insert_builtin_symbols(self) -> None:
|
|
85
|
+
if self._binding_builtins():
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
# TODO: Insert these symbols.
|
|
89
|
+
# Reference: pyright Binder.bindModule()
|
|
90
|
+
#
|
|
91
|
+
# List taken from https://docs.python.org/3/reference/import.html#__name__
|
|
92
|
+
# '__name__', '__loader__', '__package__', '__spec__', '__path__',
|
|
93
|
+
# '__file__', '__cached__', '__dict__', '__annotations__',
|
|
94
|
+
# '__builtins__', '__doc__',
|
|
95
|
+
assert (
|
|
96
|
+
TypeCheckPass._BUILTINS_MODULE is not None
|
|
97
|
+
), "Builtins module is not loaded"
|
|
98
|
+
if self.ir_in.parent_scope is not None:
|
|
99
|
+
self.log_info("Builtins module is already bound, skipping.")
|
|
100
|
+
return
|
|
101
|
+
# Review: If we ever assume a module cannot have a parent scope, this will
|
|
102
|
+
# break that contract.
|
|
103
|
+
self.ir_in.parent_scope = TypeCheckPass._BUILTINS_MODULE
|
|
104
|
+
|
|
105
|
+
# --------------------------------------------------------------------------
|
|
106
|
+
# Ast walker hooks
|
|
107
|
+
# --------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def exit_assignment(self, node: uni.Assignment) -> None:
|
|
110
|
+
"""Pyright: Checker.visitAssignment(node: AssignmentNode): boolean."""
|
|
111
|
+
# TODO: In pyright this logic is present at evaluateTypesForAssignmentStatement
|
|
112
|
+
# and we're calling getTypeForStatement from here, This can be moved into the
|
|
113
|
+
# other place or we can keep it here.
|
|
114
|
+
#
|
|
115
|
+
# Grep this in pyright TypeEvaluator.ts:
|
|
116
|
+
# `} else if (node.d.leftExpr.nodeType === ParseNodeType.Name) {`
|
|
117
|
+
#
|
|
118
|
+
if len(node.target) == 1 and (node.value is not None): # Simple assignment.
|
|
119
|
+
left_type = self.evaluator.get_type_of_expression(node.target[0])
|
|
120
|
+
right_type = self.evaluator.get_type_of_expression(node.value)
|
|
121
|
+
if not self.evaluator.assign_type(right_type, left_type):
|
|
122
|
+
self.log_error(f"Cannot assign {right_type} to {left_type}")
|
|
123
|
+
else:
|
|
124
|
+
pass # TODO: handle
|
|
125
|
+
|
|
126
|
+
def exit_atom_trailer(self, node: uni.AtomTrailer) -> None:
|
|
127
|
+
"""Handle the atom trailer node."""
|
|
128
|
+
self.evaluator.get_type_of_expression(node)
|
jaclang/compiler/program.py
CHANGED
|
@@ -26,6 +26,7 @@ from jaclang.compiler.passes.main import (
|
|
|
26
26
|
SymTabBuildPass,
|
|
27
27
|
SymTabLinkPass,
|
|
28
28
|
Transform,
|
|
29
|
+
TypeCheckPass,
|
|
29
30
|
)
|
|
30
31
|
from jaclang.compiler.passes.tool import (
|
|
31
32
|
DocIRGenPass,
|
|
@@ -46,6 +47,9 @@ ir_gen_sched = [
|
|
|
46
47
|
CFGBuildPass,
|
|
47
48
|
InheritancePass,
|
|
48
49
|
]
|
|
50
|
+
type_check_sched: list = [
|
|
51
|
+
TypeCheckPass,
|
|
52
|
+
]
|
|
49
53
|
py_code_gen = [
|
|
50
54
|
PyastGenPass,
|
|
51
55
|
PyJacAstLinkPass,
|
|
@@ -102,12 +106,20 @@ class JacProgram:
|
|
|
102
106
|
return mod
|
|
103
107
|
|
|
104
108
|
def compile(
|
|
105
|
-
self,
|
|
109
|
+
self,
|
|
110
|
+
file_path: str,
|
|
111
|
+
use_str: str | None = None,
|
|
112
|
+
# TODO: Create a compilation options class and put the bellow
|
|
113
|
+
# options in it.
|
|
114
|
+
no_cgen: bool = False,
|
|
115
|
+
type_check: bool = False,
|
|
106
116
|
) -> uni.Module:
|
|
107
117
|
"""Convert a Jac file to an AST."""
|
|
108
118
|
keep_str = use_str or read_file_with_encoding(file_path)
|
|
109
119
|
mod_targ = self.parse_str(keep_str, file_path)
|
|
110
120
|
self.run_schedule(mod=mod_targ, passes=ir_gen_sched)
|
|
121
|
+
if type_check:
|
|
122
|
+
self.run_schedule(mod=mod_targ, passes=type_check_sched)
|
|
111
123
|
if not no_cgen:
|
|
112
124
|
self.run_schedule(mod=mod_targ, passes=py_code_gen)
|
|
113
125
|
return mod_targ
|
|
@@ -119,9 +131,11 @@ class JacProgram:
|
|
|
119
131
|
BinderPass(ir_in=mod_targ, prog=self)
|
|
120
132
|
return mod_targ
|
|
121
133
|
|
|
122
|
-
def build(
|
|
134
|
+
def build(
|
|
135
|
+
self, file_path: str, use_str: str | None = None, type_check: bool = False
|
|
136
|
+
) -> uni.Module:
|
|
123
137
|
"""Convert a Jac file to an AST."""
|
|
124
|
-
mod_targ = self.compile(file_path, use_str)
|
|
138
|
+
mod_targ = self.compile(file_path, use_str, type_check=type_check)
|
|
125
139
|
JacImportDepsPass(ir_in=mod_targ, prog=self)
|
|
126
140
|
for mod in self.mod.hub.values():
|
|
127
141
|
SymTabLinkPass(ir_in=mod, prog=self)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Jaclang type checking."""
|
|
@@ -0,0 +1,421 @@
|
|
|
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 .type_utils import ClassMember
|
|
20
|
+
from .types import TypeBase
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class PrefetchedTypes:
|
|
25
|
+
"""Types whose definitions are prefetched and cached by the type evaluator."""
|
|
26
|
+
|
|
27
|
+
none_type_class: TypeBase | None = None
|
|
28
|
+
object_class: TypeBase | None = None
|
|
29
|
+
type_class: TypeBase | None = None
|
|
30
|
+
union_type_class: TypeBase | None = None
|
|
31
|
+
awaitable_class: TypeBase | None = None
|
|
32
|
+
function_class: TypeBase | None = None
|
|
33
|
+
method_class: TypeBase | None = None
|
|
34
|
+
tuple_class: TypeBase | None = None
|
|
35
|
+
bool_class: TypeBase | None = None
|
|
36
|
+
int_class: TypeBase | None = None
|
|
37
|
+
str_class: TypeBase | None = None
|
|
38
|
+
dict_class: TypeBase | None = None
|
|
39
|
+
module_type_class: TypeBase | None = None
|
|
40
|
+
typed_dict_class: TypeBase | None = None
|
|
41
|
+
typed_dict_private_class: TypeBase | None = None
|
|
42
|
+
supports_keys_and_get_item_class: TypeBase | None = None
|
|
43
|
+
mapping_class: TypeBase | None = None
|
|
44
|
+
template_class: TypeBase | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TypeEvaluator:
|
|
48
|
+
"""Type evaluator for JacLang."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, builtins_module: uni.Module, program: "JacProgram") -> None:
|
|
51
|
+
"""Initialize the type evaluator with prefetched types.
|
|
52
|
+
|
|
53
|
+
Implementation Note:
|
|
54
|
+
--------------------
|
|
55
|
+
Pyright is prefetching the builtins when an evaluation is requested
|
|
56
|
+
on a node and from that node it does lookup for the builtins scope
|
|
57
|
+
and does the prefetch once, however if we forgot to call prefetch
|
|
58
|
+
in some place then it will not be available in the evaluator, So we
|
|
59
|
+
are prefetching the builtins at the constructor level once.
|
|
60
|
+
"""
|
|
61
|
+
self.builtins_module = builtins_module
|
|
62
|
+
self.program = program
|
|
63
|
+
self.prefetch = self._prefetch_types()
|
|
64
|
+
|
|
65
|
+
# Pyright equivalent function name = getEffectiveTypeOfSymbol.
|
|
66
|
+
def get_type_of_symbol(self, symbol: uni.Symbol) -> TypeBase:
|
|
67
|
+
"""Return the effective type of the symbol."""
|
|
68
|
+
return self._get_type_of_symbol(symbol)
|
|
69
|
+
|
|
70
|
+
# NOTE: This function doesn't exists in pyright, however it exists as a helper function
|
|
71
|
+
# for the following functions.
|
|
72
|
+
def _import_module_from_path(self, path: str) -> uni.Module:
|
|
73
|
+
"""Import a module from the given path."""
|
|
74
|
+
# Get the module, if it's not loaded yet, compile and get it.
|
|
75
|
+
#
|
|
76
|
+
# TODO:
|
|
77
|
+
# We're not typechecking inside the module itself however we
|
|
78
|
+
# need to check if the module path is site-package or not and
|
|
79
|
+
# do typecheck inside as well.
|
|
80
|
+
mod: uni.Module
|
|
81
|
+
if path in self.program.mod.hub:
|
|
82
|
+
mod = self.program.mod.hub[path]
|
|
83
|
+
else:
|
|
84
|
+
mod = self.program.compile(path, no_cgen=True, type_check=False)
|
|
85
|
+
# FIXME: Inherit from builtin symbol table logic is currently implemented
|
|
86
|
+
# here and checker pass, however it should be in one location, since we're
|
|
87
|
+
# doing type_check=False, it doesn't set parent_scope to builtins, this
|
|
88
|
+
# needs to be done properly. The way jaclang handles symbol table is different
|
|
89
|
+
# than pyright, so we cannot strictly follow them, however as long as a new
|
|
90
|
+
# module has a parent scope as builtin scope, we're aligned with pyright.
|
|
91
|
+
if mod.parent_scope is None and mod is not self.builtins_module:
|
|
92
|
+
mod.parent_scope = self.builtins_module
|
|
93
|
+
return mod
|
|
94
|
+
|
|
95
|
+
def get_type_of_module(self, node: uni.ModulePath) -> types.ModuleType:
|
|
96
|
+
"""Return the effective type of the module."""
|
|
97
|
+
if node.name_spec.type is not None:
|
|
98
|
+
return cast(types.ModuleType, node.name_spec.type)
|
|
99
|
+
|
|
100
|
+
mod: uni.Module = self._import_module_from_path(node.resolve_relative_path())
|
|
101
|
+
mod_type = types.ModuleType(
|
|
102
|
+
mod_name=node.name_spec.sym_name,
|
|
103
|
+
file_uri=Path(node.resolve_relative_path()).resolve(),
|
|
104
|
+
symbol_table=mod,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
node.name_spec.type = mod_type
|
|
108
|
+
return mod_type
|
|
109
|
+
|
|
110
|
+
def get_type_of_module_item(self, node: uni.ModuleItem) -> types.TypeBase:
|
|
111
|
+
"""Return the effective type of the module item."""
|
|
112
|
+
# Module item can be both a module or a member of a module.
|
|
113
|
+
# import from .. { mod } # <-- Here mod is not a member but a module itself.
|
|
114
|
+
# import from mod { item } # <-- Here item is not a module but a member of mod.
|
|
115
|
+
if node.name_spec.type is not None:
|
|
116
|
+
return node.name_spec.type
|
|
117
|
+
|
|
118
|
+
import_node = node.parent_of_type(uni.Import)
|
|
119
|
+
if import_node.from_loc:
|
|
120
|
+
|
|
121
|
+
from_path = Path(import_node.from_loc.resolve_relative_path())
|
|
122
|
+
is_dir = from_path.is_dir() or (from_path.stem == "__init__")
|
|
123
|
+
|
|
124
|
+
# import from .. { mod }
|
|
125
|
+
if is_dir:
|
|
126
|
+
mod_dir = from_path.parent if not from_path.is_dir() else from_path
|
|
127
|
+
# FIXME: Implement module resolution properly.
|
|
128
|
+
for ext in (".jac", ".py", ".pyi"):
|
|
129
|
+
if (path := (mod_dir / (node.name.value + ext)).resolve()).exists():
|
|
130
|
+
mod = self._import_module_from_path(str(path))
|
|
131
|
+
mod_type = types.ModuleType(
|
|
132
|
+
mod_name=node.name_spec.sym_name,
|
|
133
|
+
file_uri=path,
|
|
134
|
+
symbol_table=mod,
|
|
135
|
+
)
|
|
136
|
+
# Cache the type.
|
|
137
|
+
node.name_spec.type = mod_type
|
|
138
|
+
|
|
139
|
+
# FIXME: goto definition works on imported symbol by checking if it's a MODULE
|
|
140
|
+
# type and in that case it'll call resolve_relative_path on the parent node of
|
|
141
|
+
# the symbol's definition node (a module path), So the goto definition to work
|
|
142
|
+
# properly the category should be module on a module path, If we set like this
|
|
143
|
+
# below should work but because of the above assumption (should be a mod path)
|
|
144
|
+
# it won't, This needs to be discussed.
|
|
145
|
+
#
|
|
146
|
+
# node.name_spec._sym_category = uni.SymbolType.MODULE
|
|
147
|
+
return node.name_spec.type
|
|
148
|
+
|
|
149
|
+
# import from mod { item }
|
|
150
|
+
else:
|
|
151
|
+
mod_type = self.get_type_of_module(import_node.from_loc)
|
|
152
|
+
if sym := mod_type.symbol_table.lookup(node.name.value, deep=True):
|
|
153
|
+
node.name.sym = sym
|
|
154
|
+
if node.alias:
|
|
155
|
+
node.alias.sym = sym
|
|
156
|
+
node.name_spec.type = self.get_type_of_symbol(sym)
|
|
157
|
+
return node.name_spec.type
|
|
158
|
+
|
|
159
|
+
return types.UnknownType()
|
|
160
|
+
|
|
161
|
+
def get_type_of_class(self, node: uni.Archetype) -> types.ClassType:
|
|
162
|
+
"""Return the effective type of the class."""
|
|
163
|
+
# Is this type already cached?
|
|
164
|
+
if node.name_spec.type is not None:
|
|
165
|
+
return cast(types.ClassType, node.name_spec.type)
|
|
166
|
+
|
|
167
|
+
cls_type = types.ClassType(
|
|
168
|
+
types.ClassType.ClassDetailsShared(
|
|
169
|
+
class_name=node.name_spec.sym_name,
|
|
170
|
+
symbol_table=node,
|
|
171
|
+
# TODO: Resolve the base class expression and pass them here.
|
|
172
|
+
),
|
|
173
|
+
flags=types.TypeFlags.Instantiable,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Cache the type, pyright is doing invalidateTypeCacheIfCanceled()
|
|
177
|
+
# we're not doing that any time sooner.
|
|
178
|
+
node.name_spec.type = cls_type
|
|
179
|
+
return cls_type
|
|
180
|
+
|
|
181
|
+
def get_type_of_string(self, node: uni.String | uni.MultiString) -> TypeBase:
|
|
182
|
+
"""Return the effective type of the string."""
|
|
183
|
+
# FIXME: Strings are a type of LiteralString type:
|
|
184
|
+
# "foo" is not `str` but Literal["foo"], however for now we'll
|
|
185
|
+
# not considering that and make it work and will implement that
|
|
186
|
+
# later.
|
|
187
|
+
#
|
|
188
|
+
# see: getTypeOfString() in pyright (it requires parsing the sub
|
|
189
|
+
# file of the typing module).
|
|
190
|
+
assert self.prefetch.str_class is not None
|
|
191
|
+
return self.prefetch.str_class
|
|
192
|
+
|
|
193
|
+
def get_type_of_int(self, node: uni.Int) -> TypeBase:
|
|
194
|
+
"""Return the effective type of the int."""
|
|
195
|
+
assert self.prefetch.int_class is not None
|
|
196
|
+
return self.prefetch.int_class
|
|
197
|
+
|
|
198
|
+
# Pyright equivalent function name = getTypeOfExpression();
|
|
199
|
+
def get_type_of_expression(self, node: uni.Expr) -> TypeBase:
|
|
200
|
+
"""Return the effective type of the expression."""
|
|
201
|
+
# If it's alreay "cached" return it.
|
|
202
|
+
if node.type is not None:
|
|
203
|
+
return node.type
|
|
204
|
+
|
|
205
|
+
result = self._get_type_of_expression_core(node)
|
|
206
|
+
# If the context has an expected type, pyright does a compatibility and set
|
|
207
|
+
# a diagnostics here, I don't understand why that might be necessary here.
|
|
208
|
+
|
|
209
|
+
node.type = result # Cache the result
|
|
210
|
+
return result
|
|
211
|
+
|
|
212
|
+
# Comments from pyright:
|
|
213
|
+
# // Determines if the source type can be assigned to the dest type.
|
|
214
|
+
# // If constraint are provided, type variables within the destType are
|
|
215
|
+
# // matched against existing type variables in the map. If a type variable
|
|
216
|
+
# // in the dest type is not in the type map already, it is assigned a type
|
|
217
|
+
# // and added to the map.
|
|
218
|
+
def assign_type(self, src_type: TypeBase, dest_type: TypeBase) -> bool:
|
|
219
|
+
"""Assign the source type to the destination type."""
|
|
220
|
+
if types.TypeCategory.Unknown in (src_type.category, dest_type.category):
|
|
221
|
+
# NOTE: For now if we don't have the type info, we assume it's compatible.
|
|
222
|
+
# For strict mode we should disallow usage of unknown unless explicitly ignored.
|
|
223
|
+
return True
|
|
224
|
+
# FIXME: This logic is not valid, just here as a stub.
|
|
225
|
+
if types.TypeCategory.Unknown in (src_type.category, dest_type.category):
|
|
226
|
+
return True
|
|
227
|
+
|
|
228
|
+
if src_type == dest_type:
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
if dest_type.is_class_instance() and src_type.is_class_instance():
|
|
232
|
+
assert isinstance(dest_type, types.ClassType)
|
|
233
|
+
assert isinstance(src_type, types.ClassType)
|
|
234
|
+
return self._assign_class(src_type, dest_type)
|
|
235
|
+
|
|
236
|
+
# FIXME: This is temporary.
|
|
237
|
+
return src_type == dest_type
|
|
238
|
+
|
|
239
|
+
def _assign_class(
|
|
240
|
+
self, src_type: types.ClassType, dest_type: types.ClassType
|
|
241
|
+
) -> bool:
|
|
242
|
+
"""Assign the source class type to the destination class type."""
|
|
243
|
+
if src_type.shared == dest_type.shared:
|
|
244
|
+
return True
|
|
245
|
+
|
|
246
|
+
# TODO: Search base classes and everything else pyright is doing.
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
def _prefetch_types(self) -> "PrefetchedTypes":
|
|
250
|
+
"""Return the prefetched types for the type evaluator."""
|
|
251
|
+
return PrefetchedTypes(
|
|
252
|
+
# TODO: Pyright first try load NoneType from typeshed and if it cannot
|
|
253
|
+
# then it set to unknown type.
|
|
254
|
+
none_type_class=types.UnknownType(),
|
|
255
|
+
object_class=self._get_builtin_type("object"),
|
|
256
|
+
type_class=self._get_builtin_type("type"),
|
|
257
|
+
# union_type_class=
|
|
258
|
+
# awaitable_class=
|
|
259
|
+
# function_class=
|
|
260
|
+
# method_class=
|
|
261
|
+
tuple_class=self._get_builtin_type("tuple"),
|
|
262
|
+
bool_class=self._get_builtin_type("bool"),
|
|
263
|
+
int_class=self._get_builtin_type("int"),
|
|
264
|
+
str_class=self._get_builtin_type("str"),
|
|
265
|
+
dict_class=self._get_builtin_type("dict"),
|
|
266
|
+
# module_type_class=
|
|
267
|
+
# typed_dict_class=
|
|
268
|
+
# typed_dict_private_class=
|
|
269
|
+
# supports_keys_and_get_item_class=
|
|
270
|
+
# mapping_class=
|
|
271
|
+
# template_class=
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def _get_builtin_type(self, name: str) -> TypeBase:
|
|
275
|
+
"""Return the built-in type with the given name."""
|
|
276
|
+
if (symbol := self.builtins_module.lookup(name)) is not None:
|
|
277
|
+
return self.get_type_of_symbol(symbol)
|
|
278
|
+
return types.UnknownType()
|
|
279
|
+
|
|
280
|
+
# This function is a combination of the bellow pyright functions.
|
|
281
|
+
# - getDeclaredTypeOfSymbol
|
|
282
|
+
# - getTypeForDeclaration
|
|
283
|
+
#
|
|
284
|
+
# Implementation Note:
|
|
285
|
+
# Pyright is actually have some duplicate logic for handling declared
|
|
286
|
+
# type and inferred type, we're going to unify them (if it's required
|
|
287
|
+
# in the future, we can refactor this).
|
|
288
|
+
def _get_type_of_symbol(self, symbol: uni.Symbol) -> TypeBase:
|
|
289
|
+
"""Return the declared type of the symbol."""
|
|
290
|
+
node = symbol.decl.name_of
|
|
291
|
+
match node:
|
|
292
|
+
case uni.ModulePath():
|
|
293
|
+
return self.get_type_of_module(node)
|
|
294
|
+
|
|
295
|
+
case uni.ModuleItem():
|
|
296
|
+
return self.get_type_of_module_item(node)
|
|
297
|
+
|
|
298
|
+
case uni.Archetype():
|
|
299
|
+
return self.get_type_of_class(node)
|
|
300
|
+
|
|
301
|
+
# This actually defined in the function getTypeForDeclaration();
|
|
302
|
+
# Pyright has DeclarationType.Variable.
|
|
303
|
+
case uni.Name():
|
|
304
|
+
if isinstance(node.parent, uni.Assignment):
|
|
305
|
+
if node.parent.type_tag is not None:
|
|
306
|
+
annotation_type = self.get_type_of_expression(
|
|
307
|
+
node.parent.type_tag.tag
|
|
308
|
+
)
|
|
309
|
+
return self._convert_to_instance(annotation_type)
|
|
310
|
+
|
|
311
|
+
else: # Assignment without a type annotation.
|
|
312
|
+
if node.parent.value is not None:
|
|
313
|
+
return self.get_type_of_expression(node.parent.value)
|
|
314
|
+
|
|
315
|
+
case uni.HasVar():
|
|
316
|
+
if node.type_tag is not None:
|
|
317
|
+
annotation_type = self.get_type_of_expression(node.type_tag.tag)
|
|
318
|
+
return self._convert_to_instance(annotation_type)
|
|
319
|
+
else:
|
|
320
|
+
if node.value is not None:
|
|
321
|
+
return self.get_type_of_expression(node.value)
|
|
322
|
+
|
|
323
|
+
# TODO: Implement for functions, parameters, explicit type
|
|
324
|
+
# annotations in assignment etc.
|
|
325
|
+
return types.UnknownType()
|
|
326
|
+
|
|
327
|
+
# Pyright equivalent function name = getTypeOfExpressionCore();
|
|
328
|
+
def _get_type_of_expression_core(self, expr: uni.Expr) -> TypeBase:
|
|
329
|
+
"""Core function to get the type of the expression."""
|
|
330
|
+
match expr:
|
|
331
|
+
|
|
332
|
+
case uni.String() | uni.MultiString():
|
|
333
|
+
return self._convert_to_instance(self.get_type_of_string(expr))
|
|
334
|
+
|
|
335
|
+
case uni.Int():
|
|
336
|
+
return self._convert_to_instance(self.get_type_of_int(expr))
|
|
337
|
+
|
|
338
|
+
case uni.AtomTrailer():
|
|
339
|
+
# NOTE: Pyright is using CFG to figure out the member type by narrowing the base
|
|
340
|
+
# type and filtering the members. We're not doing that anytime sooner.
|
|
341
|
+
base_type = self.get_type_of_expression(expr.target)
|
|
342
|
+
|
|
343
|
+
if expr.is_attr: # <expr>.member
|
|
344
|
+
assert isinstance(expr.right, uni.Name)
|
|
345
|
+
|
|
346
|
+
if isinstance(base_type, types.ModuleType):
|
|
347
|
+
# getTypeOfMemberAccessWithBaseType()
|
|
348
|
+
if sym := base_type.symbol_table.lookup(
|
|
349
|
+
expr.right.value, deep=True
|
|
350
|
+
):
|
|
351
|
+
expr.right.sym = sym
|
|
352
|
+
return self.get_type_of_symbol(sym)
|
|
353
|
+
return types.UnknownType()
|
|
354
|
+
|
|
355
|
+
elif base_type.is_instantiable_class():
|
|
356
|
+
assert isinstance(base_type, types.ClassType)
|
|
357
|
+
if member := self._lookup_class_member(
|
|
358
|
+
base_type, expr.right.value
|
|
359
|
+
):
|
|
360
|
+
expr.right.sym = member.symbol
|
|
361
|
+
return self.get_type_of_symbol(member.symbol)
|
|
362
|
+
return types.UnknownType()
|
|
363
|
+
|
|
364
|
+
elif base_type.is_class_instance():
|
|
365
|
+
assert isinstance(base_type, types.ClassType)
|
|
366
|
+
if member := self._lookup_object_member(
|
|
367
|
+
base_type, expr.right.value
|
|
368
|
+
):
|
|
369
|
+
expr.right.sym = member.symbol
|
|
370
|
+
return self.get_type_of_symbol(member.symbol)
|
|
371
|
+
return types.UnknownType()
|
|
372
|
+
|
|
373
|
+
elif expr.is_null_ok: # <expr>?.member
|
|
374
|
+
pass # TODO:
|
|
375
|
+
|
|
376
|
+
else: # <expr>[<expr>]
|
|
377
|
+
pass # TODO:
|
|
378
|
+
|
|
379
|
+
case uni.Name():
|
|
380
|
+
if symbol := expr.sym_tab.lookup(expr.value, deep=True):
|
|
381
|
+
return self.get_type_of_symbol(symbol)
|
|
382
|
+
|
|
383
|
+
# TODO: More expressions.
|
|
384
|
+
return types.UnknownType()
|
|
385
|
+
|
|
386
|
+
def _convert_to_instance(self, jtype: TypeBase) -> TypeBase:
|
|
387
|
+
"""Convert a class type to an instance type."""
|
|
388
|
+
# TODO: Grep pyright "Handle type[x] as a special case." They handle `type[x]` as a special case:
|
|
389
|
+
#
|
|
390
|
+
# foo: int = 42; # <-- Here `int` is instantiable class and, become instance after this method.
|
|
391
|
+
# foo: type[int] = int # <-- Here `type[int]`, this should be `int` that's instantiable.
|
|
392
|
+
#
|
|
393
|
+
if jtype.is_instantiable_class():
|
|
394
|
+
assert isinstance(jtype, types.ClassType)
|
|
395
|
+
return jtype.clone_as_instance()
|
|
396
|
+
return jtype
|
|
397
|
+
|
|
398
|
+
def _lookup_class_member(
|
|
399
|
+
self, base_type: types.ClassType, member: str
|
|
400
|
+
) -> ClassMember | None:
|
|
401
|
+
"""Lookup the class member type."""
|
|
402
|
+
assert self.prefetch.int_class is not None
|
|
403
|
+
# FIXME: Pyright's way: Implement class member iterator (based on mro and the multiple inheritance)
|
|
404
|
+
# return the first found member from the iterator.
|
|
405
|
+
|
|
406
|
+
# NOTE: This is a simple implementation to make it work and more robust implementation will
|
|
407
|
+
# be done in a future PR.
|
|
408
|
+
if sym := base_type.lookup_member_symbol(member):
|
|
409
|
+
return ClassMember(sym, base_type)
|
|
410
|
+
return None
|
|
411
|
+
|
|
412
|
+
def _lookup_object_member(
|
|
413
|
+
self, base_type: types.ClassType, member: str
|
|
414
|
+
) -> ClassMember | None:
|
|
415
|
+
"""Lookup the object member type."""
|
|
416
|
+
assert self.prefetch.int_class is not None
|
|
417
|
+
if base_type.is_class_instance():
|
|
418
|
+
assert isinstance(base_type, types.ClassType)
|
|
419
|
+
# TODO: We need to implement Member lookup flags and set SkipInstanceMember to 0.
|
|
420
|
+
return self._lookup_class_member(base_type, member)
|
|
421
|
+
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
|