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.

Files changed (52) hide show
  1. jaclang/cli/cli.md +1 -0
  2. jaclang/cli/cli.py +38 -18
  3. jaclang/compiler/passes/main/__init__.py +2 -0
  4. jaclang/compiler/passes/main/cfg_build_pass.py +21 -1
  5. jaclang/compiler/passes/main/inheritance_pass.py +8 -1
  6. jaclang/compiler/passes/main/pyast_gen_pass.py +57 -8
  7. jaclang/compiler/passes/main/sym_tab_build_pass.py +4 -0
  8. jaclang/compiler/passes/main/tests/fixtures/cfg_has_var.jac +12 -0
  9. jaclang/compiler/passes/main/tests/fixtures/cfg_if_no_else.jac +11 -0
  10. jaclang/compiler/passes/main/tests/fixtures/cfg_return.jac +9 -0
  11. jaclang/compiler/passes/main/tests/fixtures/checker_imported.jac +2 -0
  12. jaclang/compiler/passes/main/tests/fixtures/checker_importer.jac +6 -0
  13. jaclang/compiler/passes/main/tests/fixtures/data_spatial_types.jac +1 -1
  14. jaclang/compiler/passes/main/tests/fixtures/import_symbol_type_infer.jac +11 -0
  15. jaclang/compiler/passes/main/tests/fixtures/infer_type_assignment.jac +5 -0
  16. jaclang/compiler/passes/main/tests/fixtures/member_access_type_inferred.jac +13 -0
  17. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +11 -0
  18. jaclang/compiler/passes/main/tests/fixtures/type_annotation_assignment.jac +8 -0
  19. jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +62 -24
  20. jaclang/compiler/passes/main/tests/test_checker_pass.py +87 -0
  21. jaclang/compiler/passes/main/type_checker_pass.py +128 -0
  22. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +1 -4
  23. jaclang/compiler/program.py +17 -3
  24. jaclang/compiler/type_system/__init__.py +1 -0
  25. jaclang/compiler/type_system/type_evaluator.py +421 -0
  26. jaclang/compiler/type_system/type_utils.py +41 -0
  27. jaclang/compiler/type_system/types.py +240 -0
  28. jaclang/compiler/unitree.py +15 -9
  29. jaclang/langserve/dev_engine.jac +645 -0
  30. jaclang/langserve/dev_server.jac +201 -0
  31. jaclang/langserve/engine.jac +1 -1
  32. jaclang/langserve/tests/server_test/test_lang_serve.py +2 -2
  33. jaclang/langserve/tests/test_dev_server.py +80 -0
  34. jaclang/runtimelib/builtin.py +28 -39
  35. jaclang/runtimelib/importer.py +1 -1
  36. jaclang/runtimelib/machine.py +48 -64
  37. jaclang/runtimelib/memory.py +23 -5
  38. jaclang/runtimelib/tests/fixtures/savable_object.jac +10 -2
  39. jaclang/runtimelib/utils.py +13 -6
  40. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  41. jaclang/tests/fixtures/edges_walk.jac +1 -1
  42. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  43. jaclang/tests/fixtures/py_run.jac +8 -0
  44. jaclang/tests/fixtures/py_run.py +23 -0
  45. jaclang/tests/fixtures/pyfunc.py +2 -0
  46. jaclang/tests/test_cli.py +40 -0
  47. jaclang/tests/test_language.py +10 -4
  48. jaclang/utils/lang_tools.py +3 -0
  49. {jaclang-0.8.5.dist-info → jaclang-0.8.6.dist-info}/METADATA +2 -1
  50. {jaclang-0.8.5.dist-info → jaclang-0.8.6.dist-info}/RECORD +52 -31
  51. {jaclang-0.8.5.dist-info → jaclang-0.8.6.dist-info}/WHEEL +0 -0
  52. {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)
@@ -53,7 +53,4 @@ with entry {
53
53
  }
54
54
 
55
55
 
56
- def generate_joke -> dict[str, str] by llm(
57
- incl_info={"jokes_example" : self.jokes },
58
- temperature=0.0
59
- );
56
+ def generate_joke -> dict[str, str] by llm();
@@ -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, file_path: str, use_str: str | None = None, no_cgen: bool = False
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(self, file_path: str, use_str: str | None = None) -> uni.Module:
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