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
@@ -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 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)
@@ -0,0 +1,139 @@
1
+ import ast
2
+ from dataclasses import dataclass
3
+ from typing import Protocol, Iterable, Final
4
+ from ..Grammar.typhon_ast import (
5
+ RecordLiteral,
6
+ get_record_literal_fields,
7
+ get_record_type_fields,
8
+ get_pos_attributes,
9
+ pos_attribute_to_range,
10
+ get_empty_pos_attributes,
11
+ PosAttributes,
12
+ set_is_var,
13
+ is_record_literal,
14
+ is_record_type,
15
+ is_attributes_pattern,
16
+ is_pattern_tuple,
17
+ )
18
+ from .visitor import TyphonASTVisitor, TyphonASTTransformer, flat_append
19
+ from .utils.imports import (
20
+ get_insert_point_for_class,
21
+ )
22
+ from .utils.make_class import (
23
+ make_protocol_definition,
24
+ )
25
+
26
+
27
+ @dataclass
28
+ class AttributePatternFieldInfo:
29
+ name: ast.Name
30
+ annotation: ast.expr
31
+
32
+
33
+ @dataclass
34
+ class AttributePatternInfo:
35
+ class_name: str
36
+ pattern: ast.MatchClass
37
+ record: ast.Name
38
+ type_variables: list[str]
39
+ fields: list[AttributePatternFieldInfo]
40
+
41
+
42
+ class _GatherRecords(TyphonASTVisitor):
43
+ record_patterns: list[AttributePatternInfo]
44
+
45
+ def __init__(self, module: ast.Module):
46
+ super().__init__(module)
47
+ self.record_patterns = []
48
+
49
+ def visit_MatchClass(self, node: ast.MatchClass):
50
+ if isinstance(node.cls, ast.Name) and is_attributes_pattern(node.cls):
51
+ record_cls = node.cls
52
+ type_vars: list[str] = []
53
+ fields: list[AttributePatternFieldInfo] = []
54
+ # Gather the member names from keyword patterns.
55
+ for kwd_name in node.kwd_attrs:
56
+ type_var = self.new_typevar_name(kwd_name)
57
+ type_vars.append(type_var)
58
+ fields.append(
59
+ AttributePatternFieldInfo(
60
+ name=ast.Name(
61
+ id=kwd_name,
62
+ ctx=ast.Load(),
63
+ **get_pos_attributes(node.cls),
64
+ ),
65
+ annotation=ast.Name(
66
+ id=type_var, ctx=ast.Load(), **get_pos_attributes(node.cls)
67
+ ),
68
+ )
69
+ )
70
+ self.record_patterns.append(
71
+ AttributePatternInfo(
72
+ self.new_class_name(""),
73
+ node,
74
+ record_cls,
75
+ type_vars,
76
+ fields,
77
+ )
78
+ )
79
+ return self.generic_visit(node)
80
+
81
+
82
+ class _Transform(TyphonASTTransformer):
83
+ def __init__(
84
+ self,
85
+ module: ast.Module,
86
+ class_for_record_pattern: dict[ast.Name, ast.ClassDef],
87
+ info_for_record_pattern: dict[ast.Name, AttributePatternInfo],
88
+ ):
89
+ super().__init__(module)
90
+ self.class_for_record_pattern = class_for_record_pattern
91
+ self.info_for_record_pattern = info_for_record_pattern
92
+
93
+ def visit_Name(self, node: ast.Name):
94
+ if node in self.class_for_record_pattern:
95
+ class_def = self.class_for_record_pattern[node]
96
+ info = self.info_for_record_pattern[node]
97
+ return ast.Name(
98
+ id=class_def.name, ctx=ast.Load(), **get_pos_attributes(node)
99
+ )
100
+ return self.generic_visit(node)
101
+
102
+ # Convert tuple pattern
103
+ def visit_MatchSequence(self, node: ast.MatchSequence):
104
+ if is_pattern_tuple(node):
105
+ pos = get_pos_attributes(node)
106
+ return ast.MatchClass(
107
+ cls=ast.Name(id="tuple", ctx=ast.Load(), **pos),
108
+ patterns=[
109
+ ast.MatchSequence(node.patterns, **pos_attribute_to_range(pos))
110
+ ],
111
+ kwd_attrs=[],
112
+ kwd_patterns=[],
113
+ **pos_attribute_to_range(pos),
114
+ )
115
+ return self.generic_visit(node)
116
+
117
+
118
+ def extended_protocol(module: ast.Module):
119
+ gatherer = _GatherRecords(module)
120
+ gatherer.run()
121
+ # Create class and information for each record pattern.
122
+ class_for_record_pattern: dict[ast.Name, ast.ClassDef] = {}
123
+ info_for_record_pattern: dict[ast.Name, AttributePatternInfo] = {}
124
+ for info in gatherer.record_patterns:
125
+ class_def_type = make_protocol_definition(
126
+ module,
127
+ info.class_name,
128
+ info.type_variables,
129
+ info.fields,
130
+ get_pos_attributes(info.record),
131
+ )
132
+ class_for_record_pattern[info.record] = class_def_type
133
+ info_for_record_pattern[info.record] = info
134
+ module.body.insert(get_insert_point_for_class(module), class_def_type)
135
+ _Transform(
136
+ module,
137
+ class_for_record_pattern,
138
+ info_for_record_pattern,
139
+ ).run()
@@ -8,6 +8,8 @@ from ..Grammar.typhon_ast import (
8
8
  is_decl_assign,
9
9
  is_multi_decl,
10
10
  PosAttributes,
11
+ is_let_else,
12
+ is_inline_with,
11
13
  )
12
14
  from ..Grammar.syntax_errors import (
13
15
  raise_forbidden_statement_error,
@@ -48,6 +50,28 @@ class ForbiddenStatementChecker(TyphonASTVisitor):
48
50
  )
49
51
  return self.generic_visit(node)
50
52
 
53
+ def visit_With_AsyncWith(self, node: ast.With | ast.AsyncWith):
54
+ if is_inline_with(node) and self.now_is_top_level():
55
+ self._raise_forbidden_statement(
56
+ "Inline `with` statement in module top level is forbidden",
57
+ get_pos_attributes(node),
58
+ )
59
+ return self.generic_visit(node)
60
+
61
+ def visit_With(self, node: ast.With):
62
+ self.visit_With_AsyncWith(node)
63
+
64
+ def visit_AsyncWith(self, node: ast.AsyncWith):
65
+ self.visit_With_AsyncWith(node)
66
+
67
+ def visit_If(self, node: ast.If):
68
+ if is_let_else(node) and self.now_is_top_level() and node.orelse:
69
+ self._raise_forbidden_statement(
70
+ "`let-else` statement in module top level is forbidden",
71
+ get_pos_attributes(node),
72
+ )
73
+ return self.generic_visit(node)
74
+
51
75
  # TODO: Check control statements inside class definition.
52
76
 
53
77
  def _is_valid_stmt_in_class(self, node: ast.stmt) -> bool:
@@ -61,6 +85,7 @@ class ForbiddenStatementChecker(TyphonASTVisitor):
61
85
  ast.Import,
62
86
  ast.AnnAssign,
63
87
  ast.Assign,
88
+ ast.Pass,
64
89
  ),
65
90
  )
66
91
  ):
@@ -5,11 +5,13 @@ from ..Grammar.typhon_ast import (
5
5
  is_let_else,
6
6
  set_is_var,
7
7
  set_is_let_else,
8
+ LetPatternInfo,
8
9
  )
9
10
  from .visitor import TyphonASTTransformer, flat_append
10
11
  from contextlib import contextmanager
11
12
  from dataclasses import dataclass
12
- from .utils import is_body_jump_away
13
+ from .utils.jump_away import is_body_jump_away
14
+ from ..Grammar.syntax_errors import raise_let_missing_else_error
13
15
 
14
16
 
15
17
  class IfMultipleLetTransformer(TyphonASTTransformer):
@@ -32,6 +34,13 @@ class IfMultipleLetTransformer(TyphonASTTransformer):
32
34
  match <subjectN>:
33
35
  case <patternN> if <cond>:
34
36
  <body>
37
+ case _:
38
+ pass
39
+ ...
40
+ case _:
41
+ pass
42
+ case _:
43
+ pass
35
44
  else:
36
45
  <orelse>
37
46
 
@@ -43,6 +52,11 @@ class IfMultipleLetTransformer(TyphonASTTransformer):
43
52
  match <subjectN>:
44
53
  case <patternN> if <cond>:
45
54
  <body>
55
+ case _:
56
+ pass
57
+ ...
58
+ case _:
59
+ pass
46
60
  <orelse> # Unconditional execution of orelse
47
61
 
48
62
  when <body> does not jump away:
@@ -54,20 +68,22 @@ class IfMultipleLetTransformer(TyphonASTTransformer):
54
68
  case <patternN> if <cond>:
55
69
  <else_flag> = False
56
70
  <body>
71
+ case _:
72
+ pass
73
+ ...
74
+ case _:
75
+ pass
57
76
  if <else_flag>: # Only when body did not execute
58
77
  <orelse>
59
78
  """
60
79
 
61
- def visit_If(self, node: ast.If):
62
- self.generic_visit(node)
63
- body = get_let_pattern_body(node)
64
- if body is None:
65
- return node
80
+ def visit_IfLet(self, node: ast.If, info: LetPatternInfo) -> list[ast.stmt]:
81
+ body = info.body
66
82
  pos = get_pos_attributes(node)
67
83
  # When jump away, does not need flag control.
68
84
  jump_away = is_body_jump_away(body)
69
85
  if jump_away:
70
- result: list[ast.stmt] = node.body
86
+ result: list[ast.stmt] = node.body # Inside if True
71
87
  if node.orelse:
72
88
  result.extend(node.orelse)
73
89
  return result
@@ -101,6 +117,101 @@ class IfMultipleLetTransformer(TyphonASTTransformer):
101
117
  )
102
118
  return result
103
119
 
120
+ """
121
+ let <pattern1> = <subject1>, <pattern2> = <subject2>, ...,
122
+ <patternN> = <subjectN>
123
+ else { # else clause is required unless all patterns are irrefutable
124
+ <orelse> # If exists, must jump away.
125
+ }
126
+ <body>
127
+
128
+ --> (temporary represented now)
129
+
130
+ if True: # multiple_let_pattern_body set to <body>
131
+ match <subject1>:
132
+ case <pattern1>:
133
+ match <subject2>:
134
+ case <pattern2>:
135
+ ...
136
+ match <subjectN>:
137
+ case <patternN>:
138
+ <body>
139
+ case _:
140
+ pass
141
+ (raise TypeError) # If else does not exist
142
+ ...
143
+ case _:
144
+ pass
145
+ (raise TypeError) # If else does not exist
146
+ case _:
147
+ pass
148
+ (raise TypeError) # If else does not exist
149
+ else: # If exists
150
+ <orelse>
151
+
152
+ --> (transformation here)
153
+
154
+ The case when <body> does jumps away:
155
+
156
+ match <subject1>:
157
+ case <pattern1>:
158
+ ...
159
+ match <subjectN>:
160
+ case <patternN>:
161
+ <body>
162
+ case _:
163
+ pass
164
+ (raise TypeError) # If else does not exist
165
+ ...
166
+ case _:
167
+ pass
168
+ <orelse> # Unconditional execution of orelse
169
+
170
+ The case when <body> does not jump away:
171
+
172
+ <else_flag> = True
173
+ match <subject1>:
174
+ case <pattern1>:
175
+ ...
176
+ match <subjectN>:
177
+ case <patternN>:
178
+ <else_flag> = False
179
+ <body>
180
+ case _:
181
+ pass
182
+ (raise TypeError) # If else does not exist
183
+ ...
184
+ case _:
185
+ pass
186
+ (raise TypeError) # If else does not exist
187
+ if <else_flag>: # Only when body did not execute
188
+ <orelse> # If exists
189
+ """
190
+
191
+ def visit_LetElse(self, node: ast.If, info: LetPatternInfo) -> list[ast.stmt]:
192
+ pos = get_pos_attributes(node)
193
+ orelse = node.orelse
194
+ if not orelse:
195
+ if info.is_all_pattern_irrefutable:
196
+ return node.body # Simply return inside if True
197
+ else:
198
+ raise_let_missing_else_error(
199
+ "let pattern is irrefutable, possibly fails to match. else clause of let-else statement is required.",
200
+ **pos,
201
+ )
202
+ # With orelse, same as IfLet.
203
+ return self.visit_IfLet(node, info)
204
+
205
+ def visit_If(self, node: ast.If):
206
+ self.generic_visit(node) # Visit children first
207
+ info = get_let_pattern_body(node)
208
+ if info is None: # Not a if-let nor let-else
209
+ return node
210
+ if is_let_else(node):
211
+ return self.visit_LetElse(node, info)
212
+ else:
213
+ return self.visit_IfLet(node, info)
214
+
104
215
 
105
216
  class WhileLetTransformer(TyphonASTTransformer):
106
217
  """
@@ -149,8 +260,8 @@ class WhileLetTransformer(TyphonASTTransformer):
149
260
 
150
261
  def visit_While(self, node: ast.While):
151
262
  self.generic_visit(node)
152
- body = get_let_pattern_body(node)
153
- if body is None:
263
+ info = get_let_pattern_body(node)
264
+ if info is None:
154
265
  return node
155
266
  pos = get_pos_attributes(node)
156
267
  # <continue_flag> = True
@@ -161,14 +272,14 @@ class WhileLetTransformer(TyphonASTTransformer):
161
272
  **pos,
162
273
  )
163
274
  set_is_var(continue_flag_assign_true)
164
- if not is_body_jump_away(body):
275
+ if not is_body_jump_away(info.body):
165
276
  # <continue_flag> = True at end of body
166
277
  continue_flag_set_true = ast.Assign(
167
278
  targets=[ast.Name(id=continue_flag_name, ctx=ast.Store(), **pos)],
168
279
  value=ast.Constant(value=True),
169
280
  **pos,
170
281
  )
171
- body.append(continue_flag_set_true)
282
+ info.body.append(continue_flag_set_true)
172
283
  # <continue_flag> = False at start of while body
173
284
  continue_flag_set_false = ast.Assign(
174
285
  targets=[ast.Name(id=continue_flag_name, ctx=ast.Store(), **pos)],
@@ -4,12 +4,13 @@ from ..Grammar.typhon_ast import (
4
4
  clear_inline_with,
5
5
  is_let_else,
6
6
  get_let_pattern_body,
7
+ get_pos_attributes,
7
8
  )
8
9
  from .visitor import TyphonASTVisitor, flat_append
9
10
  from contextlib import contextmanager
10
11
  from dataclasses import dataclass
11
12
  from ..Driver.debugging import debug_print, debug_verbose_print
12
- from .utils import is_body_jump_away
13
+ from .utils.jump_away import is_body_jump_away
13
14
 
14
15
 
15
16
  @dataclass
@@ -73,14 +74,14 @@ class _InlineStatementBlockCaptureGather(TyphonASTVisitor):
73
74
 
74
75
  --> (is represented here actually)
75
76
 
76
- if True: # multiple_let_pattern_body set to empty
77
+ if True:
77
78
  match <subject1>:
78
79
  case <pattern1>:
79
80
  match <subject2>:
80
81
  ...
81
82
  match <subjectN>:
82
83
  case <patternN>:
83
- # empty
84
+ # empty # info.body
84
85
  else:
85
86
  <orelse> # Must jump away.
86
87
  <after>
@@ -96,7 +97,7 @@ class _InlineStatementBlockCaptureGather(TyphonASTVisitor):
96
97
 
97
98
  --> (transformation here actually)
98
99
 
99
- if True: # multiple_let_pattern_body set to empty
100
+ if True: # let_pattern_body set to empty
100
101
  match <subject1>:
101
102
  case <pattern1>:
102
103
  match <subject2>:
@@ -107,8 +108,8 @@ class _InlineStatementBlockCaptureGather(TyphonASTVisitor):
107
108
  else:
108
109
  <orelse> # Must jump away.
109
110
  """
110
- body = get_let_pattern_body(node)
111
- if body is None or not is_let_else(node):
111
+ info = get_let_pattern_body(node)
112
+ if info is None or not is_let_else(node):
112
113
  self._visit_list_scoped(node, node.body)
113
114
  self._visit_list_scoped(node, node.orelse)
114
115
  return
@@ -118,18 +119,23 @@ class _InlineStatementBlockCaptureGather(TyphonASTVisitor):
118
119
  node=node,
119
120
  parent_block=parent_block_node,
120
121
  parent_block_body=parent_block_body,
121
- body_capture_into=body,
122
+ body_capture_into=info.body,
122
123
  )
123
124
  debug_print(
124
- f"Found let else: \n node:{ast.dump(node)} \n parent:{ast.dump(parent_block_node)}\n body:{list(map(ast.dump, body))}"
125
+ f"Found let else: \n node:{ast.dump(node)} \n parent:{ast.dump(parent_block_node)}\n body:{list(map(ast.dump, info.body))}"
125
126
  )
126
- if not is_body_jump_away(node.orelse):
127
- raise RuntimeError(
128
- "let-else's else body must jump away"
129
- ) # TODO: Arrow NoReturn function call
127
+ if node.orelse:
128
+ if not is_body_jump_away(node.orelse):
129
+ raise RuntimeError(
130
+ "let-else's else body must jump away"
131
+ ) # TODO: Arrow NoReturn function call
132
+ else:
133
+ # Empty else body. The pattern must be irrefutable.
134
+ # The error is checked in if_while_let_transform.
135
+ pass
130
136
  self._visit_list_scoped(node, node.orelse)
131
137
  # Hack to make parent of the followings to case's body
132
- self.parent_block_scopes[-1] = (node, body)
138
+ self.parent_block_scopes[-1] = (node, info.body)
133
139
 
134
140
  def visit_For(self, node: ast.For):
135
141
  # No need to visit target, iter
@@ -215,9 +221,10 @@ def inline_statement_block_capture(module: ast.Module):
215
221
  if node not in parent_block_body:
216
222
  raise RuntimeError("Parent block not found") # TODO: proper error handling
217
223
  index = parent_block_body.index(node)
218
- # Move the remained stmts into with_node's body
219
- body_capture_into.clear()
224
+ # Move the remained stmts into capturer's body
220
225
  body_capture_into.extend(parent_block_body[index + 1 :])
226
+ if not body_capture_into:
227
+ body_capture_into.append(ast.Pass(**get_pos_attributes(node)))
221
228
  # Remove the remained stmts from parent block
222
229
  del parent_block_body[index + 1 :]
223
230
  debug_verbose_print(