Typhon-Language 0.1.2__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 (57) 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 +16 -11
  7. Typhon/Driver/utils.py +39 -1
  8. Typhon/Grammar/_typhon_parser.py +2920 -2718
  9. Typhon/Grammar/parser.py +80 -53
  10. Typhon/Grammar/parser_helper.py +68 -87
  11. Typhon/Grammar/syntax_errors.py +41 -20
  12. Typhon/Grammar/token_factory_custom.py +541 -485
  13. Typhon/Grammar/tokenizer_custom.py +52 -0
  14. Typhon/Grammar/typhon_ast.py +754 -76
  15. Typhon/Grammar/typhon_ast_error.py +438 -0
  16. Typhon/Grammar/unparse_custom.py +25 -0
  17. Typhon/LanguageServer/__init__.py +3 -0
  18. Typhon/LanguageServer/client/__init__.py +42 -0
  19. Typhon/LanguageServer/client/pyrefly.py +115 -0
  20. Typhon/LanguageServer/client/pyright.py +173 -0
  21. Typhon/LanguageServer/semantic_tokens.py +446 -0
  22. Typhon/LanguageServer/server.py +376 -0
  23. Typhon/LanguageServer/utils.py +65 -0
  24. Typhon/SourceMap/ast_match_based_map.py +199 -152
  25. Typhon/SourceMap/ast_matching.py +102 -87
  26. Typhon/SourceMap/datatype.py +275 -264
  27. Typhon/SourceMap/defined_name_retrieve.py +145 -0
  28. Typhon/Transform/comprehension_to_function.py +2 -5
  29. Typhon/Transform/const_member_to_final.py +12 -7
  30. Typhon/Transform/extended_patterns.py +139 -0
  31. Typhon/Transform/forbidden_statements.py +25 -0
  32. Typhon/Transform/if_while_let.py +122 -11
  33. Typhon/Transform/inline_statement_block_capture.py +22 -15
  34. Typhon/Transform/optional_operators_to_checked.py +14 -6
  35. Typhon/Transform/placeholder_to_function.py +0 -1
  36. Typhon/Transform/record_to_dataclass.py +22 -238
  37. Typhon/Transform/scope_check_rename.py +109 -29
  38. Typhon/Transform/transform.py +16 -12
  39. Typhon/Transform/type_abbrev_desugar.py +11 -15
  40. Typhon/Transform/type_annotation_check_expand.py +2 -2
  41. Typhon/Transform/utils/__init__.py +0 -0
  42. Typhon/Transform/utils/imports.py +83 -0
  43. Typhon/Transform/{utils.py → utils/jump_away.py} +2 -38
  44. Typhon/Transform/utils/make_class.py +135 -0
  45. Typhon/Transform/visitor.py +25 -0
  46. Typhon/Typing/pyrefly.py +145 -0
  47. Typhon/Typing/pyright.py +141 -144
  48. Typhon/Typing/result_diagnostic.py +1 -1
  49. Typhon/__main__.py +15 -1
  50. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/METADATA +13 -6
  51. typhon_language-0.1.4.dist-info/RECORD +65 -0
  52. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/WHEEL +1 -1
  53. typhon_language-0.1.4.dist-info/licenses/LICENSE +201 -0
  54. typhon_language-0.1.2.dist-info/RECORD +0 -48
  55. typhon_language-0.1.2.dist-info/licenses/LICENSE +0 -21
  56. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/entry_points.txt +0 -0
  57. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/top_level.txt +0 -0
@@ -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,
@@ -93,7 +93,6 @@ class _Transform(TyphonASTTransformer):
93
93
  return lambda_expr
94
94
 
95
95
  def visit(self, node: ast.AST):
96
- debug_print(f"placeholder_to_function _transform visit: {ast.dump(node)}")
97
96
  # Transform the expression transformed into function.
98
97
  if isinstance(node, ast.expr):
99
98
  if node in self.bound_exprs_to_placeholders:
@@ -10,25 +10,21 @@ from ..Grammar.typhon_ast import (
10
10
  set_is_var,
11
11
  is_record_literal,
12
12
  is_record_type,
13
- is_record_pattern,
13
+ is_attributes_pattern,
14
14
  )
15
15
  from .visitor import TyphonASTVisitor, TyphonASTTransformer, flat_append
16
- from .utils import (
17
- add_import_for_dataclass,
18
- add_import_for_protocol,
19
- add_import_for_runtime_checkable,
20
- add_import_for_final,
16
+ from .utils.imports import (
21
17
  get_insert_point_for_class,
22
18
  )
19
+ from .utils.make_class import (
20
+ make_dataclass_protocol_definition,
21
+ make_dataclass_definition,
22
+ NameAndAnnotation,
23
+ )
23
24
  from dataclasses import dataclass
24
25
  from typing import Protocol, Iterable, Final
25
26
 
26
27
 
27
- class NameAndAnnotation(Protocol):
28
- name: Final[ast.Name]
29
- annotation: Final[ast.expr]
30
-
31
-
32
28
  @dataclass
33
29
  class RecordFieldInfo:
34
30
  name: ast.Name
@@ -59,31 +55,14 @@ class RecordTypeInfo:
59
55
  type_variables: list[str]
60
56
 
61
57
 
62
- @dataclass
63
- class RecordPatternFieldInfo:
64
- name: ast.Name
65
- annotation: ast.expr
66
-
67
-
68
- @dataclass
69
- class RecordPatternInfo:
70
- class_name: str
71
- pattern: ast.MatchClass
72
- record: ast.Name
73
- type_variables: list[str]
74
- fields: list[RecordPatternFieldInfo]
75
-
76
-
77
58
  class _GatherRecords(TyphonASTVisitor):
78
59
  records: list[RecordInfo]
79
60
  record_types: list[RecordTypeInfo]
80
- record_patterns: list[RecordPatternInfo]
81
61
 
82
62
  def __init__(self, module: ast.Module):
83
63
  super().__init__(module)
84
64
  self.records = []
85
65
  self.record_types = []
86
- self.record_patterns = []
87
66
 
88
67
  def _visit_RecordLiteral(self, node: RecordLiteral):
89
68
  fields = get_record_literal_fields(node)
@@ -107,7 +86,7 @@ class _GatherRecords(TyphonASTVisitor):
107
86
  type_fields = get_record_type_fields(node)
108
87
  if not type_fields:
109
88
  return
110
- type_vars = []
89
+ type_vars: list[str] = []
111
90
  field_infos: list[RecordTypeFieldInfo] = []
112
91
  for name, annotation in type_fields:
113
92
  # Always create a new type variable for each field so that scoped type
@@ -134,100 +113,9 @@ class _GatherRecords(TyphonASTVisitor):
134
113
  self._visit_RecordType(node)
135
114
  return self.generic_visit(node)
136
115
 
137
- def visit_MatchClass(self, node: ast.MatchClass):
138
- if isinstance(node.cls, ast.Name) and is_record_pattern(node.cls):
139
- record_cls = node.cls
140
- type_vars = []
141
- fields: list[RecordPatternFieldInfo] = []
142
- # Gather the member names from keyword patterns.
143
- for kwd_name in node.kwd_attrs:
144
- type_var = self.new_typevar_name(kwd_name)
145
- type_vars.append(type_var)
146
- fields.append(
147
- RecordPatternFieldInfo(
148
- name=ast.Name(
149
- id=kwd_name,
150
- ctx=ast.Load(),
151
- **get_pos_attributes(node.cls),
152
- ),
153
- annotation=ast.Name(
154
- id=type_var, ctx=ast.Load(), **get_pos_attributes(node.cls)
155
- ),
156
- )
157
- )
158
- self.record_patterns.append(
159
- RecordPatternInfo(
160
- self.new_class_name(""),
161
- node,
162
- record_cls,
163
- type_vars,
164
- fields,
165
- )
166
- )
167
- return self.generic_visit(node)
168
-
169
-
170
- def _dataclass_decorator(
171
- dataclass_imported_name: str, repr: bool, pos: PosAttributes
172
- ) -> ast.expr:
173
- return ast.Call(
174
- func=ast.Name(id=dataclass_imported_name, ctx=ast.Load(), **pos),
175
- args=[],
176
- keywords=[
177
- ast.keyword(arg="frozen", value=ast.Constant(value=True)),
178
- ast.keyword(arg="repr", value=ast.Constant(value=repr)),
179
- ast.keyword(arg="unsafe_hash", value=ast.Constant(value=True)),
180
- ast.keyword(arg="kw_only", value=ast.Constant(value=True)),
181
- ],
182
- )
183
-
184
-
185
- def _class_contents_for_fields(
186
- fields: Iterable[NameAndAnnotation],
187
- final_imported_name: str,
188
- ) -> list[ast.stmt]:
189
- result: list[ast.stmt] = []
190
- for field in fields:
191
- name = field.name
192
- if field.annotation:
193
- annotation = ast.Subscript(
194
- value=ast.Name(
195
- id=final_imported_name, ctx=ast.Load(), **get_pos_attributes(name)
196
- ),
197
- slice=field.annotation,
198
- ctx=ast.Load(),
199
- **get_pos_attributes(name),
200
- )
201
- else:
202
- annotation = ast.Name(
203
- id=final_imported_name, ctx=ast.Load(), **get_pos_attributes(name)
204
- )
205
- ann_assign = ast.AnnAssign(
206
- target=ast.Name(id=name.id, ctx=ast.Store(), **get_pos_attributes(name)),
207
- annotation=annotation,
208
- value=None,
209
- simple=1,
210
- **get_pos_attributes(name),
211
- )
212
- # "var" because dataclass is frozen, "Final" is not required.
213
- set_is_var(ann_assign)
214
- result.append(ann_assign)
215
- return result
216
-
217
-
218
- def _type_vars_for_fields(
219
- record: ast.Name, type_variables: list[str]
220
- ) -> list[ast.type_param]:
221
- type_params: list[ast.type_param] = []
222
- for tv in type_variables:
223
- type_params.append(
224
- ast.TypeVar(name=tv, **pos_attribute_to_range(get_pos_attributes(record)))
225
- )
226
- return type_params
227
-
228
116
 
229
117
  def _add_repr_to_dataclass(
230
- class_def: ast.ClassDef, record: ast.Name, fields: list[RecordFieldInfo]
118
+ class_def: ast.ClassDef, record: ast.Name, fields: Iterable[NameAndAnnotation]
231
119
  ):
232
120
  repr_values: list[ast.expr] = []
233
121
  for i, field in enumerate(fields):
@@ -275,65 +163,6 @@ def _add_repr_to_dataclass(
275
163
  class_def.body.append(repr_func_def)
276
164
 
277
165
 
278
- def _make_dataclass_definition(
279
- info: RecordInfo,
280
- dataclass_imported_name: str,
281
- final_imported_name: str,
282
- ) -> ast.ClassDef:
283
- result = ast.ClassDef(
284
- name=info.class_name,
285
- type_params=_type_vars_for_fields(info.record, info.type_variables),
286
- bases=[],
287
- keywords=[],
288
- body=_class_contents_for_fields(info.fields, final_imported_name),
289
- decorator_list=[
290
- _dataclass_decorator(
291
- dataclass_imported_name, repr=False, pos=get_pos_attributes(info.record)
292
- )
293
- ],
294
- **get_pos_attributes(info.record),
295
- )
296
- _add_repr_to_dataclass(result, info.record, info.fields)
297
- return result
298
-
299
-
300
- def _make_dataclass_protocol_definition(
301
- class_name: str,
302
- type_variables: list[str],
303
- record: ast.Name,
304
- fields: Iterable[NameAndAnnotation],
305
- dataclass_imported_name: str,
306
- protocol_imported_name: str,
307
- runtime_checkable_imported_name: str,
308
- final_imported_name: str,
309
- ) -> ast.ClassDef:
310
- result = ast.ClassDef(
311
- name=class_name,
312
- type_params=_type_vars_for_fields(record, type_variables),
313
- bases=[
314
- ast.Name(
315
- id=protocol_imported_name,
316
- ctx=ast.Load(),
317
- **get_pos_attributes(record),
318
- )
319
- ],
320
- keywords=[],
321
- body=_class_contents_for_fields(fields, final_imported_name),
322
- decorator_list=[
323
- ast.Name(
324
- id=runtime_checkable_imported_name,
325
- ctx=ast.Load(),
326
- **get_pos_attributes(record),
327
- ),
328
- _dataclass_decorator(
329
- dataclass_imported_name, repr=True, pos=get_pos_attributes(record)
330
- ),
331
- ],
332
- **get_pos_attributes(record),
333
- )
334
- return result
335
-
336
-
337
166
  class _Transform(TyphonASTTransformer):
338
167
  def __init__(
339
168
  self,
@@ -342,16 +171,12 @@ class _Transform(TyphonASTTransformer):
342
171
  info_for_record: dict[ast.Name, RecordInfo],
343
172
  class_for_record_type: dict[ast.Name, ast.ClassDef],
344
173
  info_for_record_type: dict[ast.Name, RecordTypeInfo],
345
- class_for_record_pattern: dict[ast.Name, ast.ClassDef],
346
- info_for_record_pattern: dict[ast.Name, RecordPatternInfo],
347
174
  ):
348
175
  super().__init__(module)
349
176
  self.class_for_record = class_for_record
350
177
  self.info_for_record = info_for_record
351
178
  self.class_for_record_type = class_for_record_type
352
179
  self.info_for_record_type = info_for_record_type
353
- self.class_for_record_pattern = class_for_record_pattern
354
- self.info_for_record_pattern = info_for_record_pattern
355
180
 
356
181
  def visit_Name(self, node: ast.Name):
357
182
  if node in self.class_for_record:
@@ -383,12 +208,6 @@ class _Transform(TyphonASTTransformer):
383
208
  **get_empty_pos_attributes(),
384
209
  ),
385
210
  )
386
- elif node in self.class_for_record_pattern:
387
- class_def = self.class_for_record_pattern[node]
388
- info = self.info_for_record_pattern[node]
389
- return ast.Name(
390
- id=class_def.name, ctx=ast.Load(), **get_pos_attributes(node)
391
- )
392
211
  return self.generic_visit(node)
393
212
 
394
213
 
@@ -396,70 +215,37 @@ class _Transform(TyphonASTTransformer):
396
215
  def record_to_dataclass(module: ast.Module):
397
216
  gatherer = _GatherRecords(module)
398
217
  gatherer.run()
399
- if (
400
- not gatherer.records
401
- and not gatherer.record_types
402
- and not gatherer.record_patterns
403
- ):
218
+ if not gatherer.records and not gatherer.record_types:
404
219
  return
405
- # Add imports
406
- dataclass_imported_name = add_import_for_dataclass(module)
407
- final_imported_name = add_import_for_final(module)
408
- protocol_imported_name = ""
409
- runtime_checkable_imported_name = ""
410
- if gatherer.record_types or gatherer.record_patterns:
411
- protocol_imported_name = add_import_for_protocol(module)
412
- runtime_checkable_imported_name = add_import_for_runtime_checkable(module)
413
220
  # Create class and information for each record literal.
414
221
  class_for_record: dict[ast.Name, ast.ClassDef] = {}
415
222
  info_for_record: dict[ast.Name, RecordInfo] = {}
416
- insert_point = get_insert_point_for_class(module)
417
223
  for info in gatherer.records:
418
- class_def = _make_dataclass_definition(
419
- info,
420
- dataclass_imported_name,
421
- final_imported_name,
224
+ class_def = make_dataclass_definition(
225
+ module,
226
+ info.class_name,
227
+ info.type_variables,
228
+ info.fields,
229
+ get_pos_attributes(info.record),
422
230
  )
231
+ _add_repr_to_dataclass(class_def, info.record, info.fields)
423
232
  class_for_record[info.record] = class_def
424
233
  info_for_record[info.record] = info
425
- module.body.insert(insert_point, class_def)
426
- insert_point += 1
234
+ module.body.insert(get_insert_point_for_class(module), class_def)
427
235
  # Create class and information for each record type.
428
236
  class_for_record_type: dict[ast.Name, ast.ClassDef] = {}
429
237
  info_for_record_type: dict[ast.Name, RecordTypeInfo] = {}
430
238
  for info in gatherer.record_types:
431
- class_def_type = _make_dataclass_protocol_definition(
239
+ class_def_type = make_dataclass_protocol_definition(
240
+ module,
432
241
  info.class_name,
433
242
  info.type_variables,
434
- info.record,
435
243
  info.fields,
436
- dataclass_imported_name,
437
- protocol_imported_name,
438
- runtime_checkable_imported_name,
439
- final_imported_name,
244
+ get_pos_attributes(info.record),
440
245
  )
441
246
  class_for_record_type[info.record] = class_def_type
442
247
  info_for_record_type[info.record] = info
443
- module.body.insert(insert_point, class_def_type)
444
- insert_point += 1
445
- # Create class and information for each record pattern.
446
- class_for_record_pattern: dict[ast.Name, ast.ClassDef] = {}
447
- info_for_record_pattern: dict[ast.Name, RecordPatternInfo] = {}
448
- for info in gatherer.record_patterns:
449
- class_def_type = _make_dataclass_protocol_definition(
450
- info.class_name,
451
- info.type_variables,
452
- info.record,
453
- info.fields,
454
- dataclass_imported_name,
455
- protocol_imported_name,
456
- runtime_checkable_imported_name,
457
- final_imported_name,
458
- )
459
- class_for_record_pattern[info.record] = class_def_type
460
- info_for_record_pattern[info.record] = info
461
- module.body.insert(insert_point, class_def_type)
462
- insert_point += 1
248
+ module.body.insert(get_insert_point_for_class(module), class_def_type)
463
249
 
464
250
  _Transform(
465
251
  module,
@@ -467,6 +253,4 @@ def record_to_dataclass(module: ast.Module):
467
253
  info_for_record,
468
254
  class_for_record_type,
469
255
  info_for_record_type,
470
- class_for_record_pattern,
471
- info_for_record_pattern,
472
256
  ).run()
@@ -13,6 +13,9 @@ from ..Grammar.typhon_ast import (
13
13
  is_inline_with,
14
14
  set_is_placeholder,
15
15
  get_type_annotation,
16
+ is_anonymous_name,
17
+ get_anonymous_name_id,
18
+ is_let_else,
16
19
  )
17
20
  from ..Grammar.syntax_errors import (
18
21
  raise_scope_error,
@@ -42,10 +45,20 @@ class DeclarationContext:
42
45
  @dataclass
43
46
  class SuspendedResolveAccess:
44
47
  name: ast.Name
45
- top_level_dead: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef
48
+ temporally_dead_accessor: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef
46
49
  is_mutation: bool
47
50
  accessor_python_scope: PythonScope
48
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
+
49
62
 
50
63
  def get_builtins() -> set[str]:
51
64
  builtin_syms = set(dir(builtins))
@@ -67,10 +80,13 @@ class SymbolScopeVisitor(TyphonASTVisitor):
67
80
  self.non_declaration_assign_context = False
68
81
  self.suspended_symbols: set[str] = set()
69
82
  # suspended_resolves[dead_accessor_name][undeclared_name]
70
- 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]]] = {}
71
86
  self.require_global: dict[str, set[PythonScope]] = {}
72
87
  self.require_nonlocal: dict[str, set[PythonScope]] = {}
73
88
  self.builtins_symbols = get_builtins()
89
+ self.anonymous_names: dict[int, str] = {}
74
90
 
75
91
  def _enter_scope(self):
76
92
  self.scopes.append({})
@@ -172,14 +188,28 @@ class SymbolScopeVisitor(TyphonASTVisitor):
172
188
  is_top_level = self.now_is_top_level(ignore_current=add_to_parent_python_scope)
173
189
  if is_top_level:
174
190
  self.resolve_suspended_resolves(name)
175
- in_scope_non_python_top_level = len(self.scopes) > 1
191
+ considering_python_scopes = (
192
+ len(self.parent_python_scopes) - 1
193
+ if add_to_parent_python_scope
194
+ else len(self.parent_python_scopes)
195
+ )
196
+ in_scope_non_python_top_level = (
197
+ len(self.scopes) > considering_python_scopes and len(self.scopes) > 1
198
+ )
199
+ rename_condition = (
200
+ # To realize shadowing
201
+ self.is_shadowed(name, python_scope_to_add)
202
+ # To prevent to expose to top-level scope
203
+ or (is_top_level and in_scope_non_python_top_level)
204
+ or is_force_rename
205
+ )
206
+ if name not in self.builtins_symbols:
207
+ debug_verbose_print(
208
+ f"Renaming condition of '{name}': {rename_condition} is_shadowed={self.is_shadowed(name, python_scope_to_add)}, is_top_level={is_top_level}, in_scope_non_python_top_level={in_scope_non_python_top_level}, is_force_rename={is_force_rename}) len(scopes)={len(self.scopes)} len(parent_python_scopes)={len(self.parent_python_scopes)} rename_on_demand_to_kind={rename_on_demand_to_kind} "
209
+ )
176
210
  # Rename if required
177
211
  if rename_on_demand_to_kind is not None:
178
- if (
179
- self.is_shadowed(name, python_scope_to_add)
180
- or (is_top_level and in_scope_non_python_top_level)
181
- or is_force_rename
182
- ):
212
+ if rename_condition:
183
213
  new_name = self.new_name(rename_on_demand_to_kind, name)
184
214
  dec.renamed_to = new_name
185
215
  debug_print(f"Renamed variable '{dec.name}' to '{new_name}'")
@@ -437,19 +467,31 @@ class SymbolScopeVisitor(TyphonASTVisitor):
437
467
 
438
468
  def visit_Match(self, node: ast.Match):
439
469
  self.visit(node.subject)
440
- self.visit_list_scoped(node.cases)
470
+ # self.visit_list_scoped(node.cases) # Each case has its own scope
471
+ for case in node.cases:
472
+ self.visit(case)
441
473
  return node
442
474
 
443
475
  # Match patterns. Basis of declarative patterns are MatchAs, MatchStar.
444
476
  # Other patterns are composed of these.
445
477
  # Declarations in patterns are immutable.
446
478
  def visit_match_case(self, node: ast.match_case):
447
- with self.scope():
479
+ def visit_children():
448
480
  self.visit(node.pattern)
449
481
  if node.guard:
450
482
  self.visit(node.guard)
451
483
  self.visit_list_scoped(node.body)
452
484
 
485
+ debug_verbose_print(
486
+ f"Visiting match_case: pattern={ast.dump(node)} is_let_else={is_let_else(node)}"
487
+ )
488
+
489
+ if is_let_else(node):
490
+ visit_children()
491
+ else:
492
+ with self.scope():
493
+ visit_children()
494
+
453
495
  def visit_MatchAs(self, node: ast.MatchAs):
454
496
  if node.pattern:
455
497
  self.visit(node.pattern)
@@ -459,7 +501,8 @@ class SymbolScopeVisitor(TyphonASTVisitor):
459
501
  name,
460
502
  is_mutable=False,
461
503
  # Force rename to avoid conflict to other patterns when type annotations is specified, because they are expanded in the same scope.
462
- is_force_rename=get_type_annotation(node) is not None,
504
+ is_force_rename=get_type_annotation(node) is not None
505
+ and len(self.scopes) > 1,
463
506
  )
464
507
  node.name = name.id
465
508
  return node
@@ -471,7 +514,8 @@ class SymbolScopeVisitor(TyphonASTVisitor):
471
514
  name,
472
515
  is_mutable=False,
473
516
  # Force rename to avoid conflict to other patterns when type annotations is specified, because they are expanded in the same scope.
474
- is_force_rename=get_type_annotation(node) is not None,
517
+ is_force_rename=get_type_annotation(node) is not None
518
+ and len(self.scopes) > 1,
475
519
  )
476
520
  node.name = name.id
477
521
  return node
@@ -591,19 +635,26 @@ class SymbolScopeVisitor(TyphonASTVisitor):
591
635
  type SuspendableScope = ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef
592
636
 
593
637
  def add_suspended_resolve(
594
- 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,
595
642
  ) -> SuspendedResolveAccess:
596
- tdz = SuspendedResolveAccess(
643
+ suspended = SuspendedResolveAccess(
597
644
  name=name,
598
- top_level_dead=top_level_dead,
645
+ temporally_dead_accessor=temporally_dead_accessor,
599
646
  is_mutation=is_mutable,
600
647
  accessor_python_scope=self.get_parent_python_scope(),
601
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
+ )
602
652
  self.suspended_symbols.add(name.id)
603
- self.suspended_resolves.setdefault(top_level_dead.name, {}).setdefault(
604
- name.id, []
605
- ).append(tdz)
606
- 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
607
658
 
608
659
  def suspendable_scope(
609
660
  self,
@@ -628,19 +679,23 @@ class SymbolScopeVisitor(TyphonASTVisitor):
628
679
  return self.add_suspended_resolve(name, belong_top_level_scope, is_mutation)
629
680
 
630
681
  # Access to maybe temporally dead variable. return False if access is not valid.
631
- # Copy the dependency of suspended access as current scope's dependency.
632
- def access_temporal_dead(self, name: ast.Name) -> bool:
633
- tdz_depends = self.suspended_resolves.get(name.id, None)
634
- 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:
635
687
  return True # Not dead
636
688
  belong_top_level_scope = self.suspendable_scope()
637
689
  if belong_top_level_scope is None:
638
690
  return False # Invalid access
639
- # Copy the dependency to current scope
640
- for _, suspends in tdz_depends.items():
641
- 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:
642
697
  self.add_suspended_resolve(
643
- tdz.name, belong_top_level_scope, tdz.is_mutation
698
+ suspended.name, belong_top_level_scope, suspended.is_mutation
644
699
  )
645
700
  return True
646
701
 
@@ -696,9 +751,28 @@ class SymbolScopeVisitor(TyphonASTVisitor):
696
751
  ast.Name(id=sym.name, ctx=ast.Store(), **get_empty_pos_attributes())
697
752
  )
698
753
 
754
+ def handle_anonymous_name(self, node: ast.Name):
755
+ anon_id = get_anonymous_name_id(node)
756
+ if anon_id is None:
757
+ return False
758
+ if anon_id in self.anonymous_names:
759
+ debug_verbose_print(
760
+ f"Reused anonymous variable for '{node.id}' as '{self.anonymous_names[anon_id]}'"
761
+ )
762
+ node.id = self.anonymous_names[anon_id]
763
+ else:
764
+ new_name = self.new_name(NameKind.VARIABLE, "")
765
+ debug_verbose_print(
766
+ f"Renamed anonymous variable '{node.id}' to '{new_name}'"
767
+ )
768
+ self.anonymous_names[anon_id] = new_name
769
+ node.id = new_name
770
+ return True
771
+
699
772
  # The main part of this visitor.
700
773
  # Variable reference and declaration. Rename if necessary.
701
774
  def visit_Name(self, node: ast.Name):
775
+ is_anon = self.handle_anonymous_name(node)
702
776
  if self.declaration_context:
703
777
  # Left-hand side of an declaration assignment
704
778
  if self.current_scope().get(node.id):
@@ -707,10 +781,13 @@ class SymbolScopeVisitor(TyphonASTVisitor):
707
781
  node.id,
708
782
  is_mutable=self.declaration_context.is_mutable,
709
783
  pos=get_pos_attributes(node),
710
- is_force_rename=self.declaration_context.is_force_rename,
784
+ is_force_rename=self.declaration_context.is_force_rename
785
+ and not is_anon,
711
786
  rename_on_demand_to_kind=(
712
787
  NameKind.VARIABLE
713
788
  if self.declaration_context.is_mutable
789
+ else None
790
+ if is_anon
714
791
  else NameKind.CONST
715
792
  ),
716
793
  )
@@ -742,12 +819,15 @@ class SymbolScopeVisitor(TyphonASTVisitor):
742
819
  if self.is_temporal_dead(node.id):
743
820
  # Accessing to a temporally dead variable
744
821
  debug_verbose_print(f"Temporal dead access to '{node.id}'")
745
- 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}'")
746
824
  return self.error_tdz_violation(node)
825
+ debug_verbose_print(f"Accessing variable '{node.id}'")
747
826
  self.access_to_symbol_non_decl(
748
827
  sym, is_mutation=self.non_declaration_assign_context
749
828
  )
750
829
  with self.type_annotation_maybe_in_declaration(node):
830
+ debug_verbose_print(f"Maybe type annotated Name: {ast.dump(node)}")
751
831
  return self.generic_visit(node)
752
832
 
753
833
  def visit_Starred(self, node: ast.Starred):