jaclang 0.8.4__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 (88) hide show
  1. jaclang/cli/cli.md +1 -0
  2. jaclang/cli/cli.py +109 -37
  3. jaclang/compiler/jac.lark +3 -3
  4. jaclang/compiler/larkparse/jac_parser.py +2 -2
  5. jaclang/compiler/parser.py +14 -21
  6. jaclang/compiler/passes/main/__init__.py +5 -1
  7. jaclang/compiler/passes/main/binder_pass.py +594 -0
  8. jaclang/compiler/passes/main/cfg_build_pass.py +21 -1
  9. jaclang/compiler/passes/main/import_pass.py +8 -256
  10. jaclang/compiler/passes/main/inheritance_pass.py +10 -3
  11. jaclang/compiler/passes/main/pyast_gen_pass.py +92 -77
  12. jaclang/compiler/passes/main/pyast_load_pass.py +24 -13
  13. jaclang/compiler/passes/main/sem_def_match_pass.py +1 -1
  14. jaclang/compiler/passes/main/sym_tab_build_pass.py +4 -0
  15. jaclang/compiler/passes/main/tests/fixtures/M1.jac +3 -0
  16. jaclang/compiler/passes/main/tests/fixtures/cfg_has_var.jac +12 -0
  17. jaclang/compiler/passes/main/tests/fixtures/cfg_if_no_else.jac +11 -0
  18. jaclang/compiler/passes/main/tests/fixtures/cfg_return.jac +9 -0
  19. jaclang/compiler/passes/main/tests/fixtures/checker_imported.jac +2 -0
  20. jaclang/compiler/passes/main/tests/fixtures/checker_importer.jac +6 -0
  21. jaclang/compiler/passes/main/tests/fixtures/data_spatial_types.jac +1 -1
  22. jaclang/compiler/passes/main/tests/fixtures/import_symbol_type_infer.jac +11 -0
  23. jaclang/compiler/passes/main/tests/fixtures/infer_type_assignment.jac +5 -0
  24. jaclang/compiler/passes/main/tests/fixtures/member_access_type_inferred.jac +13 -0
  25. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +11 -0
  26. jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +47 -0
  27. jaclang/compiler/passes/main/tests/fixtures/type_annotation_assignment.jac +8 -0
  28. jaclang/compiler/passes/main/tests/test_binder_pass.py +111 -0
  29. jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +62 -24
  30. jaclang/compiler/passes/main/tests/test_checker_pass.py +87 -0
  31. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +13 -13
  32. jaclang/compiler/passes/main/tests/test_sem_def_match_pass.py +6 -6
  33. jaclang/compiler/passes/main/type_checker_pass.py +128 -0
  34. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +2 -0
  35. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +3 -0
  36. jaclang/compiler/program.py +32 -11
  37. jaclang/compiler/tests/test_sr_errors.py +32 -0
  38. jaclang/compiler/type_system/__init__.py +1 -0
  39. jaclang/compiler/type_system/type_evaluator.py +421 -0
  40. jaclang/compiler/type_system/type_utils.py +41 -0
  41. jaclang/compiler/type_system/types.py +240 -0
  42. jaclang/compiler/unitree.py +36 -24
  43. jaclang/langserve/dev_engine.jac +645 -0
  44. jaclang/langserve/dev_server.jac +201 -0
  45. jaclang/langserve/engine.jac +24 -5
  46. jaclang/langserve/tests/server_test/test_lang_serve.py +2 -2
  47. jaclang/langserve/tests/test_dev_server.py +80 -0
  48. jaclang/langserve/tests/test_server.py +13 -0
  49. jaclang/runtimelib/builtin.py +28 -39
  50. jaclang/runtimelib/importer.py +34 -63
  51. jaclang/runtimelib/machine.py +48 -64
  52. jaclang/runtimelib/memory.py +23 -5
  53. jaclang/runtimelib/tests/fixtures/savable_object.jac +10 -2
  54. jaclang/runtimelib/utils.py +42 -6
  55. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  56. jaclang/tests/fixtures/edges_walk.jac +1 -1
  57. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  58. jaclang/tests/fixtures/py_run.jac +8 -0
  59. jaclang/tests/fixtures/py_run.py +23 -0
  60. jaclang/tests/fixtures/pyfunc.py +2 -0
  61. jaclang/tests/fixtures/pyfunc_fmt.py +60 -0
  62. jaclang/tests/fixtures/pyfunc_fstr.py +25 -0
  63. jaclang/tests/fixtures/pyfunc_kwesc.py +33 -0
  64. jaclang/tests/fixtures/python_run_test.py +19 -0
  65. jaclang/tests/test_cli.py +107 -0
  66. jaclang/tests/test_language.py +106 -5
  67. jaclang/utils/lang_tools.py +6 -3
  68. jaclang/utils/module_resolver.py +90 -0
  69. jaclang/utils/symtable_test_helpers.py +125 -0
  70. jaclang/utils/test.py +3 -4
  71. jaclang/vendor/interegular/__init__.py +34 -0
  72. jaclang/vendor/interegular/comparator.py +163 -0
  73. jaclang/vendor/interegular/fsm.py +1015 -0
  74. jaclang/vendor/interegular/patterns.py +732 -0
  75. jaclang/vendor/interegular/py.typed +0 -0
  76. jaclang/vendor/interegular/utils/__init__.py +15 -0
  77. jaclang/vendor/interegular/utils/simple_parser.py +165 -0
  78. jaclang/vendor/interegular-0.3.3.dist-info/INSTALLER +1 -0
  79. jaclang/vendor/interegular-0.3.3.dist-info/LICENSE.txt +21 -0
  80. jaclang/vendor/interegular-0.3.3.dist-info/METADATA +64 -0
  81. jaclang/vendor/interegular-0.3.3.dist-info/RECORD +20 -0
  82. jaclang/vendor/interegular-0.3.3.dist-info/REQUESTED +0 -0
  83. jaclang/vendor/interegular-0.3.3.dist-info/WHEEL +5 -0
  84. jaclang/vendor/interegular-0.3.3.dist-info/top_level.txt +1 -0
  85. {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/METADATA +2 -1
  86. {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/RECORD +88 -43
  87. {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/WHEEL +0 -0
  88. {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/entry_points.txt +0 -0
@@ -484,6 +484,8 @@ class DocIRGenPass(UniPass):
484
484
  indent_parts.append(i.gen.doc_ir)
485
485
  else:
486
486
  parts.append(i.gen.doc_ir)
487
+ if isinstance(i, uni.Token) and i.name == Tok.KW_BY:
488
+ parts.append(self.space())
487
489
  node.gen.doc_ir = self.group(self.concat(parts))
488
490
 
489
491
  def exit_atom_trailer(self, node: uni.AtomTrailer) -> None:
@@ -51,3 +51,6 @@ def test_run {
51
51
  with entry {
52
52
  test_run();
53
53
  }
54
+
55
+
56
+ def generate_joke -> dict[str, str] by llm();
@@ -11,6 +11,7 @@ import jaclang.compiler.unitree as uni
11
11
  from jaclang.compiler.parser import JacParser
12
12
  from jaclang.compiler.passes.main import (
13
13
  Alert,
14
+ BinderPass,
14
15
  CFGBuildPass,
15
16
  DeclImplMatchPass,
16
17
  DefUsePass,
@@ -25,12 +26,14 @@ from jaclang.compiler.passes.main import (
25
26
  SymTabBuildPass,
26
27
  SymTabLinkPass,
27
28
  Transform,
29
+ TypeCheckPass,
28
30
  )
29
31
  from jaclang.compiler.passes.tool import (
30
32
  DocIRGenPass,
31
33
  FuseCommentsPass,
32
34
  JacFormatPass,
33
35
  )
36
+ from jaclang.runtimelib.utils import read_file_with_encoding
34
37
  from jaclang.utils.log import logging
35
38
 
36
39
 
@@ -44,6 +47,9 @@ ir_gen_sched = [
44
47
  CFGBuildPass,
45
48
  InheritancePass,
46
49
  ]
50
+ type_check_sched: list = [
51
+ TypeCheckPass,
52
+ ]
47
53
  py_code_gen = [
48
54
  PyastGenPass,
49
55
  PyJacAstLinkPass,
@@ -73,7 +79,7 @@ class JacProgram:
73
79
  def parse_str(self, source_str: str, file_path: str) -> uni.Module:
74
80
  """Convert a Jac file to an AST."""
75
81
  had_error = False
76
- if file_path.endswith(".py"):
82
+ if file_path.endswith(".py") or file_path.endswith(".pyi"):
77
83
  parsed_ast = py_ast.parse(source_str)
78
84
  py_ast_ret = PyastBuildPass(
79
85
  ir_in=uni.PythonModuleAst(
@@ -100,21 +106,36 @@ class JacProgram:
100
106
  return mod
101
107
 
102
108
  def compile(
103
- 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,
104
116
  ) -> uni.Module:
105
117
  """Convert a Jac file to an AST."""
106
- if not use_str:
107
- with open(file_path, "r", encoding="utf-8") as file:
108
- use_str = file.read()
109
- mod_targ = self.parse_str(use_str, file_path)
118
+ keep_str = use_str or read_file_with_encoding(file_path)
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
114
126
 
115
- def build(self, file_path: str, use_str: str | None = None) -> uni.Module:
127
+ def bind(self, file_path: str, use_str: str | None = None) -> uni.Module:
128
+ """Bind the Jac module."""
129
+ keep_str = use_str or read_file_with_encoding(file_path)
130
+ mod_targ = self.parse_str(keep_str, file_path)
131
+ BinderPass(ir_in=mod_targ, prog=self)
132
+ return mod_targ
133
+
134
+ def build(
135
+ self, file_path: str, use_str: str | None = None, type_check: bool = False
136
+ ) -> uni.Module:
116
137
  """Convert a Jac file to an AST."""
117
- mod_targ = self.compile(file_path, use_str)
138
+ mod_targ = self.compile(file_path, use_str, type_check=type_check)
118
139
  JacImportDepsPass(ir_in=mod_targ, prog=self)
119
140
  for mod in self.mod.hub.values():
120
141
  SymTabLinkPass(ir_in=mod, prog=self)
@@ -141,9 +162,9 @@ class JacProgram:
141
162
  def jac_file_formatter(file_path: str) -> str:
142
163
  """Convert a Jac file to an AST."""
143
164
  prog = JacProgram()
144
- with open(file_path) as file:
145
- source = uni.Source(file.read(), mod_path=file_path)
146
- prse: Transform = JacParser(root_ir=source, prog=prog)
165
+ source_str = read_file_with_encoding(file_path)
166
+ source = uni.Source(source_str, mod_path=file_path)
167
+ prse: Transform = JacParser(root_ir=source, prog=prog)
147
168
  for i in format_sched:
148
169
  prse = i(ir_in=prse.ir_out, prog=prog)
149
170
  prse.errors_had = prog.errors_had
@@ -0,0 +1,32 @@
1
+ """Test jac.lark grammar for Shift/Reduce errors."""
2
+
3
+ import os
4
+
5
+ import jaclang
6
+ from jaclang.utils.test import TestCase
7
+
8
+ try:
9
+ from lark import Lark
10
+ from lark.exceptions import GrammarError
11
+ except ImportError: # pragma: no cover - lark should be installed for tests
12
+ Lark = None # type: ignore
13
+ GrammarError = Exception
14
+
15
+
16
+ class TestJacGrammar(TestCase):
17
+ """Check that jac.lark has no shift/reduce conflicts."""
18
+
19
+ def test_no_shift_reduce_errors(self) -> None:
20
+ """Ensure jac.lark parses with strict mode."""
21
+ if Lark is None:
22
+ self.fail("lark library not available")
23
+
24
+ lark_path = os.path.join(os.path.dirname(jaclang.__file__), "compiler/jac.lark")
25
+ with open(lark_path, "r", encoding="utf-8") as f:
26
+ grammar = f.read()
27
+
28
+ # Lark's strict mode raises GrammarError on conflicts
29
+ try:
30
+ Lark(grammar, parser="lalr", start="start", strict=True)
31
+ except GrammarError as e: # pragma: no cover - fail if conflicts
32
+ self.fail(f"Shift/reduce conflicts detected: {e}")
@@ -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