Typhon-Language 0.1.3__py3-none-any.whl → 0.1.4__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.
Files changed (47) hide show
  1. Typhon/Driver/configs.py +14 -0
  2. Typhon/Driver/debugging.py +148 -5
  3. Typhon/Driver/diagnostic.py +4 -3
  4. Typhon/Driver/language_server.py +25 -0
  5. Typhon/Driver/run.py +1 -1
  6. Typhon/Driver/translate.py +14 -10
  7. Typhon/Driver/utils.py +39 -1
  8. Typhon/Grammar/_typhon_parser.py +2738 -2525
  9. Typhon/Grammar/parser.py +80 -53
  10. Typhon/Grammar/parser_helper.py +68 -87
  11. Typhon/Grammar/syntax_errors.py +31 -21
  12. Typhon/Grammar/token_factory_custom.py +541 -485
  13. Typhon/Grammar/tokenizer_custom.py +52 -0
  14. Typhon/Grammar/typhon_ast.py +372 -44
  15. Typhon/Grammar/typhon_ast_error.py +438 -0
  16. Typhon/LanguageServer/__init__.py +3 -0
  17. Typhon/LanguageServer/client/__init__.py +42 -0
  18. Typhon/LanguageServer/client/pyrefly.py +115 -0
  19. Typhon/LanguageServer/client/pyright.py +173 -0
  20. Typhon/LanguageServer/semantic_tokens.py +446 -0
  21. Typhon/LanguageServer/server.py +376 -0
  22. Typhon/LanguageServer/utils.py +65 -0
  23. Typhon/SourceMap/ast_match_based_map.py +199 -152
  24. Typhon/SourceMap/ast_matching.py +102 -87
  25. Typhon/SourceMap/datatype.py +27 -16
  26. Typhon/SourceMap/defined_name_retrieve.py +145 -0
  27. Typhon/Transform/comprehension_to_function.py +2 -5
  28. Typhon/Transform/const_member_to_final.py +12 -7
  29. Typhon/Transform/forbidden_statements.py +1 -0
  30. Typhon/Transform/optional_operators_to_checked.py +14 -6
  31. Typhon/Transform/scope_check_rename.py +44 -18
  32. Typhon/Transform/type_abbrev_desugar.py +11 -15
  33. Typhon/Transform/type_annotation_check_expand.py +2 -2
  34. Typhon/Transform/utils/imports.py +39 -4
  35. Typhon/Transform/utils/make_class.py +18 -23
  36. Typhon/Transform/visitor.py +25 -0
  37. Typhon/Typing/pyrefly.py +145 -0
  38. Typhon/Typing/pyright.py +2 -4
  39. Typhon/__main__.py +15 -1
  40. {typhon_language-0.1.3.dist-info → typhon_language-0.1.4.dist-info}/METADATA +7 -5
  41. typhon_language-0.1.4.dist-info/RECORD +65 -0
  42. {typhon_language-0.1.3.dist-info → typhon_language-0.1.4.dist-info}/WHEEL +1 -1
  43. typhon_language-0.1.4.dist-info/licenses/LICENSE +201 -0
  44. typhon_language-0.1.3.dist-info/RECORD +0 -53
  45. typhon_language-0.1.3.dist-info/licenses/LICENSE +0 -21
  46. {typhon_language-0.1.3.dist-info → typhon_language-0.1.4.dist-info}/entry_points.txt +0 -0
  47. {typhon_language-0.1.3.dist-info → typhon_language-0.1.4.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,8 @@ from intervaltree import IntervalTree, Interval
5
5
  from typing import Iterable
6
6
  import ast
7
7
 
8
+ from basedpyright.langserver import main
9
+
8
10
 
9
11
  @dataclass(frozen=True, unsafe_hash=True, order=True)
10
12
  class Pos:
@@ -69,7 +71,7 @@ class Pos:
69
71
  )
70
72
 
71
73
 
72
- # Common central datatype for source range
74
+ # Common central datatype for source range. All are 0 based.
73
75
  @dataclass(frozen=True, unsafe_hash=True, order=True)
74
76
  class Range:
75
77
  start: Pos
@@ -98,17 +100,20 @@ class Range:
98
100
  @staticmethod
99
101
  def from_pos_range(pos_range: PosRange) -> "Range":
100
102
  return Range(
101
- start=Pos(line=pos_range["lineno"], column=pos_range["col_offset"]),
102
- end=Pos(line=pos_range["end_lineno"], column=pos_range["end_col_offset"]),
103
+ start=Pos(line=pos_range["lineno"] - 1, column=pos_range["col_offset"]),
104
+ end=Pos(
105
+ line=pos_range["end_lineno"] - 1, column=pos_range["end_col_offset"]
106
+ ),
103
107
  )
104
108
 
105
109
  @staticmethod
106
110
  def from_pos_attr(attr: PosAttributes) -> "Range | None":
111
+ # Python ast position is 1-based for line, 0-based for column
107
112
  if attr["end_lineno"] is None or attr["end_col_offset"] is None:
108
113
  return None
109
114
  return Range(
110
- start=Pos(line=attr["lineno"], column=attr["col_offset"]),
111
- end=Pos(line=attr["end_lineno"], column=attr["end_col_offset"]),
115
+ start=Pos(line=attr["lineno"] - 1, column=attr["col_offset"]),
116
+ end=Pos(line=attr["end_lineno"] - 1, column=attr["end_col_offset"]),
112
117
  )
113
118
 
114
119
  @staticmethod
@@ -126,8 +131,8 @@ class Range:
126
131
  if end_line == start_line and start_column == end_column:
127
132
  end_column += 1 # Ensure non-zero length
128
133
  return Range(
129
- start=Pos(line=start_line, column=start_column),
130
- end=Pos(line=end_line, column=end_column),
134
+ start=Pos(line=start_line - 1, column=start_column),
135
+ end=Pos(line=end_line - 1, column=end_column),
131
136
  )
132
137
 
133
138
  @staticmethod
@@ -143,13 +148,13 @@ class Range:
143
148
 
144
149
  @staticmethod
145
150
  def from_syntax_error(e: SyntaxError) -> "Range":
146
- start_line = e.lineno or 0
151
+ start_line = e.lineno or 1
147
152
  start_column = e.offset or 0
148
153
  end_line = e.end_lineno or start_line
149
154
  end_column = e.end_offset or (start_column + 1)
150
155
  return Range(
151
- start=Pos(line=start_line, column=start_column),
152
- end=Pos(line=end_line, column=end_column),
156
+ start=Pos(line=start_line - 1, column=start_column),
157
+ end=Pos(line=end_line - 1, column=end_column),
153
158
  )
154
159
 
155
160
  @staticmethod
@@ -168,11 +173,10 @@ class Range:
168
173
  return self.of_lines(lines)
169
174
 
170
175
  def of_lines(self, lines: list[str]) -> str:
171
- # Range is 1 based. Convert to 0 based for string slicing.
172
- start_line = self.start.line - 1
173
- end_line = self.end.line - 1
174
- start_column = self.start.column - 1
175
- end_column = self.end.column - 1
176
+ start_line = self.start.line
177
+ end_line = self.end.line
178
+ start_column = self.start.column
179
+ end_column = self.end.column
176
180
 
177
181
  def get_in_line(line: str, start_col: int, end_col: int) -> str:
178
182
  return line[start_col : min(end_col, len(line))]
@@ -190,6 +194,9 @@ class Range:
190
194
  result_lines.append(get_in_line(lines[end_line], 0, end_column))
191
195
  return "\n".join(result_lines)
192
196
 
197
+ def deconstruct_str(self):
198
+ return f"Range(Pos({self.start.line}, {self.start.column}), Pos({self.end.line}, {self.end.column}))"
199
+
193
200
 
194
201
  type RangeInterval[T] = tuple[Range, T]
195
202
 
@@ -247,6 +254,7 @@ class RangeIntervalTree[T]:
247
254
 
248
255
  def minimal_containers(self, range: Range) -> list[RangeInterval[T]]:
249
256
  containers = self._container_intervals(range)
257
+ debug_verbose_print(f"Minimal containers for range {range}: {containers}")
250
258
  if not containers:
251
259
  return []
252
260
  result: list[RangeInterval[T]] = []
@@ -255,10 +263,13 @@ class RangeIntervalTree[T]:
255
263
  is_minimal = True
256
264
  for j, other_interval in enumerate(containers):
257
265
  if (
258
- i != j and interval.contains_interval(other_interval) # type: ignore[misc]
266
+ i != j
267
+ and interval.contains_interval(other_interval) # type: ignore[misc]
268
+ and not interval.range_matches(other_interval) # type: ignore[misc]
259
269
  ):
260
270
  is_minimal = False
261
271
  break
262
272
  if is_minimal:
273
+ debug_verbose_print(f" Minimal container: {interval}")
263
274
  result.append((Range.from_interval(interval), interval.data)) # type: ignore[misc]
264
275
  return result
@@ -0,0 +1,145 @@
1
+ import ast
2
+
3
+ from ..Driver.debugging import debug_verbose_print
4
+ from ..Grammar.typhon_ast import (
5
+ set_defined_name,
6
+ get_pos_attributes,
7
+ set_import_from_names,
8
+ )
9
+
10
+
11
+ # Assume the base source code is in canonical form by ast.unparse
12
+ class _DefinedNameRetriever(ast.NodeVisitor):
13
+ def __init__(self, unparsed_source_code: str):
14
+ self.unparsed_source_code = unparsed_source_code
15
+
16
+ def _visit_defines(
17
+ self,
18
+ node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef,
19
+ column_offset: int,
20
+ ):
21
+ pos = get_pos_attributes(node)
22
+ start_line = pos["lineno"] # decorator is not included in the original position
23
+ start_col = pos["col_offset"] + column_offset
24
+ debug_verbose_print(
25
+ f'Retrieving defined name "{node.name}" for node: {ast.dump(node)} at line {start_line}, col {start_col}'
26
+ )
27
+ name = ast.Name(
28
+ id=node.name,
29
+ ctx=ast.Store(),
30
+ lineno=start_line,
31
+ col_offset=start_col,
32
+ end_lineno=start_line,
33
+ end_col_offset=start_col + len(node.name),
34
+ )
35
+ set_defined_name(node, name)
36
+
37
+ def visit_FunctionDef(self, node: ast.FunctionDef):
38
+ self._visit_defines(node, len("def "))
39
+ self.generic_visit(node)
40
+
41
+ def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef):
42
+ self._visit_defines(node, len("async def "))
43
+ self.generic_visit(node)
44
+
45
+ def visit_ClassDef(self, node: ast.ClassDef):
46
+ self._visit_defines(node, len("class "))
47
+ self.generic_visit(node)
48
+
49
+ def visit_alias(self, node: ast.alias):
50
+ pos = get_pos_attributes(node)
51
+ start_line = pos["lineno"]
52
+ start_col = pos["col_offset"]
53
+ if node.asname:
54
+ if pos["end_col_offset"]:
55
+ start_col = pos["end_col_offset"] - len(node.asname)
56
+ else:
57
+ # Fallback: search 'as' in the source code line
58
+ # TODO: Is this truly necessary? and reliable?
59
+ line_start = self.unparsed_source_code.splitlines()[start_line - 1]
60
+ as_index = line_start.find(" as ", start_col)
61
+ if as_index != -1:
62
+ start_col = as_index + len(" as ")
63
+ name = ast.Name(
64
+ id=node.asname,
65
+ ctx=ast.Store(),
66
+ lineno=start_line,
67
+ col_offset=start_col,
68
+ end_lineno=start_line,
69
+ end_col_offset=start_col + len(node.asname),
70
+ )
71
+ set_defined_name(node, name)
72
+ else:
73
+ # No asname. Stem of imported name is the defined name.
74
+ after_last_dot_index = node.name.rfind(".") + 1
75
+ start_col += after_last_dot_index
76
+ defined_name = node.name[after_last_dot_index:]
77
+ name = ast.Name(
78
+ id=defined_name,
79
+ ctx=ast.Store(),
80
+ lineno=start_line,
81
+ col_offset=start_col,
82
+ end_lineno=start_line,
83
+ end_col_offset=start_col + len(defined_name),
84
+ )
85
+ set_defined_name(node, name)
86
+
87
+ def visit_Attribute(self, node: ast.Attribute):
88
+ pos = get_pos_attributes(node)
89
+ start_line = pos["lineno"]
90
+ end_col = pos["end_col_offset"]
91
+ # The defined name is the attribute name.
92
+ attr_index = (
93
+ end_col - len(node.attr) - 1 if end_col is not None else pos["col_offset"]
94
+ )
95
+ debug_verbose_print(
96
+ f'Retrieving defined name for attribute "{node.attr}" of {ast.dump(node.value)} at line {start_line}, col {attr_index}'
97
+ )
98
+ name = ast.Name(
99
+ id=node.attr,
100
+ ctx=node.ctx,
101
+ lineno=start_line,
102
+ col_offset=attr_index,
103
+ end_lineno=start_line,
104
+ end_col_offset=end_col,
105
+ )
106
+ set_defined_name(node, name)
107
+ self.generic_visit(node)
108
+
109
+ def visit_ImportFrom(self, node: ast.ImportFrom):
110
+ for alias in node.names:
111
+ self.visit(alias)
112
+ column = node.col_offset + len("from ") + node.level * len(".")
113
+ module_names: list[ast.Name] = []
114
+ for mod in node.module.split(".") if node.module else []:
115
+ if mod:
116
+ name = ast.Name(
117
+ id=mod,
118
+ lineno=node.lineno,
119
+ col_offset=column,
120
+ end_lineno=node.lineno,
121
+ end_col_offset=column + len(mod),
122
+ ctx=ast.Load(),
123
+ )
124
+ module_names.append(name)
125
+ column += len(mod) + len(".")
126
+ set_import_from_names(node, module_names)
127
+
128
+ def visit_arg(self, node: ast.arg):
129
+ pos = get_pos_attributes(node)
130
+ start_col = pos["col_offset"]
131
+ name = ast.Name(
132
+ id=node.arg,
133
+ ctx=ast.Store(),
134
+ lineno=pos["lineno"],
135
+ col_offset=start_col,
136
+ end_lineno=pos["end_lineno"],
137
+ end_col_offset=start_col + len(node.arg),
138
+ )
139
+ set_defined_name(node, name)
140
+ self.generic_visit(node)
141
+
142
+
143
+ def defined_name_retrieve(node: ast.AST, unparsed_source_code: str) -> None:
144
+ visitor = _DefinedNameRetriever(unparsed_source_code)
145
+ visitor.visit(node)
@@ -56,12 +56,9 @@ class _Transform(TyphonASTTransformer):
56
56
  return result
57
57
 
58
58
  def visit_ControlComprehension(self, node: ControlComprehension):
59
+ clear_is_control_comprehension(node)
59
60
  return ast.Call(
60
- func=ast.Name(
61
- id=node.id,
62
- ctx=ast.Load(),
63
- **get_pos_attributes(node),
64
- ),
61
+ func=node,
65
62
  args=[],
66
63
  keywords=[],
67
64
  **get_pos_attributes(node),
@@ -4,9 +4,8 @@ from ..Grammar.typhon_ast import (
4
4
  get_pos_attributes,
5
5
  is_let_assign,
6
6
  )
7
- from .visitor import TyphonASTTransformer, flat_append
8
- from .name_generator import get_final_name
9
- from .utils.imports import add_import_for_final
7
+ from .visitor import TyphonASTTransformer
8
+ from .utils.imports import add_import_for_final, get_final
10
9
 
11
10
 
12
11
  class ConstMemberToFinal(TyphonASTTransformer):
@@ -24,7 +23,9 @@ class ConstMemberToFinal(TyphonASTTransformer):
24
23
  target: ast.Name,
25
24
  annotation: ast.expr | None,
26
25
  ):
27
- assert is_decl_assign(node), "Unexpected non-decl assign in class def"
26
+ assert is_decl_assign(node), (
27
+ f"Unexpected non-decl assign in class def: {ast.dump(node)}"
28
+ )
28
29
  if not is_let_assign(node):
29
30
  return self.generic_visit(node)
30
31
  self.changed = True
@@ -32,15 +33,17 @@ class ConstMemberToFinal(TyphonASTTransformer):
32
33
  self.generic_visit(node)
33
34
  if annotation is not None:
34
35
  new_annotation = ast.Subscript(
35
- value=ast.Name(id=get_final_name(), ctx=ast.Load(), **pos),
36
+ value=get_final(ctx=ast.Load(), **pos),
36
37
  slice=annotation,
37
38
  ctx=ast.Load(),
38
39
  **pos,
39
40
  )
40
41
  else:
41
- new_annotation = ast.Name(id=get_final_name(), ctx=ast.Load(), **pos)
42
+ new_annotation = get_final(ctx=ast.Load(), **pos)
42
43
  return ast.AnnAssign(
43
- target=ast.Name(id=target.id, ctx=ast.Store(), **pos),
44
+ target=ast.Name(
45
+ id=target.id, ctx=ast.Store(), **get_pos_attributes(target)
46
+ ),
44
47
  annotation=new_annotation,
45
48
  value=node.value,
46
49
  simple=1,
@@ -73,4 +76,6 @@ def const_member_to_final(module: ast.Module):
73
76
  transformer = ConstMemberToFinal(module)
74
77
  transformer.run()
75
78
  if transformer.changed:
79
+ # If we use add_import_get_final, final import confuses the visitor
80
+ # by pushing the current visiting node.
76
81
  add_import_for_final(module)
@@ -85,6 +85,7 @@ class ForbiddenStatementChecker(TyphonASTVisitor):
85
85
  ast.Import,
86
86
  ast.AnnAssign,
87
87
  ast.Assign,
88
+ ast.Pass,
88
89
  ),
89
90
  )
90
91
  ):
@@ -10,8 +10,10 @@ from ..Grammar.typhon_ast import (
10
10
  is_optional,
11
11
  is_optional_pipe,
12
12
  clear_is_optional,
13
+ get_defined_name,
14
+ maybe_copy_defined_name,
13
15
  )
14
-
16
+ from ..Driver.debugging import debug_verbose_print
15
17
  from .visitor import TyphonASTTransformer
16
18
  from .name_generator import get_unwrap_name, get_unwrap_error_name
17
19
 
@@ -178,13 +180,19 @@ class _OptionalToCheckTransformer(TyphonASTTransformer):
178
180
  if not is_optional(node):
179
181
  return self.generic_visit(node)
180
182
  pos = get_pos_attributes(node)
183
+ debug_verbose_print(
184
+ f"Transforming optional attribute access at line {pos['lineno']}, col {pos['col_offset']} for attribute '{node.attr}' defined name: '{get_defined_name(node)}'"
185
+ )
181
186
  result = self._optional_check_if_exp(
182
187
  node.value,
183
- lambda tmp_name: ast.Attribute(
184
- value=ast.Name(id=tmp_name, ctx=ast.Load()),
185
- attr=node.attr,
186
- ctx=node.ctx,
187
- **pos,
188
+ lambda tmp_name: maybe_copy_defined_name(
189
+ node,
190
+ ast.Attribute(
191
+ value=ast.Name(id=tmp_name, ctx=ast.Load()),
192
+ attr=node.attr,
193
+ ctx=node.ctx,
194
+ **pos,
195
+ ),
188
196
  ),
189
197
  ast.Constant(value=None, **pos),
190
198
  pos,
@@ -45,10 +45,20 @@ class DeclarationContext:
45
45
  @dataclass
46
46
  class SuspendedResolveAccess:
47
47
  name: ast.Name
48
- top_level_dead: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef
48
+ temporally_dead_accessor: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef
49
49
  is_mutation: bool
50
50
  accessor_python_scope: PythonScope
51
51
 
52
+ def __hash__(self) -> int:
53
+ return hash(
54
+ (
55
+ self.name.id,
56
+ id(self.temporally_dead_accessor),
57
+ self.is_mutation,
58
+ id(self.accessor_python_scope),
59
+ )
60
+ )
61
+
52
62
 
53
63
  def get_builtins() -> set[str]:
54
64
  builtin_syms = set(dir(builtins))
@@ -70,7 +80,9 @@ class SymbolScopeVisitor(TyphonASTVisitor):
70
80
  self.non_declaration_assign_context = False
71
81
  self.suspended_symbols: set[str] = set()
72
82
  # suspended_resolves[dead_accessor_name][undeclared_name]
73
- self.suspended_resolves: dict[str, dict[str, list[SuspendedResolveAccess]]] = {}
83
+ # dead_accessor_name: Name of function/class which is in its TDZ.
84
+ # undeclared_name: Name which is undeclared used in dead_accessor.
85
+ self.suspended_resolves: dict[str, dict[str, set[SuspendedResolveAccess]]] = {}
74
86
  self.require_global: dict[str, set[PythonScope]] = {}
75
87
  self.require_nonlocal: dict[str, set[PythonScope]] = {}
76
88
  self.builtins_symbols = get_builtins()
@@ -623,19 +635,26 @@ class SymbolScopeVisitor(TyphonASTVisitor):
623
635
  type SuspendableScope = ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef
624
636
 
625
637
  def add_suspended_resolve(
626
- self, name: ast.Name, top_level_dead: SuspendableScope, is_mutable: bool
638
+ self,
639
+ name: ast.Name,
640
+ temporally_dead_accessor: SuspendableScope,
641
+ is_mutable: bool,
627
642
  ) -> SuspendedResolveAccess:
628
- tdz = SuspendedResolveAccess(
643
+ suspended = SuspendedResolveAccess(
629
644
  name=name,
630
- top_level_dead=top_level_dead,
645
+ temporally_dead_accessor=temporally_dead_accessor,
631
646
  is_mutation=is_mutable,
632
647
  accessor_python_scope=self.get_parent_python_scope(),
633
648
  )
649
+ debug_verbose_print(
650
+ f" Adding suspended access to temporal dead variable '{name.id}' in top-level scope '{temporally_dead_accessor.name}'"
651
+ )
634
652
  self.suspended_symbols.add(name.id)
635
- self.suspended_resolves.setdefault(top_level_dead.name, {}).setdefault(
636
- name.id, []
637
- ).append(tdz)
638
- return tdz
653
+ self.suspended_resolves.setdefault(
654
+ temporally_dead_accessor.name, {}
655
+ ).setdefault(name.id, set()).add(suspended)
656
+ debug_verbose_print(f" Current suspended_resolves: {self.suspended_resolves}")
657
+ return suspended
639
658
 
640
659
  def suspendable_scope(
641
660
  self,
@@ -660,19 +679,23 @@ class SymbolScopeVisitor(TyphonASTVisitor):
660
679
  return self.add_suspended_resolve(name, belong_top_level_scope, is_mutation)
661
680
 
662
681
  # Access to maybe temporally dead variable. return False if access is not valid.
663
- # Copy the dependency of suspended access as current scope's dependency.
664
- def access_temporal_dead(self, name: ast.Name) -> bool:
665
- tdz_depends = self.suspended_resolves.get(name.id, None)
666
- if tdz_depends is None:
682
+ # When temporally dead, copy transitively the dependency of suspended access
683
+ # (if exists) as current scope's dependency.
684
+ def access_maybe_temporal_dead(self, name: ast.Name) -> bool:
685
+ name_depends_but_unresolved = self.suspended_resolves.get(name.id, None)
686
+ if name_depends_but_unresolved is None:
667
687
  return True # Not dead
668
688
  belong_top_level_scope = self.suspendable_scope()
669
689
  if belong_top_level_scope is None:
670
690
  return False # Invalid access
671
- # Copy the dependency to current scope
672
- for _, suspends in tdz_depends.items():
673
- for tdz in suspends:
691
+ # Copy the dependencies (not resolved yet) of name to current scope
692
+ for _, suspended_accesses in name_depends_but_unresolved.items():
693
+ debug_verbose_print(
694
+ f" Copying suspended accesses for '{name.id}': suspends={suspended_accesses}"
695
+ )
696
+ for suspended in suspended_accesses:
674
697
  self.add_suspended_resolve(
675
- tdz.name, belong_top_level_scope, tdz.is_mutation
698
+ suspended.name, belong_top_level_scope, suspended.is_mutation
676
699
  )
677
700
  return True
678
701
 
@@ -796,12 +819,15 @@ class SymbolScopeVisitor(TyphonASTVisitor):
796
819
  if self.is_temporal_dead(node.id):
797
820
  # Accessing to a temporally dead variable
798
821
  debug_verbose_print(f"Temporal dead access to '{node.id}'")
799
- if not self.access_temporal_dead(node):
822
+ if not self.access_maybe_temporal_dead(node):
823
+ debug_verbose_print(f"TDZ violation to '{node.id}'")
800
824
  return self.error_tdz_violation(node)
825
+ debug_verbose_print(f"Accessing variable '{node.id}'")
801
826
  self.access_to_symbol_non_decl(
802
827
  sym, is_mutation=self.non_declaration_assign_context
803
828
  )
804
829
  with self.type_annotation_maybe_in_declaration(node):
830
+ debug_verbose_print(f"Maybe type annotated Name: {ast.dump(node)}")
805
831
  return self.generic_visit(node)
806
832
 
807
833
  def visit_Starred(self, node: ast.Starred):
@@ -12,12 +12,11 @@ from ..Grammar.typhon_ast import (
12
12
  is_typing_expression,
13
13
  is_optional_question,
14
14
  )
15
- from ..Grammar.syntax_errors import (
16
- raise_type_annotation_error,
17
- try_handle_syntax_error_or,
18
- )
19
15
  from .visitor import TyphonASTVisitor, TyphonASTTransformer
20
- from .utils.imports import add_import_for_protocol, get_insert_point_for_class
16
+ from .utils.imports import (
17
+ get_insert_point_for_class,
18
+ get_protocol,
19
+ )
21
20
  from ..Driver.debugging import debug_print, debug_verbose_print
22
21
 
23
22
 
@@ -34,7 +33,7 @@ class _GatherArrowType(TyphonASTVisitor):
34
33
 
35
34
 
36
35
  def _protocol_for_function_type(
37
- func_type: FunctionType, arrow_type_name: str, protocol_name: str
36
+ mod: ast.Module, func_type: FunctionType, arrow_type_name: str
38
37
  ) -> ast.ClassDef:
39
38
  func_type.id = arrow_type_name
40
39
  args = get_args_of_function_type(func_type)
@@ -77,9 +76,7 @@ def _protocol_for_function_type(
77
76
  )
78
77
  protocol_def = ast.ClassDef(
79
78
  name=arrow_type_name,
80
- bases=[
81
- ast.Name(id=protocol_name, ctx=ast.Load()),
82
- ],
79
+ bases=[get_protocol(mod, ctx=ast.Load(), **get_empty_pos_attributes())],
83
80
  keywords=[],
84
81
  body=[func_def],
85
82
  decorator_list=[],
@@ -92,18 +89,18 @@ def _protocol_for_function_type(
92
89
 
93
90
 
94
91
  def _add_protocols(
95
- mod: ast.Module, func_types: list[tuple[FunctionType, str]], protocol_name: str
92
+ mod: ast.Module, func_types: list[tuple[FunctionType, str]]
96
93
  ) -> dict[FunctionType, ast.ClassDef]:
97
94
  result: dict[FunctionType, ast.ClassDef] = {}
95
+ # Ensure protocol is imported before classes.
96
+ get_protocol(mod, ctx=ast.Load(), **get_empty_pos_attributes())
98
97
  # Insert after imports.
99
98
  insert_point = get_insert_point_for_class(mod)
100
99
  for func_type, arrow_type_name in func_types:
101
100
  debug_print(
102
101
  f"Adding protocol for function type: {func_type.__dict__} as {arrow_type_name}"
103
102
  )
104
- protocol_def = _protocol_for_function_type(
105
- func_type, arrow_type_name, protocol_name
106
- )
103
+ protocol_def = _protocol_for_function_type(mod, func_type, arrow_type_name)
107
104
  result[func_type] = protocol_def
108
105
  mod.body.insert(insert_point, protocol_def)
109
106
  insert_point += 1
@@ -193,8 +190,7 @@ def type_abbrev_desugar(mod: ast.Module):
193
190
  gatherer = _GatherArrowType(mod)
194
191
  gatherer.run()
195
192
  if gatherer.func_types:
196
- protocol_name = add_import_for_protocol(mod)
197
- _add_protocols(mod, gatherer.func_types, protocol_name)
193
+ _add_protocols(mod, gatherer.func_types)
198
194
  # Run optional question first, as it is represented as Tuple nodes.
199
195
  _OptionalQuestionTransformer(mod).run()
200
196
  _TupleListTransformer(mod).run()
@@ -13,7 +13,7 @@ from ..Grammar.typhon_ast import (
13
13
  from ..Grammar.syntax_errors import (
14
14
  raise_type_annotation_error,
15
15
  handle_syntax_error,
16
- TyphonSyntaxError,
16
+ TyphonTransformSyntaxError,
17
17
  )
18
18
  from .visitor import TyphonASTTransformer, TyphonASTVisitor, flat_append
19
19
  from ..Driver.debugging import debug_print, debug_verbose_print
@@ -50,7 +50,7 @@ def _expand_target_annotation(
50
50
  result.append(new_assign)
51
51
  copy_is_let_var(orig_node, new_assign)
52
52
  return result
53
- except TyphonSyntaxError as e:
53
+ except TyphonTransformSyntaxError as e:
54
54
  handle_syntax_error(module, e)
55
55
  return []
56
56
 
@@ -1,8 +1,10 @@
1
1
  from ...Grammar.typhon_ast import (
2
+ PosAttributes,
2
3
  add_import_alias_top,
3
4
  is_case_irrefutable,
4
5
  get_pos_attributes,
5
6
  pos_attribute_to_range,
7
+ set_is_internal_name,
6
8
  set_is_var,
7
9
  )
8
10
  from ..name_generator import (
@@ -14,33 +16,66 @@ from ..name_generator import (
14
16
  import ast
15
17
  from contextlib import contextmanager
16
18
  from dataclasses import dataclass
17
- from typing import Protocol, Iterable, Final
19
+ from typing import Protocol, Iterable, Final, Unpack
18
20
 
19
21
 
20
- def add_import_for_protocol(mod: ast.Module):
22
+ def _add_import_for_protocol(mod: ast.Module):
21
23
  name = get_protocol_name()
22
24
  add_import_alias_top(mod, "typing", "Protocol", name)
23
25
  return name
24
26
 
25
27
 
28
+ def get_protocol(
29
+ mod: ast.Module, ctx: ast.expr_context, **kwargs: Unpack[PosAttributes]
30
+ ) -> ast.Name:
31
+ protocol_name = _add_import_for_protocol(mod)
32
+ return set_is_internal_name(ast.Name(id=protocol_name, ctx=ctx, **kwargs))
33
+
34
+
26
35
  def add_import_for_final(mod: ast.Module):
27
36
  name = get_final_name()
28
37
  add_import_alias_top(mod, "typing", "Final", name)
29
38
  return name
30
39
 
31
40
 
32
- def add_import_for_dataclass(mod: ast.Module):
41
+ def get_final(ctx: ast.expr_context, **kwargs: Unpack[PosAttributes]) -> ast.Name:
42
+ final_name = get_final_name()
43
+ return set_is_internal_name(ast.Name(id=final_name, ctx=ctx, **kwargs))
44
+
45
+
46
+ def add_import_get_final(
47
+ mod: ast.Module, ctx: ast.expr_context, **kwargs: Unpack[PosAttributes]
48
+ ) -> ast.Name:
49
+ final_name = add_import_for_final(mod)
50
+ return set_is_internal_name(ast.Name(id=final_name, ctx=ctx, **kwargs))
51
+
52
+
53
+ def _add_import_for_dataclass(mod: ast.Module):
33
54
  name = get_dataclass_name()
34
55
  add_import_alias_top(mod, "dataclasses", "dataclass", name)
35
56
  return name
36
57
 
37
58
 
38
- def add_import_for_runtime_checkable(mod: ast.Module):
59
+ def get_dataclass(
60
+ mod: ast.Module, ctx: ast.expr_context, **kwargs: Unpack[PosAttributes]
61
+ ) -> ast.Name:
62
+ dataclass_name = _add_import_for_dataclass(mod)
63
+ return set_is_internal_name(ast.Name(id=dataclass_name, ctx=ctx, **kwargs))
64
+
65
+
66
+ def _add_import_for_runtime_checkable(mod: ast.Module):
39
67
  name = get_runtime_checkable_name()
40
68
  add_import_alias_top(mod, "typing", "runtime_checkable", name)
41
69
  return name
42
70
 
43
71
 
72
+ def get_runtime_checkable(
73
+ mod: ast.Module, ctx: ast.expr_context, **kwargs: Unpack[PosAttributes]
74
+ ) -> ast.Name:
75
+ runtime_checkable_name = _add_import_for_runtime_checkable(mod)
76
+ return set_is_internal_name(ast.Name(id=runtime_checkable_name, ctx=ctx, **kwargs))
77
+
78
+
44
79
  def get_insert_point_for_class(module: ast.Module) -> int:
45
80
  for index, stmt in enumerate(module.body):
46
81
  if not isinstance(stmt, (ast.Import, ast.ImportFrom)):