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,507 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
# pyre-strict
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import ast
|
|
7
|
+
import os.path
|
|
8
|
+
import symtable
|
|
9
|
+
import sys
|
|
10
|
+
import typing
|
|
11
|
+
from ast import (
|
|
12
|
+
alias,
|
|
13
|
+
AST,
|
|
14
|
+
AsyncFunctionDef,
|
|
15
|
+
ClassDef,
|
|
16
|
+
comprehension,
|
|
17
|
+
copy_location,
|
|
18
|
+
DictComp,
|
|
19
|
+
FunctionDef,
|
|
20
|
+
GeneratorExp,
|
|
21
|
+
iter_fields,
|
|
22
|
+
Lambda,
|
|
23
|
+
ListComp,
|
|
24
|
+
NodeVisitor,
|
|
25
|
+
SetComp,
|
|
26
|
+
Try,
|
|
27
|
+
)
|
|
28
|
+
from collections import deque
|
|
29
|
+
from collections.abc import Callable, Mapping, MutableMapping
|
|
30
|
+
from contextlib import nullcontext
|
|
31
|
+
from dataclasses import dataclass
|
|
32
|
+
from symtable import Class, SymbolTable
|
|
33
|
+
from typing import final, Generic, TypeVar
|
|
34
|
+
|
|
35
|
+
from .runtime import freeze_type, mutable
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Increment this whenever we change the output of the strict modules
|
|
39
|
+
# interpreter. It must stay below 32768 (15 bits), because we use the high bit
|
|
40
|
+
# to encode strictness of the module.
|
|
41
|
+
MAGIC_NUMBER = 55
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
DEFAULT_STUB_PATH: str = os.path.dirname(__file__) + "/stubs"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def make_fixed_modules() -> Mapping[str, Mapping[str, object]]:
|
|
48
|
+
typing_members = {}
|
|
49
|
+
for name in typing.__all__:
|
|
50
|
+
typing_members[name] = getattr(typing, name)
|
|
51
|
+
strict_mod_members = {
|
|
52
|
+
"freeze_type": freeze_type,
|
|
53
|
+
"mutable": mutable,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
"typing": typing_members,
|
|
58
|
+
"strict_modules": dict(strict_mod_members),
|
|
59
|
+
"__strict__": strict_mod_members,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
FIXED_MODULES: Mapping[str, Mapping[str, object]] = make_fixed_modules()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
TVar = TypeVar("TScope")
|
|
67
|
+
TScopeData = TypeVar("TData", covariant=True)
|
|
68
|
+
|
|
69
|
+
SymbolMap = dict[AST, SymbolTable]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class StrictModuleError(Exception):
|
|
74
|
+
msg: str
|
|
75
|
+
filename: str
|
|
76
|
+
lineno: int
|
|
77
|
+
col: int
|
|
78
|
+
metadata: str = ""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _is_scoped_generator_node(node: AST) -> bool:
|
|
82
|
+
return sys.version_info < (3, 12) or not isinstance(
|
|
83
|
+
node,
|
|
84
|
+
(
|
|
85
|
+
ast.ListComp,
|
|
86
|
+
ast.DictComp,
|
|
87
|
+
ast.SetComp,
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# SymbolTableType is new in 3.13
|
|
93
|
+
if sys.version_info >= (3, 14):
|
|
94
|
+
ANNOTATION_TYPE: str = symtable.SymbolTableType.ANNOTATION
|
|
95
|
+
|
|
96
|
+
def is_annotation(x: symtable.SymbolTable) -> bool:
|
|
97
|
+
return x.get_type() == ANNOTATION_TYPE
|
|
98
|
+
else:
|
|
99
|
+
|
|
100
|
+
def is_annotation(x: symtable.SymbolTable) -> bool:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@final
|
|
105
|
+
class SymbolMapBuilder(ast.NodeVisitor):
|
|
106
|
+
def __init__(self, symbols: SymbolTable) -> None:
|
|
107
|
+
self.symbol_stack: deque[SymbolTable] = deque([symbols])
|
|
108
|
+
children = self.symbol_stack.popleft().get_children()
|
|
109
|
+
self.symbol_stack.extendleft(
|
|
110
|
+
x for x in reversed(children) if not is_annotation(x)
|
|
111
|
+
)
|
|
112
|
+
self.mapping: SymbolMap = {}
|
|
113
|
+
|
|
114
|
+
def _process_scope_node(self, node: AST) -> None:
|
|
115
|
+
current_symbol = self.mapping[node] = self.symbol_stack.popleft()
|
|
116
|
+
children = current_symbol.get_children()
|
|
117
|
+
self.symbol_stack.extendleft(
|
|
118
|
+
x for x in reversed(children) if not is_annotation(x)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def visit_ClassDef(self, node: ClassDef) -> None:
|
|
122
|
+
for child in node.bases:
|
|
123
|
+
self.visit(child)
|
|
124
|
+
for child in node.keywords:
|
|
125
|
+
self.visit(child)
|
|
126
|
+
for child in node.decorator_list:
|
|
127
|
+
self.visit(child)
|
|
128
|
+
self._process_scope_node(node)
|
|
129
|
+
for child in node.body:
|
|
130
|
+
self.visit(child)
|
|
131
|
+
|
|
132
|
+
def visit_FunctionDef(self, node: FunctionDef) -> None:
|
|
133
|
+
self._visit_function_like(node)
|
|
134
|
+
|
|
135
|
+
def visit_AsyncFunctionDef(self, node: AsyncFunctionDef) -> None:
|
|
136
|
+
self._visit_function_like(node)
|
|
137
|
+
|
|
138
|
+
def visit_Lambda(self, node: Lambda) -> None:
|
|
139
|
+
if node.args:
|
|
140
|
+
self.visit(node.args)
|
|
141
|
+
self._process_scope_node(node)
|
|
142
|
+
self.visit(node.body)
|
|
143
|
+
|
|
144
|
+
def _visit_function_like(self, node: FunctionDef | AsyncFunctionDef) -> None:
|
|
145
|
+
# args -> returns -> decorator_list -> body
|
|
146
|
+
if node.args:
|
|
147
|
+
self.visit(node.args)
|
|
148
|
+
returns = node.returns
|
|
149
|
+
if returns:
|
|
150
|
+
self.visit(returns)
|
|
151
|
+
for child in node.decorator_list:
|
|
152
|
+
self.visit(child)
|
|
153
|
+
self._process_scope_node(node)
|
|
154
|
+
for child in node.body:
|
|
155
|
+
self.visit(child)
|
|
156
|
+
|
|
157
|
+
def visit_ListComp(self, node: ListComp) -> None:
|
|
158
|
+
self._visit_generator_like(node, [node.elt], node.generators)
|
|
159
|
+
|
|
160
|
+
def visit_SetComp(self, node: SetComp) -> None:
|
|
161
|
+
self._visit_generator_like(node, [node.elt], node.generators)
|
|
162
|
+
|
|
163
|
+
def visit_DictComp(self, node: DictComp) -> None:
|
|
164
|
+
self._visit_generator_like(node, [node.key, node.value], node.generators)
|
|
165
|
+
|
|
166
|
+
def visit_GeneratorExp(self, node: GeneratorExp) -> None:
|
|
167
|
+
self._visit_generator_like(node, [node.elt], node.generators)
|
|
168
|
+
|
|
169
|
+
def _visit_generator_like(
|
|
170
|
+
self, node: AST, elements: list[AST], comprehensions: list[comprehension]
|
|
171
|
+
) -> None:
|
|
172
|
+
# first iter is visited in the outer scope
|
|
173
|
+
self.visit(comprehensions[0].iter)
|
|
174
|
+
# everything else is in the inner scope
|
|
175
|
+
if _is_scoped_generator_node(node):
|
|
176
|
+
# In 3.12 list comprehensions are inlined
|
|
177
|
+
self._process_scope_node(node)
|
|
178
|
+
# process first comprehension, without iter
|
|
179
|
+
for child in comprehensions[0].ifs:
|
|
180
|
+
self.visit(child)
|
|
181
|
+
for comp in comprehensions[1:]:
|
|
182
|
+
self.visit(comp)
|
|
183
|
+
# process elements
|
|
184
|
+
for element in elements:
|
|
185
|
+
self.visit(element)
|
|
186
|
+
|
|
187
|
+
def visit_Try(self, node: Try) -> None:
|
|
188
|
+
# Need to match the order the symbol visitor constructs these in, which
|
|
189
|
+
# walks orelse before handlers.
|
|
190
|
+
for val in node.body:
|
|
191
|
+
self.visit(val)
|
|
192
|
+
for val in node.orelse:
|
|
193
|
+
self.visit(val)
|
|
194
|
+
for val in node.handlers:
|
|
195
|
+
self.visit(val)
|
|
196
|
+
for val in node.finalbody:
|
|
197
|
+
self.visit(val)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def get_symbol_map(node: ast.AST, symtable: SymbolTable) -> SymbolMap:
|
|
201
|
+
visitor = SymbolMapBuilder(symtable)
|
|
202
|
+
visitor.visit(node)
|
|
203
|
+
return visitor.mapping
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@final
|
|
207
|
+
class SymbolScope(Generic[TVar, TScopeData]):
|
|
208
|
+
def __init__(
|
|
209
|
+
self,
|
|
210
|
+
symbols: SymbolTable,
|
|
211
|
+
scope_data: TScopeData,
|
|
212
|
+
vars: MutableMapping[str, TVar] | None = None,
|
|
213
|
+
invisible: bool = False,
|
|
214
|
+
) -> None:
|
|
215
|
+
self.symbols = symbols
|
|
216
|
+
self.vars = vars
|
|
217
|
+
self.scope_data = scope_data
|
|
218
|
+
self.invisible = invisible
|
|
219
|
+
|
|
220
|
+
def __getitem__(self, name: str) -> TVar:
|
|
221
|
+
v = self.vars
|
|
222
|
+
if v is None:
|
|
223
|
+
raise KeyError(name)
|
|
224
|
+
|
|
225
|
+
return v[name]
|
|
226
|
+
|
|
227
|
+
def __setitem__(self, name: str, value: TVar) -> None:
|
|
228
|
+
v = self.vars
|
|
229
|
+
if v is None:
|
|
230
|
+
v = self.vars = {}
|
|
231
|
+
|
|
232
|
+
v[name] = value
|
|
233
|
+
|
|
234
|
+
def __delitem__(self, name: str) -> None:
|
|
235
|
+
v = self.vars
|
|
236
|
+
if v is None:
|
|
237
|
+
raise KeyError(name)
|
|
238
|
+
|
|
239
|
+
del v[name]
|
|
240
|
+
|
|
241
|
+
def __contains__(self, name: str) -> bool:
|
|
242
|
+
v = self.vars
|
|
243
|
+
if v is None:
|
|
244
|
+
return False
|
|
245
|
+
|
|
246
|
+
return name in v
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def mangle_priv_name(name: str, scopes: list[SymbolScope[TVar, TScopeData]]) -> str:
|
|
250
|
+
if name.startswith("__") and not name.endswith("__"):
|
|
251
|
+
# symtable has name mangled private names. Walk the scope list
|
|
252
|
+
# backwards and apply the mangled class name
|
|
253
|
+
for scope in reversed(scopes):
|
|
254
|
+
if isinstance(scope.symbols, symtable.Class) and not scope.invisible:
|
|
255
|
+
return "_" + scope.symbols.get_name().lstrip("_") + name
|
|
256
|
+
|
|
257
|
+
return name
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def imported_name(name: alias) -> str:
|
|
261
|
+
return name.asname or name.name.partition(".")[0]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@final
|
|
265
|
+
class ScopeContextManager(Generic[TVar, TScopeData]):
|
|
266
|
+
def __init__(
|
|
267
|
+
self, parent: ScopeStack[TVar, TScopeData], scope: SymbolScope[TVar, TScopeData]
|
|
268
|
+
) -> None:
|
|
269
|
+
self.parent = parent
|
|
270
|
+
self.scope = scope
|
|
271
|
+
|
|
272
|
+
def __enter__(self) -> SymbolScope[TVar, TScopeData]:
|
|
273
|
+
self.parent.push(self.scope)
|
|
274
|
+
return self.scope
|
|
275
|
+
|
|
276
|
+
def __exit__(
|
|
277
|
+
self, exc_type: type[Exception], exc_val: Exception, exc_tb: object
|
|
278
|
+
) -> None:
|
|
279
|
+
self.parent.pop()
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@final
|
|
283
|
+
class ScopeStack(Generic[TVar, TScopeData]):
|
|
284
|
+
def __init__(
|
|
285
|
+
self,
|
|
286
|
+
*scopes: SymbolScope[TVar, TScopeData],
|
|
287
|
+
symbol_map: SymbolMap,
|
|
288
|
+
scope_factory: Callable[
|
|
289
|
+
[SymbolTable, AST, MutableMapping[str, TVar] | None],
|
|
290
|
+
SymbolScope[TVar, TScopeData],
|
|
291
|
+
] = lambda symtable, node, vars: SymbolScope(symtable, None),
|
|
292
|
+
) -> None:
|
|
293
|
+
self.scopes: list[SymbolScope[TVar, TScopeData]] = list(scopes)
|
|
294
|
+
self.symbol_map = symbol_map
|
|
295
|
+
self.scope_factory: Callable[
|
|
296
|
+
[SymbolTable, AST, MutableMapping[str, TVar] | None],
|
|
297
|
+
SymbolScope[TVar, TScopeData],
|
|
298
|
+
] = scope_factory
|
|
299
|
+
|
|
300
|
+
def push(self, scope: SymbolScope[TVar, TScopeData]) -> None:
|
|
301
|
+
self.scopes.append(scope)
|
|
302
|
+
|
|
303
|
+
def pop(self) -> None:
|
|
304
|
+
self.scopes.pop()
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def current_symbols(self) -> SymbolTable:
|
|
308
|
+
return self.scopes[-1].symbols
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def current(self) -> SymbolScope[TVar, TScopeData]:
|
|
312
|
+
return self.scopes[-1]
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def in_global_scope(self) -> bool:
|
|
316
|
+
return len(self.scopes) == 1
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def in_class_scope(self) -> bool:
|
|
320
|
+
return isinstance(self.scopes[-1].symbols, Class)
|
|
321
|
+
|
|
322
|
+
def with_node_scope(
|
|
323
|
+
self, node: AST, vars: MutableMapping[str, TVar] | None = None
|
|
324
|
+
) -> ScopeContextManager[TVar, TScopeData] | nullcontext[None]:
|
|
325
|
+
if not _is_scoped_generator_node(node):
|
|
326
|
+
assert isinstance(node, (ast.ListComp, ast.DictComp, ast.SetComp))
|
|
327
|
+
# In 3.12 list/dict/set comprehensions are inlined
|
|
328
|
+
return nullcontext()
|
|
329
|
+
|
|
330
|
+
next_symtable = self.symbol_map[node]
|
|
331
|
+
return ScopeContextManager(self, self.scope_factory(next_symtable, node, vars))
|
|
332
|
+
|
|
333
|
+
def is_global(self, name: str) -> bool:
|
|
334
|
+
if self.in_global_scope:
|
|
335
|
+
return True
|
|
336
|
+
|
|
337
|
+
return self.current_symbols.lookup(
|
|
338
|
+
mangle_priv_name(name, self.scopes)
|
|
339
|
+
).is_global()
|
|
340
|
+
|
|
341
|
+
def is_nonlocal(self, name: str) -> bool:
|
|
342
|
+
return (
|
|
343
|
+
not self.is_global(name)
|
|
344
|
+
and self.current_symbols.lookup(
|
|
345
|
+
mangle_priv_name(name, self.scopes)
|
|
346
|
+
).is_free()
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
def is_local(self, name: str) -> bool:
|
|
350
|
+
return self.current_symbols.lookup(
|
|
351
|
+
mangle_priv_name(name, self.scopes)
|
|
352
|
+
).is_local()
|
|
353
|
+
|
|
354
|
+
def scope_for(self, name: str) -> SymbolScope[TVar, TScopeData]:
|
|
355
|
+
if self.is_global(name):
|
|
356
|
+
return self.scopes[0]
|
|
357
|
+
elif self.is_nonlocal(name):
|
|
358
|
+
for scope in reversed(self.scopes):
|
|
359
|
+
lookup = scope.symbols.lookup(name)
|
|
360
|
+
if lookup.is_global() or lookup.is_free():
|
|
361
|
+
return scope
|
|
362
|
+
|
|
363
|
+
return self.scopes[-1]
|
|
364
|
+
|
|
365
|
+
def __getitem__(self, name: str) -> TVar:
|
|
366
|
+
is_leaf = True
|
|
367
|
+
for scope in reversed(self.scopes):
|
|
368
|
+
if (is_leaf or not isinstance(scope.symbols, Class)) and name in scope:
|
|
369
|
+
return scope[name]
|
|
370
|
+
is_leaf = False
|
|
371
|
+
raise KeyError(f"{name} not found in scope")
|
|
372
|
+
|
|
373
|
+
def __contains__(self, name: str) -> bool:
|
|
374
|
+
is_leaf = True
|
|
375
|
+
for scope in reversed(self.scopes):
|
|
376
|
+
if (is_leaf or not isinstance(scope.symbols, Class)) and name in scope:
|
|
377
|
+
return True
|
|
378
|
+
is_leaf = False
|
|
379
|
+
return False
|
|
380
|
+
|
|
381
|
+
def __setitem__(self, name: str, value: TVar) -> None:
|
|
382
|
+
if self.is_global(name):
|
|
383
|
+
self.scopes[0][name] = value
|
|
384
|
+
elif self.is_nonlocal(name):
|
|
385
|
+
for scope in reversed(self.scopes[:-1]):
|
|
386
|
+
if name in scope:
|
|
387
|
+
scope[name] = value
|
|
388
|
+
break
|
|
389
|
+
else:
|
|
390
|
+
current_scope = self.scopes[-1]
|
|
391
|
+
current_scope[name] = value
|
|
392
|
+
|
|
393
|
+
def __delitem__(self, name: str) -> None:
|
|
394
|
+
if self.is_global(name):
|
|
395
|
+
if name in self.scopes[0]:
|
|
396
|
+
del self.scopes[0][name]
|
|
397
|
+
elif self.is_nonlocal(name):
|
|
398
|
+
for scope in reversed(self.scopes[:-1]):
|
|
399
|
+
if name in scope:
|
|
400
|
+
del scope[name]
|
|
401
|
+
break
|
|
402
|
+
else:
|
|
403
|
+
current_scope = self.scopes[-1]
|
|
404
|
+
if name in current_scope:
|
|
405
|
+
del current_scope[name]
|
|
406
|
+
|
|
407
|
+
def local_contains(self, name: str) -> bool:
|
|
408
|
+
return name in self.scopes[-1]
|
|
409
|
+
|
|
410
|
+
def shallow_copy(self) -> ScopeStack[TVar, TScopeData]:
|
|
411
|
+
# same underlying stack but a different dict
|
|
412
|
+
return ScopeStack(
|
|
413
|
+
*(d for d in self.scopes),
|
|
414
|
+
symbol_map=self.symbol_map,
|
|
415
|
+
scope_factory=self.scope_factory,
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
def with_scope(
|
|
419
|
+
self, scope: SymbolScope[TVar, TScopeData]
|
|
420
|
+
) -> ScopeContextManager[TVar, TScopeData]:
|
|
421
|
+
return ScopeContextManager(self, scope)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
TAst = TypeVar("TAst", bound=AST)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class AstRewriter(NodeVisitor):
|
|
428
|
+
"""performs rewrites on the AST, but only produces new nodes, rather than
|
|
429
|
+
modifying nodes in place like ast.NodeTransformer."""
|
|
430
|
+
|
|
431
|
+
@staticmethod
|
|
432
|
+
def update_node(node: TAst, **replacement: object) -> TAst:
|
|
433
|
+
res = node
|
|
434
|
+
for name, val in replacement.items():
|
|
435
|
+
existing = getattr(res, name)
|
|
436
|
+
if existing == val:
|
|
437
|
+
continue
|
|
438
|
+
|
|
439
|
+
if node is res:
|
|
440
|
+
res = AstRewriter.clone_node(node)
|
|
441
|
+
|
|
442
|
+
setattr(res, name, val)
|
|
443
|
+
return res
|
|
444
|
+
|
|
445
|
+
@staticmethod
|
|
446
|
+
def clone_node(node: TAst) -> TAst:
|
|
447
|
+
attrs = []
|
|
448
|
+
for name in node._fields:
|
|
449
|
+
attr = getattr(node, name, None)
|
|
450
|
+
if isinstance(attr, list):
|
|
451
|
+
attr = list(attr)
|
|
452
|
+
attrs.append(attr)
|
|
453
|
+
|
|
454
|
+
new = type(node)(*attrs)
|
|
455
|
+
return copy_location(new, node)
|
|
456
|
+
|
|
457
|
+
def walk_list(self, old_values: list[TAst]) -> list[TAst]:
|
|
458
|
+
new_values = []
|
|
459
|
+
changed = False
|
|
460
|
+
for old_value in old_values:
|
|
461
|
+
if isinstance(old_value, AST):
|
|
462
|
+
new_value = self.visit(old_value)
|
|
463
|
+
changed |= new_value is not old_value
|
|
464
|
+
if new_value is None:
|
|
465
|
+
continue
|
|
466
|
+
elif not isinstance(new_value, AST):
|
|
467
|
+
new_values.extend(new_value)
|
|
468
|
+
continue
|
|
469
|
+
|
|
470
|
+
new_values.append(new_value)
|
|
471
|
+
else:
|
|
472
|
+
new_values.append(old_value)
|
|
473
|
+
return new_values if changed else old_values
|
|
474
|
+
|
|
475
|
+
def generic_visit(self, node: TAst) -> TAst:
|
|
476
|
+
ret_node = node
|
|
477
|
+
for field, old_value in iter_fields(node):
|
|
478
|
+
if isinstance(old_value, list):
|
|
479
|
+
new_values = self.walk_list(old_value)
|
|
480
|
+
if new_values != old_value:
|
|
481
|
+
if ret_node is node:
|
|
482
|
+
ret_node = self.clone_node(node)
|
|
483
|
+
setattr(ret_node, field, new_values)
|
|
484
|
+
elif isinstance(old_value, AST):
|
|
485
|
+
new_node = self.visit(old_value)
|
|
486
|
+
assert new_node is not None, (
|
|
487
|
+
"can't remove AST nodes that aren't part of a list"
|
|
488
|
+
)
|
|
489
|
+
if new_node is not old_value:
|
|
490
|
+
if ret_node is node:
|
|
491
|
+
ret_node = self.clone_node(node)
|
|
492
|
+
|
|
493
|
+
setattr(ret_node, field, new_node)
|
|
494
|
+
|
|
495
|
+
return ret_node
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def lineinfo(node: TAst, target: AST | None = None) -> TAst:
|
|
499
|
+
if not target:
|
|
500
|
+
# set lineno to -1 to indicate non-user code
|
|
501
|
+
node.lineno = -1
|
|
502
|
+
node.col_offset = -1
|
|
503
|
+
node.end_lineno = -1
|
|
504
|
+
node.end_col_offset = -1
|
|
505
|
+
else:
|
|
506
|
+
copy_location(node, target)
|
|
507
|
+
return node
|