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.
- Typhon/Driver/configs.py +14 -0
- Typhon/Driver/debugging.py +148 -5
- Typhon/Driver/diagnostic.py +4 -3
- Typhon/Driver/language_server.py +25 -0
- Typhon/Driver/run.py +1 -1
- Typhon/Driver/translate.py +14 -10
- Typhon/Driver/utils.py +39 -1
- Typhon/Grammar/_typhon_parser.py +2738 -2525
- Typhon/Grammar/parser.py +80 -53
- Typhon/Grammar/parser_helper.py +68 -87
- Typhon/Grammar/syntax_errors.py +31 -21
- Typhon/Grammar/token_factory_custom.py +541 -485
- Typhon/Grammar/tokenizer_custom.py +52 -0
- Typhon/Grammar/typhon_ast.py +372 -44
- Typhon/Grammar/typhon_ast_error.py +438 -0
- Typhon/LanguageServer/__init__.py +3 -0
- Typhon/LanguageServer/client/__init__.py +42 -0
- Typhon/LanguageServer/client/pyrefly.py +115 -0
- Typhon/LanguageServer/client/pyright.py +173 -0
- Typhon/LanguageServer/semantic_tokens.py +446 -0
- Typhon/LanguageServer/server.py +376 -0
- Typhon/LanguageServer/utils.py +65 -0
- Typhon/SourceMap/ast_match_based_map.py +199 -152
- Typhon/SourceMap/ast_matching.py +102 -87
- Typhon/SourceMap/datatype.py +27 -16
- Typhon/SourceMap/defined_name_retrieve.py +145 -0
- Typhon/Transform/comprehension_to_function.py +2 -5
- Typhon/Transform/const_member_to_final.py +12 -7
- Typhon/Transform/forbidden_statements.py +1 -0
- Typhon/Transform/optional_operators_to_checked.py +14 -6
- Typhon/Transform/scope_check_rename.py +44 -18
- Typhon/Transform/type_abbrev_desugar.py +11 -15
- Typhon/Transform/type_annotation_check_expand.py +2 -2
- Typhon/Transform/utils/imports.py +39 -4
- Typhon/Transform/utils/make_class.py +18 -23
- Typhon/Transform/visitor.py +25 -0
- Typhon/Typing/pyrefly.py +145 -0
- Typhon/Typing/pyright.py +2 -4
- Typhon/__main__.py +15 -1
- {typhon_language-0.1.3.dist-info → typhon_language-0.1.4.dist-info}/METADATA +7 -5
- typhon_language-0.1.4.dist-info/RECORD +65 -0
- {typhon_language-0.1.3.dist-info → typhon_language-0.1.4.dist-info}/WHEEL +1 -1
- typhon_language-0.1.4.dist-info/licenses/LICENSE +201 -0
- typhon_language-0.1.3.dist-info/RECORD +0 -53
- typhon_language-0.1.3.dist-info/licenses/LICENSE +0 -21
- {typhon_language-0.1.3.dist-info → typhon_language-0.1.4.dist-info}/entry_points.txt +0 -0
- {typhon_language-0.1.3.dist-info → typhon_language-0.1.4.dist-info}/top_level.txt +0 -0
Typhon/SourceMap/datatype.py
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
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=
|
|
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
|
|
8
|
-
from .
|
|
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),
|
|
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=
|
|
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 =
|
|
42
|
+
new_annotation = get_final(ctx=ast.Load(), **pos)
|
|
42
43
|
return ast.AnnAssign(
|
|
43
|
-
target=ast.Name(
|
|
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)
|
|
@@ -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:
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
638
|
+
self,
|
|
639
|
+
name: ast.Name,
|
|
640
|
+
temporally_dead_accessor: SuspendableScope,
|
|
641
|
+
is_mutable: bool,
|
|
627
642
|
) -> SuspendedResolveAccess:
|
|
628
|
-
|
|
643
|
+
suspended = SuspendedResolveAccess(
|
|
629
644
|
name=name,
|
|
630
|
-
|
|
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(
|
|
636
|
-
name
|
|
637
|
-
).
|
|
638
|
-
|
|
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
|
-
#
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
|
672
|
-
for _,
|
|
673
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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]]
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)):
|