jaclang 0.2.4__py3-none-any.whl → 0.3.0__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 (82) hide show
  1. jaclang/__init__.py +9 -3
  2. jaclang/cli/__init__.py +0 -1
  3. jaclang/cli/__jac_gen__/cli.py +6 -6
  4. jaclang/cli/__jac_gen__/cli_impl.py +2 -2
  5. jaclang/cli/__jac_gen__/cmds.py +2 -3
  6. jaclang/cli/__jac_gen__/cmds_impl.py +2 -3
  7. jaclang/cli/cmds.jac +1 -1
  8. jaclang/cli/cmds_impl.jac +2 -3
  9. jaclang/core/__init__.py +5 -11
  10. jaclang/core/__jac_gen__/corelib.py +289 -0
  11. jaclang/core/__jac_gen__/corelib_impl.py +220 -0
  12. jaclang/core/corelib.jac +21 -34
  13. jaclang/core/corelib_impl.jac +317 -0
  14. jaclang/jac/__init__.py +1 -0
  15. jaclang/jac/__jac_gen__/jac_parser.py +2 -2
  16. jaclang/jac/absyntree.py +32 -62
  17. jaclang/jac/constant.py +3 -7
  18. jaclang/jac/importer.py +1 -1
  19. jaclang/jac/parser.py +14 -10
  20. jaclang/jac/passes/main/__init__.py +2 -0
  21. jaclang/jac/passes/main/def_use_pass.py +4 -7
  22. jaclang/jac/passes/main/pyast_gen_pass.py +116 -35
  23. jaclang/jac/passes/main/schedules.py +6 -0
  24. jaclang/jac/passes/main/sym_tab_build_pass.py +40 -19
  25. jaclang/jac/passes/main/tests/test_jac_format_pass.py +22 -4
  26. jaclang/jac/passes/main/tests/test_pyast_gen_pass.py +3 -1
  27. jaclang/jac/passes/main/tests/test_type_check_pass.py +42 -0
  28. jaclang/jac/passes/main/type_check_pass.py +103 -0
  29. jaclang/jac/passes/tool/ast_printer_pass.py +8 -2
  30. jaclang/jac/passes/tool/fuse_comments_pass.py +57 -39
  31. jaclang/jac/passes/tool/jac_formatter_pass.py +419 -192
  32. jaclang/jac/passes/tool/sym_tab_printer_pass.py +10 -93
  33. jaclang/jac/passes/tool/tests/test_ast_print_pass.py +2 -1
  34. jaclang/jac/passes/transform.py +0 -39
  35. jaclang/jac/passes/utils/__init__.py +1 -0
  36. jaclang/jac/passes/utils/mypy_ast_build.py +302 -0
  37. jaclang/jac/plugin/__init__.py +5 -2
  38. jaclang/jac/plugin/default.py +20 -4
  39. jaclang/jac/plugin/feature.py +16 -7
  40. jaclang/jac/plugin/spec.py +34 -6
  41. jaclang/jac/symtable.py +6 -0
  42. jaclang/jac/tests/test_workspace.py +55 -1
  43. jaclang/jac/transpiler.py +4 -9
  44. jaclang/utils/helpers.py +0 -33
  45. jaclang/utils/lang_tools.py +3 -0
  46. jaclang/utils/test.py +3 -1
  47. jaclang/utils/treeprinter.py +171 -0
  48. jaclang/vendor/lark/py.typed +0 -0
  49. jaclang/vendor/mypy/checker.py +19 -12
  50. jaclang/vendor/mypy/checkexpr.py +31 -10
  51. jaclang/vendor/mypy/constraints.py +56 -38
  52. jaclang/vendor/mypy/expandtype.py +1 -0
  53. jaclang/vendor/mypy/meet.py +10 -1
  54. jaclang/vendor/mypy/messages.py +16 -4
  55. jaclang/vendor/mypy/moduleinspect.py +10 -4
  56. jaclang/vendor/mypy/py.typed +1 -0
  57. jaclang/vendor/mypy/semanal.py +18 -17
  58. jaclang/vendor/mypy/semanal_enum.py +7 -4
  59. jaclang/vendor/mypy/semanal_namedtuple.py +11 -1
  60. jaclang/vendor/mypy/semanal_typeddict.py +25 -11
  61. jaclang/vendor/mypy/stubdoc.py +18 -4
  62. jaclang/vendor/mypy/stubgen.py +80 -1
  63. jaclang/vendor/mypy/stubgenc.py +47 -5
  64. jaclang/vendor/mypy/stubtest.py +53 -3
  65. jaclang/vendor/mypy/stubutil.py +9 -9
  66. jaclang/vendor/mypy/test/testipc.py +16 -7
  67. jaclang/vendor/mypy/test/teststubtest.py +20 -2
  68. jaclang/vendor/mypy/types.py +1 -1
  69. jaclang/vendor/mypyc/irbuild/prebuildvisitor.py +2 -1
  70. jaclang/vendor/mypyc/test/test_run.py +2 -4
  71. jaclang/vendor/pluggy/py.typed +0 -0
  72. {jaclang-0.2.4.dist-info → jaclang-0.3.0.dist-info}/METADATA +1 -1
  73. {jaclang-0.2.4.dist-info → jaclang-0.3.0.dist-info}/RECORD +77 -71
  74. {jaclang-0.2.4.dist-info → jaclang-0.3.0.dist-info}/WHEEL +1 -1
  75. {jaclang-0.2.4.dist-info → jaclang-0.3.0.dist-info}/entry_points.txt +3 -0
  76. jaclang/core/arch_impl.jac +0 -131
  77. jaclang/core/element_impl.jac +0 -109
  78. jaclang/core/exec_ctx_impl.jac +0 -14
  79. jaclang/core/memory_impl.jac +0 -57
  80. jaclang/jac/tests/fixtures/__jac_gen__/hello_world.py +0 -5
  81. /jaclang/{jac/tests/fixtures → core}/__jac_gen__/__init__.py +0 -0
  82. {jaclang-0.2.4.dist-info → jaclang-0.3.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  """Jac Language Features."""
2
2
  from __future__ import annotations
3
3
 
4
- from typing import Any, Callable, Optional, Protocol, TypeVar
4
+ from typing import Any, Callable, Optional, Protocol, Type, TypeVar
5
5
 
6
6
  from jaclang.jac.constant import EdgeDir
7
7
 
@@ -10,14 +10,26 @@ import pluggy
10
10
  hookspec = pluggy.HookspecMarker("jac")
11
11
 
12
12
 
13
- class ArchitypeProtocol(Protocol):
13
+ class Architype:
14
14
  """Architype Protocol."""
15
15
 
16
- _jac_: None
16
+ class ArchitypeProtocol(Protocol):
17
+ """Architype Protocol."""
18
+
19
+ _jac_: ArchitypeProtocol
20
+
21
+ def __call__(self, target: Architype) -> None:
22
+ """Call the architype's data spatial behavior."""
23
+ if callable(self._jac_):
24
+ return self._jac_(target)
25
+
26
+
27
+ class AbsRootHook:
28
+ """Abstract Root Node."""
17
29
 
18
30
 
19
31
  T = TypeVar("T")
20
- AT = TypeVar("AT", bound=ArchitypeProtocol)
32
+ AT = TypeVar("AT", bound=Architype)
21
33
 
22
34
 
23
35
  class JacFeatureSpec:
@@ -25,39 +37,45 @@ class JacFeatureSpec:
25
37
 
26
38
  @staticmethod
27
39
  @hookspec(firstresult=True)
28
- def bind_architype(arch: AT) -> None:
40
+ def bind_architype(arch: Type[AT], arch_type: str) -> bool:
29
41
  """Create a new architype."""
42
+ raise NotImplementedError
30
43
 
31
44
  @staticmethod
32
45
  @hookspec(firstresult=True)
33
46
  def make_ds_ability(event: str, trigger: Optional[type]) -> Callable[[type], type]:
34
47
  """Create a new architype."""
48
+ raise NotImplementedError
35
49
 
36
50
  @staticmethod
37
51
  @hookspec(firstresult=True)
38
52
  def elvis(op1: Optional[T], op2: T) -> T: # noqa: ANN401
39
53
  """Jac's elvis operator feature."""
54
+ raise NotImplementedError
40
55
 
41
56
  @staticmethod
42
57
  @hookspec(firstresult=True)
43
58
  def report(expr: Any) -> Any: # noqa: ANN401
44
59
  """Jac's report stmt feature."""
60
+ raise NotImplementedError
45
61
 
46
62
  @staticmethod
47
63
  @hookspec(firstresult=True)
48
64
  def ignore(walker_obj: Any, expr: Any) -> bool: # noqa: ANN401
49
65
  """Jac's ignore stmt feature."""
66
+ raise NotImplementedError
50
67
 
51
68
  @staticmethod
52
69
  @hookspec(firstresult=True)
53
70
  def visit(walker_obj: Any, expr: Any) -> bool: # noqa: ANN401
54
71
  """Jac's visit stmt feature."""
55
- return True
72
+ raise NotImplementedError
56
73
 
57
74
  @staticmethod
58
75
  @hookspec(firstresult=True)
59
76
  def disengage(walker_obj: Any) -> bool: # noqa: ANN401
60
77
  """Jac's disengage stmt feature."""
78
+ raise NotImplementedError
61
79
 
62
80
  @staticmethod
63
81
  @hookspec(firstresult=True)
@@ -65,6 +83,7 @@ class JacFeatureSpec:
65
83
  node_obj: Any, dir: EdgeDir, filter_type: Optional[type] # noqa: ANN401
66
84
  ) -> list[Any]:
67
85
  """Jac's apply_dir stmt feature."""
86
+ raise NotImplementedError
68
87
 
69
88
  @staticmethod
70
89
  @hookspec(firstresult=True)
@@ -73,11 +92,13 @@ class JacFeatureSpec:
73
92
 
74
93
  Note: connect needs to call assign compr with tuple in op
75
94
  """
95
+ raise NotImplementedError
76
96
 
77
97
  @staticmethod
78
98
  @hookspec(firstresult=True)
79
99
  def disconnect(op1: Optional[T], op2: T, op: Any) -> T: # noqa: ANN401
80
100
  """Jac's connect operator feature."""
101
+ raise NotImplementedError
81
102
 
82
103
  @staticmethod
83
104
  @hookspec(firstresult=True)
@@ -85,3 +106,10 @@ class JacFeatureSpec:
85
106
  target: list[T], attr_val: tuple[tuple[str], tuple[Any]]
86
107
  ) -> list[T]:
87
108
  """Jac's assign comprehension feature."""
109
+ raise NotImplementedError
110
+
111
+ @staticmethod
112
+ @hookspec(firstresult=True)
113
+ def get_root() -> Architype:
114
+ """Jac's root getter."""
115
+ raise NotImplementedError
jaclang/jac/symtable.py CHANGED
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
  from enum import Enum
5
5
  from typing import Optional, TYPE_CHECKING
6
6
 
7
+ from jaclang.utils.treeprinter import print_symtab_tree
8
+
7
9
 
8
10
  if TYPE_CHECKING:
9
11
  import jaclang.jac.absyntree as ast
@@ -158,6 +160,10 @@ class SymbolTable:
158
160
  self.kid.append(SymbolTable(name, key_node, self))
159
161
  return self.kid[-1]
160
162
 
163
+ def pp(self, depth: Optional[int] = None) -> str:
164
+ """Pretty print."""
165
+ return print_symtab_tree(root=self, depth=depth)
166
+
161
167
  def __repr__(self) -> str:
162
168
  """Repr."""
163
169
  out = f"{self.name} {super().__repr__()}:\n"
@@ -5,7 +5,7 @@ from jaclang.jac.workspace import Workspace
5
5
  from jaclang.utils.test import TestCase
6
6
 
7
7
 
8
- class TestWrokspace(TestCase):
8
+ class TestWorkspace(TestCase):
9
9
  """Test Jac Workspace."""
10
10
 
11
11
  def test_workspace_basic(self) -> None:
@@ -36,3 +36,57 @@ class TestWrokspace(TestCase):
36
36
  ws = Workspace(path=os.path.join(os.path.dirname(__file__)))
37
37
  key = [i for i in ws.modules.keys() if "fam.jac" in i][0]
38
38
  self.assertGreater(len(ws.get_uses(key)), 5)
39
+
40
+ def test_man_code_dir(self) -> None:
41
+ """Test of circle workspace."""
42
+ loc = os.path.join(os.path.dirname(__file__))
43
+ ws = Workspace(path=loc + "/../../../examples/manual_code")
44
+ key = [i for i in ws.modules.keys() if "circle.jac" in i][0]
45
+ # print(ws.modules[key].ir.sym_tab.pp())
46
+ # for i in ws.get_symbols(key):
47
+ # print(i.decl.pp(depth=2))
48
+ out = ""
49
+ for i in ws.get_uses(key):
50
+ # print(i.pp(depth=2).strip())
51
+ out += i.pp(depth=2)
52
+ for i in [
53
+ "math",
54
+ "calculate_area",
55
+ "RAD",
56
+ "expected_area",
57
+ "Circle",
58
+ "c",
59
+ "ShapeType",
60
+ "float",
61
+ "radius",
62
+ "CIRCLE",
63
+ "Shape",
64
+ "__init__",
65
+ "print",
66
+ ]:
67
+ self.assertIn(i, out)
68
+
69
+ # def test_decl_impl(self) -> None:
70
+ # """Test of circle workspace."""
71
+ # loc = os.path.join(os.path.dirname(__file__))
72
+ # ws = Workspace(path=loc + "/../../../examples/manual_code")
73
+ # key = [i for i in ws.modules.keys() if "circle_clean.jac" in i][0]
74
+ # out = ""
75
+ # for i in ws.get_uses(key):
76
+ # out += i.pp(depth=2)
77
+ # for i in [
78
+ # "math",
79
+ # "calculate_area",
80
+ # "RAD",
81
+ # "expected_area",
82
+ # "Circle",
83
+ # "c",
84
+ # "ShapeType",
85
+ # "float",
86
+ # "radius",
87
+ # "CIRCLE",
88
+ # "Shape",
89
+ # "__init__",
90
+ # "print",
91
+ # ]:
92
+ # self.assertIn(i, out)
jaclang/jac/transpiler.py CHANGED
@@ -4,11 +4,7 @@ from typing import Optional, Type
4
4
  import jaclang.jac.absyntree as ast
5
5
  from jaclang.jac.parser import JacParser
6
6
  from jaclang.jac.passes import Pass
7
- from jaclang.jac.passes.main import (
8
- PyOutPass,
9
- PyastGenPass,
10
- pass_schedule,
11
- )
7
+ from jaclang.jac.passes.main import PyOutPass, pass_schedule
12
8
  from jaclang.jac.passes.tool import JacFormatPass
13
9
  from jaclang.jac.passes.tool.schedules import format_pass
14
10
  from jaclang.jac.passes.transform import Alert
@@ -18,7 +14,6 @@ def transpile_jac(file_path: str) -> list[Alert]:
18
14
  """Transpiler Jac file and return python code as string."""
19
15
  code = jac_file_to_pass(
20
16
  file_path=file_path,
21
- target=PyastGenPass,
22
17
  schedule=pass_schedule,
23
18
  )
24
19
  if isinstance(code.ir, ast.Module) and not code.errors_had:
@@ -30,7 +25,7 @@ def transpile_jac(file_path: str) -> list[Alert]:
30
25
 
31
26
  def jac_file_to_pass(
32
27
  file_path: str,
33
- target: Optional[Type[Pass]] = PyastGenPass,
28
+ target: Optional[Type[Pass]] = None,
34
29
  schedule: list[Type[Pass]] = pass_schedule,
35
30
  ) -> Pass:
36
31
  """Convert a Jac file to an AST."""
@@ -46,7 +41,7 @@ def jac_file_to_pass(
46
41
  def jac_str_to_pass(
47
42
  jac_str: str,
48
43
  file_path: str,
49
- target: Optional[Type[Pass]] = PyastGenPass,
44
+ target: Optional[Type[Pass]] = None,
50
45
  schedule: list[Type[Pass]] = pass_schedule,
51
46
  ) -> Pass:
52
47
  """Convert a Jac file to an AST."""
@@ -64,7 +59,7 @@ def jac_str_to_pass(
64
59
 
65
60
  def jac_pass_to_pass(
66
61
  in_pass: Pass,
67
- target: Optional[Type[Pass]] = PyastGenPass,
62
+ target: Optional[Type[Pass]] = None,
68
63
  schedule: list[Type[Pass]] = pass_schedule,
69
64
  ) -> Pass:
70
65
  """Convert a Jac file to an AST."""
jaclang/utils/helpers.py CHANGED
@@ -1,11 +1,9 @@
1
1
  """Utility functions and classes for Jac compilation toolchain."""
2
2
  import re
3
3
  import textwrap
4
- import traceback
5
4
 
6
5
 
7
6
  import jaclang.jac.absyntree as ast
8
- from jaclang.jac.constant import Constants as Con, Values as Val
9
7
 
10
8
 
11
9
  def pascal_to_snake(pascal_string: str) -> str:
@@ -44,37 +42,6 @@ def dedent_code_block(code: str) -> str:
44
42
  return textwrap.dedent(code)
45
43
 
46
44
 
47
- def handle_jac_error(code_string: str, e: Exception, tb: traceback.StackSummary) -> str:
48
- """Handle Jac Error."""
49
- except_line = e.end_lineno if isinstance(e, SyntaxError) else list(tb)[-1].lineno
50
- if not isinstance(except_line, int) or except_line == 0:
51
- return ""
52
- py_error_region = clip_code_section(
53
- add_line_numbers(code_string), except_line, Val.JAC_ERROR_LINE_RANGE
54
- )
55
- try:
56
- jac_err_line = int(code_string.splitlines()[except_line - 1].split()[-1])
57
- mod_index = int(code_string.splitlines()[except_line - 1].split()[-2])
58
- mod_paths = code_string.split(Con.JAC_DEBUG_SPLITTER)[1].strip().splitlines()
59
- target_mod = mod_paths[mod_index]
60
- with open(target_mod, "r") as file:
61
- jac_code_string = file.read()
62
- jac_error_region = clip_code_section(
63
- add_line_numbers(jac_code_string), jac_err_line, Val.JAC_ERROR_LINE_RANGE
64
- )
65
- target_mod = f"JacCode File: {target_mod}:{jac_err_line}"
66
- except Exception as e:
67
- jac_error_region = str(e)
68
- target_mod = ""
69
- snippet = (
70
- f"{Con.JAC_ERROR_PREAMBLE}\n"
71
- f"{target_mod}\n"
72
- f"JacCode Snippet:\n{jac_error_region}\n"
73
- f"PyCode Snippet:\n{py_error_region}\n"
74
- )
75
- return snippet
76
-
77
-
78
45
  def get_ast_nodes_as_snake_case() -> list[str]:
79
46
  """Get all AST nodes as snake case."""
80
47
  import inspect
@@ -191,6 +191,9 @@ class AstTool:
191
191
  arrow = "-.->" if "Optional" in kid.typ else "-->"
192
192
  typ = (
193
193
  kid.typ.replace("Optional[", "")
194
+ .replace("SubNodeList[", "")
195
+ .replace("SubTag[", "")
196
+ .replace("Sequence[", "")
194
197
  .replace("]", "")
195
198
  .replace("|", ",")
196
199
  .replace("list[", "list - ")
jaclang/utils/test.py CHANGED
@@ -65,7 +65,9 @@ class TestCaseMicroSuite(ABC, TestCase):
65
65
  """Attach micro tests."""
66
66
  for filename in [
67
67
  os.path.normpath(os.path.join(root, name))
68
- for root, _, files in os.walk(os.path.dirname(jaclang.__file__))
68
+ for root, _, files in os.walk(
69
+ os.path.dirname(os.path.dirname(jaclang.__file__))
70
+ )
69
71
  for name in files
70
72
  if name.endswith(".jac") and not name.startswith("err")
71
73
  ]:
@@ -0,0 +1,171 @@
1
+ """Tree Printing Helpers for Jac."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Optional, TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from jaclang.jac.absyntree import AstNode, SymbolTable
8
+
9
+
10
+ def print_ast_tree(
11
+ root: AstNode,
12
+ marker: str = "+-- ",
13
+ level_markers: Optional[list[bool]] = None,
14
+ output_file: Optional[str] = None,
15
+ max_depth: Optional[int] = None,
16
+ ) -> str:
17
+ """Recursively print ast tree."""
18
+ from jaclang.jac.absyntree import AstSymbolNode, Token
19
+
20
+ def __node_repr_in_tree(node: AstNode) -> str:
21
+ if isinstance(node, Token):
22
+ return f"{node.__class__.__name__} - {node.value}"
23
+ elif isinstance(node, AstSymbolNode):
24
+ return f"{node.__class__.__name__} - {node.sym_name}"
25
+ else:
26
+ return f"{node.__class__.__name__}"
27
+
28
+ if root is None or (
29
+ max_depth is not None and len(level_markers or []) >= max_depth
30
+ ):
31
+ return ""
32
+
33
+ empty_str = " " * len(marker)
34
+ connection_str = "|" + empty_str[:-1]
35
+ if not level_markers:
36
+ level_markers = []
37
+ level = len(level_markers) # recursion level
38
+
39
+ def mapper(draw: bool) -> str:
40
+ return connection_str if draw else empty_str
41
+
42
+ markers = "".join(map(mapper, level_markers[:-1]))
43
+ markers += marker if level > 0 else ""
44
+
45
+ tree_str = f"{root.loc}\t{markers}{__node_repr_in_tree(root)}\n"
46
+
47
+ for i, child in enumerate(root.kid):
48
+ is_last = i == len(root.kid) - 1
49
+ tree_str += print_ast_tree(
50
+ child, marker, [*level_markers, not is_last], output_file, max_depth
51
+ )
52
+
53
+ # Write to file only at the top level call
54
+ if output_file and level == 0:
55
+ with open(output_file, "w") as f:
56
+ f.write(tree_str)
57
+
58
+ return tree_str
59
+
60
+
61
+ class SymbolTree:
62
+ """Symbol Tree Node."""
63
+
64
+ def __init__(
65
+ self,
66
+ node_name: str,
67
+ parent: Optional[SymbolTree] = None,
68
+ children: Optional[list[SymbolTree]] = None,
69
+ ) -> None:
70
+ """Initialize Symbol Tree Node."""
71
+ self.parent = parent
72
+ self.kid = children if children is not None else []
73
+ self.name = node_name
74
+
75
+ @property
76
+ def parent(self) -> Optional[SymbolTree]:
77
+ """Get parent node."""
78
+ return self.__parent
79
+
80
+ @parent.setter
81
+ def parent(self, parent_node: Optional[SymbolTree]) -> None:
82
+ """Set parent node."""
83
+ if parent_node:
84
+ self.__parent = parent_node
85
+ parent_node.kid.append(self)
86
+
87
+
88
+ def _build_symbol_tree_common(
89
+ node: SymbolTable, parent_node: Optional[SymbolTree] = None
90
+ ) -> SymbolTree:
91
+ root = SymbolTree(
92
+ node_name=f"SymTable::{node.owner.__class__.__name__}({node.name})",
93
+ parent=parent_node,
94
+ )
95
+ symbols = SymbolTree(node_name="Symbols", parent=root)
96
+ children = SymbolTree(node_name="Sub Tables", parent=root)
97
+
98
+ for sym in node.tab.values():
99
+ symbol_node = SymbolTree(node_name=f"{sym.sym_name}", parent=symbols)
100
+ SymbolTree(node_name=f"{sym.access} {sym.sym_type}", parent=symbol_node)
101
+
102
+ if sym.decl and sym.decl.loc.first_line > 0:
103
+ SymbolTree(
104
+ node_name=f"decl: line {sym.decl.loc.first_line}, col {sym.decl.loc.col_start}",
105
+ parent=symbol_node,
106
+ )
107
+ defn = SymbolTree(node_name="defn", parent=symbol_node)
108
+ [
109
+ SymbolTree(
110
+ node_name=f"line {n.loc.first_line}, col {n.loc.col_start}",
111
+ parent=defn,
112
+ )
113
+ for n in sym.defn
114
+ ]
115
+
116
+ for k in node.kid:
117
+ _build_symbol_tree_common(k, children)
118
+ return root
119
+
120
+
121
+ def print_symtab_tree(
122
+ root: SymbolTable,
123
+ marker: str = "+-- ",
124
+ level_markers: Optional[list[bool]] = None,
125
+ output_file: Optional[str] = None,
126
+ depth: Optional[int] = None,
127
+ ) -> str:
128
+ """Recursively print symbol table tree."""
129
+ return get_symtab_tree_str(
130
+ _build_symbol_tree_common(root),
131
+ marker,
132
+ level_markers,
133
+ output_file,
134
+ depth,
135
+ )
136
+
137
+
138
+ def get_symtab_tree_str(
139
+ root: SymbolTree,
140
+ marker: str = "+-- ",
141
+ level_markers: Optional[list[bool]] = None,
142
+ output_file: Optional[str] = None,
143
+ depth: Optional[int] = None,
144
+ ) -> str:
145
+ """Recursively print symbol table tree."""
146
+ if root is None or depth == 0:
147
+ return ""
148
+
149
+ level_markers = level_markers or []
150
+ markers = "".join(
151
+ [
152
+ "|" + " " * (len(marker) - 1) if draw else " " * len(marker)
153
+ for draw in level_markers[:-1]
154
+ ]
155
+ ) + (marker if level_markers else "")
156
+ line = f"{markers}{root.name}\n"
157
+
158
+ if output_file:
159
+ with open(output_file, "a+") as f:
160
+ f.write(line)
161
+
162
+ return line + "".join(
163
+ get_symtab_tree_str(
164
+ child,
165
+ marker,
166
+ level_markers + [i < len(root.kid) - 1],
167
+ output_file,
168
+ None if depth is None else depth - 1,
169
+ )
170
+ for i, child in enumerate(root.kid)
171
+ )
File without changes
@@ -6106,24 +6106,31 @@ class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
6106
6106
  if node.arg_kinds[0] != nodes.ARG_POS:
6107
6107
  # the first argument might be used as a kwarg
6108
6108
  called_type = get_proper_type(self.lookup_type(node.callee))
6109
- assert isinstance(called_type, (CallableType, Overloaded))
6109
+
6110
+ # TODO: there are some more cases in check_call() to handle.
6111
+ if isinstance(called_type, Instance):
6112
+ call = find_member(
6113
+ "__call__", called_type, called_type, is_operator=True
6114
+ )
6115
+ if call is not None:
6116
+ called_type = get_proper_type(call)
6110
6117
 
6111
6118
  # *assuming* the overloaded function is correct, there's a couple cases:
6112
6119
  # 1) The first argument has different names, but is pos-only. We don't
6113
6120
  # care about this case, the argument must be passed positionally.
6114
6121
  # 2) The first argument allows keyword reference, therefore must be the
6115
6122
  # same between overloads.
6116
- name = called_type.items[0].arg_names[0]
6117
-
6118
- if name in node.arg_names:
6119
- idx = node.arg_names.index(name)
6120
- # we want the idx-th variable to be narrowed
6121
- expr = collapse_walrus(node.args[idx])
6122
- else:
6123
- self.fail(
6124
- message_registry.TYPE_GUARD_POS_ARG_REQUIRED, node
6125
- )
6126
- return {}, {}
6123
+ if isinstance(called_type, (CallableType, Overloaded)):
6124
+ name = called_type.items[0].arg_names[0]
6125
+ if name in node.arg_names:
6126
+ idx = node.arg_names.index(name)
6127
+ # we want the idx-th variable to be narrowed
6128
+ expr = collapse_walrus(node.args[idx])
6129
+ else:
6130
+ self.fail(
6131
+ message_registry.TYPE_GUARD_POS_ARG_REQUIRED, node
6132
+ )
6133
+ return {}, {}
6127
6134
  if literal(expr) == LITERAL_TYPE:
6128
6135
  # Note: we wrap the target type, so that we can special case later.
6129
6136
  # Namely, for isinstance() we use a normal meet, while TypeGuard is
@@ -3870,8 +3870,9 @@ class ExpressionChecker(ExpressionVisitor[Type]):
3870
3870
  self,
3871
3871
  left: Type,
3872
3872
  right: Type,
3873
- original_container: Type | None = None,
3874
3873
  *,
3874
+ original_container: Type | None = None,
3875
+ seen_types: set[tuple[Type, Type]] | None = None,
3875
3876
  prefer_literal: bool = True,
3876
3877
  ) -> bool:
3877
3878
  """Check for dangerous non-overlapping comparisons like 42 == 'no'.
@@ -3892,6 +3893,12 @@ class ExpressionChecker(ExpressionVisitor[Type]):
3892
3893
  if not self.chk.options.strict_equality:
3893
3894
  return False
3894
3895
 
3896
+ if seen_types is None:
3897
+ seen_types = set()
3898
+ if (left, right) in seen_types:
3899
+ return False
3900
+ seen_types.add((left, right))
3901
+
3895
3902
  left, right = get_proper_types((left, right))
3896
3903
 
3897
3904
  # We suppress the error if there is a custom __eq__() method on either
@@ -3949,7 +3956,9 @@ class ExpressionChecker(ExpressionVisitor[Type]):
3949
3956
  abstract_set = self.chk.lookup_typeinfo("typing.AbstractSet")
3950
3957
  left = map_instance_to_supertype(left, abstract_set)
3951
3958
  right = map_instance_to_supertype(right, abstract_set)
3952
- return self.dangerous_comparison(left.args[0], right.args[0])
3959
+ return self.dangerous_comparison(
3960
+ left.args[0], right.args[0], seen_types=seen_types
3961
+ )
3953
3962
  elif left.type.has_base("typing.Mapping") and right.type.has_base(
3954
3963
  "typing.Mapping"
3955
3964
  ):
@@ -3958,13 +3967,17 @@ class ExpressionChecker(ExpressionVisitor[Type]):
3958
3967
  left = map_instance_to_supertype(left, abstract_map)
3959
3968
  right = map_instance_to_supertype(right, abstract_map)
3960
3969
  return self.dangerous_comparison(
3961
- left.args[0], right.args[0]
3962
- ) or self.dangerous_comparison(left.args[1], right.args[1])
3970
+ left.args[0], right.args[0], seen_types=seen_types
3971
+ ) or self.dangerous_comparison(
3972
+ left.args[1], right.args[1], seen_types=seen_types
3973
+ )
3963
3974
  elif (
3964
3975
  left_name in ("builtins.list", "builtins.tuple")
3965
3976
  and right_name == left_name
3966
3977
  ):
3967
- return self.dangerous_comparison(left.args[0], right.args[0])
3978
+ return self.dangerous_comparison(
3979
+ left.args[0], right.args[0], seen_types=seen_types
3980
+ )
3968
3981
  elif left_name in OVERLAPPING_BYTES_ALLOWLIST and right_name in (
3969
3982
  OVERLAPPING_BYTES_ALLOWLIST
3970
3983
  ):
@@ -6624,11 +6637,16 @@ class PolyTranslator(TypeTranslator):
6624
6637
  See docstring for apply_poly() for details.
6625
6638
  """
6626
6639
 
6627
- def __init__(self, poly_tvars: Sequence[TypeVarLikeType]) -> None:
6640
+ def __init__(
6641
+ self,
6642
+ poly_tvars: Iterable[TypeVarLikeType],
6643
+ bound_tvars: frozenset[TypeVarLikeType] = frozenset(),
6644
+ seen_aliases: frozenset[TypeInfo] = frozenset(),
6645
+ ) -> None:
6628
6646
  self.poly_tvars = set(poly_tvars)
6629
6647
  # This is a simplified version of TypeVarScope used during semantic analysis.
6630
- self.bound_tvars: set[TypeVarLikeType] = set()
6631
- self.seen_aliases: set[TypeInfo] = set()
6648
+ self.bound_tvars = bound_tvars
6649
+ self.seen_aliases = seen_aliases
6632
6650
 
6633
6651
  def collect_vars(self, t: CallableType | Parameters) -> list[TypeVarLikeType]:
6634
6652
  found_vars = []
@@ -6706,10 +6724,13 @@ class PolyTranslator(TypeTranslator):
6706
6724
  if t.args and t.type.is_protocol and t.type.protocol_members == ["__call__"]:
6707
6725
  if t.type in self.seen_aliases:
6708
6726
  raise PolyTranslationError()
6709
- self.seen_aliases.add(t.type)
6710
6727
  call = find_member("__call__", t, t, is_operator=True)
6711
6728
  assert call is not None
6712
- return call.accept(self)
6729
+ return call.accept(
6730
+ PolyTranslator(
6731
+ self.poly_tvars, self.bound_tvars, self.seen_aliases | {t.type}
6732
+ )
6733
+ )
6713
6734
  return super().visit_instance(t)
6714
6735
 
6715
6736