cinderx 2026.1.16.2__cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.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.
- __static__/__init__.py +641 -0
- __static__/compiler_flags.py +8 -0
- __static__/enum.py +160 -0
- __static__/native_utils.py +77 -0
- __static__/type_code.py +48 -0
- __strict__/__init__.py +39 -0
- _cinderx.so +0 -0
- cinderx/__init__.py +577 -0
- cinderx/__pycache__/__init__.cpython-314.pyc +0 -0
- cinderx/_asyncio.py +156 -0
- cinderx/compileall.py +710 -0
- cinderx/compiler/__init__.py +40 -0
- cinderx/compiler/__main__.py +137 -0
- cinderx/compiler/config.py +7 -0
- cinderx/compiler/consts.py +72 -0
- cinderx/compiler/debug.py +70 -0
- cinderx/compiler/dis_stable.py +283 -0
- cinderx/compiler/errors.py +151 -0
- cinderx/compiler/flow_graph_optimizer.py +1287 -0
- cinderx/compiler/future.py +91 -0
- cinderx/compiler/misc.py +32 -0
- cinderx/compiler/opcode_cinder.py +18 -0
- cinderx/compiler/opcode_static.py +100 -0
- cinderx/compiler/opcodebase.py +158 -0
- cinderx/compiler/opcodes.py +991 -0
- cinderx/compiler/optimizer.py +547 -0
- cinderx/compiler/pyassem.py +3711 -0
- cinderx/compiler/pycodegen.py +7660 -0
- cinderx/compiler/pysourceloader.py +62 -0
- cinderx/compiler/static/__init__.py +1404 -0
- cinderx/compiler/static/compiler.py +629 -0
- cinderx/compiler/static/declaration_visitor.py +335 -0
- cinderx/compiler/static/definite_assignment_checker.py +280 -0
- cinderx/compiler/static/effects.py +160 -0
- cinderx/compiler/static/module_table.py +666 -0
- cinderx/compiler/static/type_binder.py +2176 -0
- cinderx/compiler/static/types.py +10580 -0
- cinderx/compiler/static/util.py +81 -0
- cinderx/compiler/static/visitor.py +91 -0
- cinderx/compiler/strict/__init__.py +69 -0
- cinderx/compiler/strict/class_conflict_checker.py +249 -0
- cinderx/compiler/strict/code_gen_base.py +409 -0
- cinderx/compiler/strict/common.py +507 -0
- cinderx/compiler/strict/compiler.py +352 -0
- cinderx/compiler/strict/feature_extractor.py +130 -0
- cinderx/compiler/strict/flag_extractor.py +97 -0
- cinderx/compiler/strict/loader.py +827 -0
- cinderx/compiler/strict/preprocessor.py +11 -0
- cinderx/compiler/strict/rewriter/__init__.py +5 -0
- cinderx/compiler/strict/rewriter/remove_annotations.py +84 -0
- cinderx/compiler/strict/rewriter/rewriter.py +975 -0
- cinderx/compiler/strict/runtime.py +77 -0
- cinderx/compiler/symbols.py +1754 -0
- cinderx/compiler/unparse.py +414 -0
- cinderx/compiler/visitor.py +194 -0
- cinderx/jit.py +230 -0
- cinderx/opcode.py +202 -0
- cinderx/static.py +113 -0
- cinderx/strictmodule.py +6 -0
- cinderx/test_support.py +341 -0
- cinderx-2026.1.16.2.dist-info/METADATA +15 -0
- cinderx-2026.1.16.2.dist-info/RECORD +68 -0
- cinderx-2026.1.16.2.dist-info/WHEEL +6 -0
- cinderx-2026.1.16.2.dist-info/licenses/LICENSE +21 -0
- cinderx-2026.1.16.2.dist-info/top_level.txt +5 -0
- opcodes/__init__.py +0 -0
- opcodes/assign_opcode_numbers.py +272 -0
- opcodes/cinderx_opcodes.py +121 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
# pyre-strict
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
from ast import (
|
|
9
|
+
AnnAssign,
|
|
10
|
+
Assign,
|
|
11
|
+
AST,
|
|
12
|
+
AsyncFor,
|
|
13
|
+
AsyncFunctionDef,
|
|
14
|
+
AsyncWith,
|
|
15
|
+
ClassDef,
|
|
16
|
+
For,
|
|
17
|
+
FunctionDef,
|
|
18
|
+
If,
|
|
19
|
+
Import,
|
|
20
|
+
ImportFrom,
|
|
21
|
+
Name,
|
|
22
|
+
Try,
|
|
23
|
+
While,
|
|
24
|
+
With,
|
|
25
|
+
)
|
|
26
|
+
from typing import TYPE_CHECKING, Union
|
|
27
|
+
|
|
28
|
+
from .module_table import DeferredImport, ModuleTable
|
|
29
|
+
from .types import (
|
|
30
|
+
AwaitableTypeRef,
|
|
31
|
+
Class,
|
|
32
|
+
DecoratedMethod,
|
|
33
|
+
Function,
|
|
34
|
+
InitSubclassFunction,
|
|
35
|
+
ResolvedTypeRef,
|
|
36
|
+
TypeEnvironment,
|
|
37
|
+
TypeName,
|
|
38
|
+
TypeRef,
|
|
39
|
+
UnknownDecoratedMethod,
|
|
40
|
+
)
|
|
41
|
+
from .util import make_qualname, sys_hexversion_check
|
|
42
|
+
from .visitor import GenericVisitor
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from .compiler import Compiler
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class NestedScope:
|
|
49
|
+
def __init__(self, parent_qualname: str | None) -> None:
|
|
50
|
+
self.qualname: str = make_qualname(parent_qualname, "<nested>")
|
|
51
|
+
|
|
52
|
+
def declare_class(self, node: AST, klass: Class) -> None:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
def declare_function(self, func: Function | DecoratedMethod) -> None:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def declare_variable(self, node: AnnAssign, module: ModuleTable) -> None:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
def declare_variables(self, node: Assign, module: ModuleTable) -> None:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
# pyre-ignore[11]: Annotation `ast.TypeAlias` is not defined as a type
|
|
65
|
+
def declare_type_alias(self, node: ast.TypeAlias) -> None:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
TScopeTypes = Union[ModuleTable, Class, Function, NestedScope]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class DeclarationVisitor(GenericVisitor[None]):
|
|
73
|
+
def __init__(
|
|
74
|
+
self, mod_name: str, filename: str, symbols: Compiler, optimize: int
|
|
75
|
+
) -> None:
|
|
76
|
+
module = symbols[mod_name] = ModuleTable(
|
|
77
|
+
mod_name, filename, symbols, first_pass_done=False
|
|
78
|
+
)
|
|
79
|
+
super().__init__(module)
|
|
80
|
+
self.scopes: list[TScopeTypes] = [self.module]
|
|
81
|
+
self.optimize = optimize
|
|
82
|
+
self.compiler = symbols
|
|
83
|
+
self.type_env: TypeEnvironment = symbols.type_env
|
|
84
|
+
|
|
85
|
+
def finish_bind(self) -> None:
|
|
86
|
+
self.module.finish_bind()
|
|
87
|
+
|
|
88
|
+
def parent_scope(self) -> TScopeTypes:
|
|
89
|
+
return self.scopes[-1]
|
|
90
|
+
|
|
91
|
+
def enter_scope(self, scope: TScopeTypes) -> None:
|
|
92
|
+
self.scopes.append(scope)
|
|
93
|
+
|
|
94
|
+
def exit_scope(self) -> None:
|
|
95
|
+
self.scopes.pop()
|
|
96
|
+
|
|
97
|
+
def qualify_name(self, name: str) -> str:
|
|
98
|
+
return make_qualname(self.parent_scope().qualname, name)
|
|
99
|
+
|
|
100
|
+
def make_nested_scope(self) -> NestedScope:
|
|
101
|
+
return NestedScope(self.parent_scope().qualname)
|
|
102
|
+
|
|
103
|
+
def enter_nested_scope(self) -> None:
|
|
104
|
+
self.enter_scope(self.make_nested_scope())
|
|
105
|
+
|
|
106
|
+
def visitAnnAssign(self, node: AnnAssign) -> None:
|
|
107
|
+
self.parent_scope().declare_variable(node, self.module)
|
|
108
|
+
|
|
109
|
+
def visitAssign(self, node: Assign) -> None:
|
|
110
|
+
self.parent_scope().declare_variables(node, self.module)
|
|
111
|
+
|
|
112
|
+
# pyre-ignore[11]: Annotation `ast.TypeAlias` is not defined as a type
|
|
113
|
+
def visitTypeAlias(self, node: ast.TypeAlias) -> None:
|
|
114
|
+
self.parent_scope().declare_type_alias(node)
|
|
115
|
+
|
|
116
|
+
def visitClassDef(self, node: ClassDef) -> None:
|
|
117
|
+
parent_scope = self.parent_scope()
|
|
118
|
+
qualname = make_qualname(parent_scope.qualname, node.name)
|
|
119
|
+
|
|
120
|
+
bases = [
|
|
121
|
+
self.module.resolve_type(base, qualname) or self.type_env.dynamic
|
|
122
|
+
for base in node.bases
|
|
123
|
+
]
|
|
124
|
+
if not bases:
|
|
125
|
+
bases.append(self.type_env.object)
|
|
126
|
+
|
|
127
|
+
with self.compiler.error_sink.error_context(self.filename, node):
|
|
128
|
+
klasses = []
|
|
129
|
+
for base in bases:
|
|
130
|
+
klasses.append(
|
|
131
|
+
base.make_subclass(
|
|
132
|
+
TypeName(self.module_name, qualname),
|
|
133
|
+
bases,
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
for cur_type in klasses:
|
|
137
|
+
if type(cur_type) is not type(klasses[0]):
|
|
138
|
+
self.syntax_error("Incompatible subtypes", node)
|
|
139
|
+
klass = klasses[0]
|
|
140
|
+
|
|
141
|
+
for base in bases:
|
|
142
|
+
if base is self.type_env.named_tuple:
|
|
143
|
+
# In named tuples, the fields are actually elements
|
|
144
|
+
# of the tuple, so we can't do any advanced binding against it.
|
|
145
|
+
klass = self.type_env.dynamic
|
|
146
|
+
break
|
|
147
|
+
|
|
148
|
+
if base is self.type_env.protocol:
|
|
149
|
+
# Protocols aren't guaranteed to exist in the actual MRO, so let's treat
|
|
150
|
+
# them as dynamic to force dynamic dispatch.
|
|
151
|
+
klass = self.type_env.dynamic
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
if base is self.type_env.typed_dict:
|
|
155
|
+
# TASK(T121706684) Supporting typed dicts is tricky, similar
|
|
156
|
+
# to protocols and named tuples
|
|
157
|
+
klass = self.type_env.dynamic
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
if base.is_final:
|
|
161
|
+
self.syntax_error(
|
|
162
|
+
f"Class `{klass.instance.name}` cannot subclass a Final class: `{base.instance.name}`",
|
|
163
|
+
node,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# we can't statically load classes nested inside functions
|
|
167
|
+
if not isinstance(parent_scope, (ModuleTable, Class)):
|
|
168
|
+
klass = self.type_env.dynamic
|
|
169
|
+
|
|
170
|
+
self.enter_scope(
|
|
171
|
+
self.make_nested_scope() if klass is self.type_env.dynamic else klass
|
|
172
|
+
)
|
|
173
|
+
for item in node.body:
|
|
174
|
+
with self.compiler.error_sink.error_context(self.filename, item):
|
|
175
|
+
self.visit(item)
|
|
176
|
+
self.exit_scope()
|
|
177
|
+
|
|
178
|
+
for d in reversed(node.decorator_list):
|
|
179
|
+
if klass is self.type_env.dynamic:
|
|
180
|
+
break
|
|
181
|
+
with self.compiler.error_sink.error_context(self.filename, d):
|
|
182
|
+
decorator = self.module.resolve_decorator(d) or self.type_env.dynamic
|
|
183
|
+
klass = decorator.resolve_decorate_class(klass, d, self)
|
|
184
|
+
|
|
185
|
+
parent_scope.declare_class(node, klass.exact_type())
|
|
186
|
+
# We want the name corresponding to `C` to be the exact type when imported.
|
|
187
|
+
self.module.types[node] = klass.exact_type()
|
|
188
|
+
|
|
189
|
+
def _visitFunc(self, node: FunctionDef | AsyncFunctionDef) -> None:
|
|
190
|
+
function = self._make_function(node)
|
|
191
|
+
self.parent_scope().declare_function(function)
|
|
192
|
+
|
|
193
|
+
def _make_function(self, node: FunctionDef | AsyncFunctionDef) -> Function:
|
|
194
|
+
qualname = self.qualify_name(node.name)
|
|
195
|
+
if node.name == "__init_subclass__":
|
|
196
|
+
func = InitSubclassFunction(
|
|
197
|
+
node, self.module, self.return_type_ref(node, qualname)
|
|
198
|
+
)
|
|
199
|
+
parent_scope = self.parent_scope()
|
|
200
|
+
if isinstance(parent_scope, Class):
|
|
201
|
+
parent_scope.has_init_subclass = True
|
|
202
|
+
else:
|
|
203
|
+
func = Function(node, self.module, self.return_type_ref(node, qualname))
|
|
204
|
+
self.enter_scope(func)
|
|
205
|
+
for item in node.body:
|
|
206
|
+
self.visit(item)
|
|
207
|
+
self.exit_scope()
|
|
208
|
+
|
|
209
|
+
func_type = func
|
|
210
|
+
if node.decorator_list:
|
|
211
|
+
# Since we haven't resolved decorators yet (until finish_bind), we
|
|
212
|
+
# don't know what type we should ultimately set for this node;
|
|
213
|
+
# Function.finish_bind() will likely override this.
|
|
214
|
+
func_type = UnknownDecoratedMethod(func, node.decorator_list[0])
|
|
215
|
+
|
|
216
|
+
self.module.types[node] = func_type
|
|
217
|
+
return func
|
|
218
|
+
|
|
219
|
+
def visitFunctionDef(self, node: FunctionDef) -> None:
|
|
220
|
+
self._visitFunc(node)
|
|
221
|
+
|
|
222
|
+
def visitAsyncFunctionDef(self, node: AsyncFunctionDef) -> None:
|
|
223
|
+
self._visitFunc(node)
|
|
224
|
+
|
|
225
|
+
def return_type_ref(
|
|
226
|
+
self, node: FunctionDef | AsyncFunctionDef, requester: str
|
|
227
|
+
) -> TypeRef:
|
|
228
|
+
ann = node.returns
|
|
229
|
+
if not ann:
|
|
230
|
+
res = ResolvedTypeRef(self.type_env.dynamic)
|
|
231
|
+
else:
|
|
232
|
+
res = TypeRef(self.module, requester, ann)
|
|
233
|
+
if isinstance(node, AsyncFunctionDef):
|
|
234
|
+
res = AwaitableTypeRef(res, self.module.compiler)
|
|
235
|
+
return res
|
|
236
|
+
|
|
237
|
+
def visitImport(self, node: Import) -> None:
|
|
238
|
+
for name in node.names:
|
|
239
|
+
asname = name.asname
|
|
240
|
+
if asname is None:
|
|
241
|
+
top_level_module = name.name.split(".")[0]
|
|
242
|
+
self.module.declare_import(
|
|
243
|
+
top_level_module,
|
|
244
|
+
None,
|
|
245
|
+
DeferredImport(
|
|
246
|
+
self.module,
|
|
247
|
+
name.name,
|
|
248
|
+
top_level_module,
|
|
249
|
+
self.optimize,
|
|
250
|
+
self.compiler,
|
|
251
|
+
mod_to_return=top_level_module,
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
else:
|
|
255
|
+
self.module.declare_import(
|
|
256
|
+
asname,
|
|
257
|
+
None,
|
|
258
|
+
DeferredImport(
|
|
259
|
+
self.module, name.name, asname, self.optimize, self.compiler
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def visitImportFrom(self, node: ImportFrom) -> None:
|
|
264
|
+
mod_name = node.module
|
|
265
|
+
if not mod_name or node.level:
|
|
266
|
+
raise NotImplementedError("relative imports aren't supported")
|
|
267
|
+
for name in node.names:
|
|
268
|
+
child_name = name.asname or name.name
|
|
269
|
+
self.module.declare_import(
|
|
270
|
+
child_name,
|
|
271
|
+
(mod_name, name.name),
|
|
272
|
+
DeferredImport(
|
|
273
|
+
self.module,
|
|
274
|
+
mod_name,
|
|
275
|
+
name.name,
|
|
276
|
+
self.optimize,
|
|
277
|
+
self.compiler,
|
|
278
|
+
name.name,
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# We don't pick up declarations in nested statements
|
|
283
|
+
def visitFor(self, node: For) -> None:
|
|
284
|
+
self.enter_nested_scope()
|
|
285
|
+
self.generic_visit(node)
|
|
286
|
+
self.exit_scope()
|
|
287
|
+
|
|
288
|
+
def visitAsyncFor(self, node: AsyncFor) -> None:
|
|
289
|
+
self.enter_nested_scope()
|
|
290
|
+
self.generic_visit(node)
|
|
291
|
+
self.exit_scope()
|
|
292
|
+
|
|
293
|
+
def visitWhile(self, node: While) -> None:
|
|
294
|
+
self.enter_nested_scope()
|
|
295
|
+
self.generic_visit(node)
|
|
296
|
+
self.exit_scope()
|
|
297
|
+
|
|
298
|
+
def visitIf(self, node: If) -> None:
|
|
299
|
+
test = node.test
|
|
300
|
+
if isinstance(test, Name) and test.id == "TYPE_CHECKING":
|
|
301
|
+
self.visit_list(node.body)
|
|
302
|
+
else:
|
|
303
|
+
result = sys_hexversion_check(node)
|
|
304
|
+
# We should check the version if provided
|
|
305
|
+
if result is not None:
|
|
306
|
+
self.module.mark_known_boolean_test(test, value=bool(result))
|
|
307
|
+
if result:
|
|
308
|
+
self.visit_list(node.body)
|
|
309
|
+
else:
|
|
310
|
+
self.visit_list(node.orelse)
|
|
311
|
+
return
|
|
312
|
+
else:
|
|
313
|
+
self.enter_nested_scope()
|
|
314
|
+
self.visit_list(node.body)
|
|
315
|
+
self.exit_scope()
|
|
316
|
+
|
|
317
|
+
if node.orelse:
|
|
318
|
+
self.enter_nested_scope()
|
|
319
|
+
self.visit_list(node.orelse)
|
|
320
|
+
self.exit_scope()
|
|
321
|
+
|
|
322
|
+
def visitWith(self, node: With) -> None:
|
|
323
|
+
self.enter_nested_scope()
|
|
324
|
+
self.generic_visit(node)
|
|
325
|
+
self.exit_scope()
|
|
326
|
+
|
|
327
|
+
def visitAsyncWith(self, node: AsyncWith) -> None:
|
|
328
|
+
self.enter_nested_scope()
|
|
329
|
+
self.generic_visit(node)
|
|
330
|
+
self.exit_scope()
|
|
331
|
+
|
|
332
|
+
def visitTry(self, node: Try) -> None:
|
|
333
|
+
self.enter_nested_scope()
|
|
334
|
+
self.generic_visit(node)
|
|
335
|
+
self.exit_scope()
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# @oncall strictmod
|
|
2
|
+
|
|
3
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
# pyre-strict
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import ast
|
|
9
|
+
from ast import (
|
|
10
|
+
AnnAssign,
|
|
11
|
+
Assign,
|
|
12
|
+
AsyncFunctionDef,
|
|
13
|
+
AugAssign,
|
|
14
|
+
ClassDef,
|
|
15
|
+
For,
|
|
16
|
+
FunctionDef,
|
|
17
|
+
If,
|
|
18
|
+
Import,
|
|
19
|
+
ImportFrom,
|
|
20
|
+
Lambda,
|
|
21
|
+
Name,
|
|
22
|
+
NodeVisitor,
|
|
23
|
+
Raise,
|
|
24
|
+
stmt,
|
|
25
|
+
Try,
|
|
26
|
+
While,
|
|
27
|
+
With,
|
|
28
|
+
)
|
|
29
|
+
from typing import Iterable
|
|
30
|
+
|
|
31
|
+
from ..consts import SC_CELL, SC_LOCAL
|
|
32
|
+
from ..symbols import Scope
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DefiniteAssignmentVisitor(NodeVisitor):
|
|
36
|
+
def __init__(self, scope: Scope) -> None:
|
|
37
|
+
self.scope = scope
|
|
38
|
+
self.assigned: set[str] = set()
|
|
39
|
+
self.unassigned: set[Name] = set()
|
|
40
|
+
|
|
41
|
+
def analyzeFunction(self, node: FunctionDef | AsyncFunctionDef) -> None:
|
|
42
|
+
for arg in node.args.args:
|
|
43
|
+
self.assigned.add(arg.arg)
|
|
44
|
+
for arg in node.args.kwonlyargs:
|
|
45
|
+
self.assigned.add(arg.arg)
|
|
46
|
+
for arg in node.args.posonlyargs:
|
|
47
|
+
self.assigned.add(arg.arg)
|
|
48
|
+
|
|
49
|
+
vararg = node.args.vararg
|
|
50
|
+
if vararg:
|
|
51
|
+
self.assigned.add(vararg.arg)
|
|
52
|
+
kwarg = node.args.kwarg
|
|
53
|
+
if kwarg:
|
|
54
|
+
self.assigned.add(kwarg.arg)
|
|
55
|
+
for body_stmt in node.body:
|
|
56
|
+
self.visit(body_stmt)
|
|
57
|
+
|
|
58
|
+
def set_assigned(self, name: str) -> None:
|
|
59
|
+
if self.is_local(name):
|
|
60
|
+
self.assigned.add(name)
|
|
61
|
+
|
|
62
|
+
def is_local(self, name: str) -> bool:
|
|
63
|
+
scope = self.scope.check_name(name)
|
|
64
|
+
return scope == SC_LOCAL or scope == SC_CELL
|
|
65
|
+
|
|
66
|
+
def visit_Name(self, node: Name) -> None:
|
|
67
|
+
# we only walk the module and class defs and nested class defs
|
|
68
|
+
# can't access parent class def variables, so the variable is
|
|
69
|
+
# definitely assigned in the inner most class def
|
|
70
|
+
# TODO: T52624111 needs special handling for __class__
|
|
71
|
+
if not self.is_local(node.id):
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
if isinstance(node.ctx, ast.Load):
|
|
75
|
+
if node.id not in self.assigned:
|
|
76
|
+
self.unassigned.add(node)
|
|
77
|
+
elif isinstance(node.ctx, ast.Del):
|
|
78
|
+
if node.id not in self.assigned:
|
|
79
|
+
self.unassigned.add(node)
|
|
80
|
+
else:
|
|
81
|
+
self.assigned.remove(node.id)
|
|
82
|
+
else:
|
|
83
|
+
self.assigned.add(node.id)
|
|
84
|
+
|
|
85
|
+
def visit_Assign(self, node: Assign) -> None:
|
|
86
|
+
# generic_visit will evaluate these in a different order due to _fields
|
|
87
|
+
# being targets, value
|
|
88
|
+
self.visit(node.value)
|
|
89
|
+
for target in node.targets:
|
|
90
|
+
self.visit(target)
|
|
91
|
+
|
|
92
|
+
def visit_AugAssign(self, node: AugAssign) -> None:
|
|
93
|
+
target = node.target
|
|
94
|
+
if isinstance(target, ast.Name):
|
|
95
|
+
if target.id not in self.assigned:
|
|
96
|
+
self.unassigned.add(target)
|
|
97
|
+
self.generic_visit(node.value)
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
self.generic_visit(node)
|
|
101
|
+
|
|
102
|
+
def visit_Try(self, node: Try) -> None:
|
|
103
|
+
if not node.handlers:
|
|
104
|
+
# Try/finally, all assignments are guaranteed
|
|
105
|
+
entry = set(self.assigned)
|
|
106
|
+
self.walk_stmts(node.body)
|
|
107
|
+
|
|
108
|
+
post_try = set(self.assigned)
|
|
109
|
+
|
|
110
|
+
# finally is not guaranteed to have any try statements executed,
|
|
111
|
+
# but any deletes should be assumed
|
|
112
|
+
self.assigned = entry.intersection(post_try)
|
|
113
|
+
|
|
114
|
+
self.walk_stmts(node.finalbody)
|
|
115
|
+
|
|
116
|
+
# Anything the finally deletes is removed (the finally cannot delete
|
|
117
|
+
# things which weren't defined on entry, because they aren't definitely
|
|
118
|
+
# assigned)
|
|
119
|
+
for value in entry:
|
|
120
|
+
if value not in self.assigned and value in post_try:
|
|
121
|
+
post_try.remove(value)
|
|
122
|
+
#
|
|
123
|
+
# Anything that was assigned in the try is assigned after
|
|
124
|
+
post_try.update(self.assigned)
|
|
125
|
+
self.assigned = post_try
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
# try/except/maybe finally. Only the finally assignments are guaranteed
|
|
129
|
+
entry = set(self.assigned)
|
|
130
|
+
self.walk_stmts(node.body)
|
|
131
|
+
# Remove anything that got deleted...
|
|
132
|
+
|
|
133
|
+
elseentry = set(self.assigned)
|
|
134
|
+
entry.intersection_update(self.assigned)
|
|
135
|
+
|
|
136
|
+
finalentry = set(entry)
|
|
137
|
+
for handler in node.handlers:
|
|
138
|
+
self.assigned = set(entry)
|
|
139
|
+
handler_name = handler.name
|
|
140
|
+
if handler_name is not None:
|
|
141
|
+
self.set_assigned(handler_name)
|
|
142
|
+
|
|
143
|
+
self.walk_stmts(handler.body)
|
|
144
|
+
# All deletes for any entry apply to the finally
|
|
145
|
+
finalentry.intersection_update(self.assigned)
|
|
146
|
+
|
|
147
|
+
if node.orelse:
|
|
148
|
+
self.assigned = elseentry
|
|
149
|
+
self.walk_stmts(node.orelse)
|
|
150
|
+
finalentry.intersection_update(self.assigned)
|
|
151
|
+
|
|
152
|
+
self.assigned = finalentry
|
|
153
|
+
if node.finalbody:
|
|
154
|
+
self.walk_stmts(node.finalbody)
|
|
155
|
+
|
|
156
|
+
def visit_ClassDef(self, node: ClassDef) -> None:
|
|
157
|
+
for base in node.bases:
|
|
158
|
+
self.visit(base)
|
|
159
|
+
|
|
160
|
+
for kw in node.keywords:
|
|
161
|
+
self.visit(kw)
|
|
162
|
+
|
|
163
|
+
for dec in node.decorator_list:
|
|
164
|
+
self.visit(dec)
|
|
165
|
+
|
|
166
|
+
self.set_assigned(node.name)
|
|
167
|
+
|
|
168
|
+
def visit_FunctionDef(self, node: FunctionDef) -> None:
|
|
169
|
+
self._visit_func_like(node)
|
|
170
|
+
|
|
171
|
+
def visit_AsyncFunctionDef(self, node: AsyncFunctionDef) -> None:
|
|
172
|
+
self._visit_func_like(node)
|
|
173
|
+
|
|
174
|
+
def visit_Lambda(self, node: Lambda) -> None:
|
|
175
|
+
self.visit(node.args)
|
|
176
|
+
|
|
177
|
+
def _visit_func_like(self, node: FunctionDef | AsyncFunctionDef) -> None:
|
|
178
|
+
self.visit(node.args)
|
|
179
|
+
|
|
180
|
+
returns = node.returns
|
|
181
|
+
if returns:
|
|
182
|
+
self.visit(returns)
|
|
183
|
+
|
|
184
|
+
for dec in node.decorator_list:
|
|
185
|
+
self.visit(dec)
|
|
186
|
+
|
|
187
|
+
# Body doesn't run so isn't processed
|
|
188
|
+
self.set_assigned(node.name)
|
|
189
|
+
|
|
190
|
+
def visit_With(self, node: With) -> None:
|
|
191
|
+
# All of the with items will be definitely assigned
|
|
192
|
+
for item in node.items:
|
|
193
|
+
self.visit(item)
|
|
194
|
+
|
|
195
|
+
entry = set(self.assigned)
|
|
196
|
+
|
|
197
|
+
self.walk_stmts(node.body)
|
|
198
|
+
# No assignments are guaranteed, the body can throw and the context
|
|
199
|
+
# manager can suppress it. But all deletes need to be assumed to have
|
|
200
|
+
# occurred
|
|
201
|
+
entry.intersection_update(self.assigned)
|
|
202
|
+
self.assigned = entry
|
|
203
|
+
|
|
204
|
+
def visit_Import(self, node: Import) -> None:
|
|
205
|
+
for name in node.names:
|
|
206
|
+
self.set_assigned(name.asname or name.name.partition(".")[0])
|
|
207
|
+
|
|
208
|
+
def visit_ImportFrom(self, node: ImportFrom) -> None:
|
|
209
|
+
for name in node.names:
|
|
210
|
+
self.set_assigned(name.asname or name.name)
|
|
211
|
+
|
|
212
|
+
def visit_AnnAssign(self, node: AnnAssign) -> None:
|
|
213
|
+
# We won't actually define anything w/o a value
|
|
214
|
+
if node.value:
|
|
215
|
+
self.generic_visit(node)
|
|
216
|
+
|
|
217
|
+
# Don't need AsyncFor, AsyncWith Return as they can't be present at
|
|
218
|
+
# module / class level
|
|
219
|
+
|
|
220
|
+
def visit_For(self, node: For) -> None:
|
|
221
|
+
self.visit(node.iter)
|
|
222
|
+
|
|
223
|
+
entry = set(self.assigned)
|
|
224
|
+
self.visit(node.target)
|
|
225
|
+
self.walk_stmts(node.body)
|
|
226
|
+
|
|
227
|
+
# no assigns are guaranteed, need to assume deletes occur, and
|
|
228
|
+
# they impact the else block too.
|
|
229
|
+
entry.intersection_update(self.assigned)
|
|
230
|
+
if node.orelse:
|
|
231
|
+
self.assigned = set(entry)
|
|
232
|
+
self.walk_stmts(node.orelse)
|
|
233
|
+
|
|
234
|
+
# else may not run, so again no signs guaranteed, deletes are assumed
|
|
235
|
+
entry.intersection_update(self.assigned)
|
|
236
|
+
|
|
237
|
+
self.assigned = entry
|
|
238
|
+
|
|
239
|
+
def visit_If(self, node: If) -> None:
|
|
240
|
+
self.visit(node.test)
|
|
241
|
+
entry = set(self.assigned)
|
|
242
|
+
self.walk_stmts(node.body)
|
|
243
|
+
|
|
244
|
+
post_if = self.assigned
|
|
245
|
+
|
|
246
|
+
if node.orelse:
|
|
247
|
+
self.assigned = set(entry)
|
|
248
|
+
|
|
249
|
+
self.walk_stmts(node.orelse)
|
|
250
|
+
|
|
251
|
+
self.assigned = self.assigned.intersection(post_if)
|
|
252
|
+
else:
|
|
253
|
+
# Remove anything which was deleted
|
|
254
|
+
entry.intersection_update(post_if)
|
|
255
|
+
self.assigned = entry
|
|
256
|
+
|
|
257
|
+
def visit_While(self, node: While) -> None:
|
|
258
|
+
self.visit(node.test)
|
|
259
|
+
entry = set(self.assigned)
|
|
260
|
+
self.walk_stmts(node.body)
|
|
261
|
+
|
|
262
|
+
# Any dels will remove assignment
|
|
263
|
+
entry.intersection_update(self.assigned)
|
|
264
|
+
if node.orelse:
|
|
265
|
+
self.assigned = set(entry)
|
|
266
|
+
|
|
267
|
+
self.walk_stmts(node.orelse)
|
|
268
|
+
# Any dels will remove definite assignment
|
|
269
|
+
entry.intersection_update(self.assigned)
|
|
270
|
+
|
|
271
|
+
# While loop may never be entered, else may never be entered, so
|
|
272
|
+
# it never definitely assigns anything
|
|
273
|
+
self.assigned = entry
|
|
274
|
+
|
|
275
|
+
def walk_stmts(self, nodes: Iterable[stmt]) -> None:
|
|
276
|
+
for node in nodes:
|
|
277
|
+
self.visit(node)
|
|
278
|
+
if isinstance(node, Raise):
|
|
279
|
+
# following statements are unreachable
|
|
280
|
+
return
|