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,975 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
# pyre-strict
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
import sys
|
|
9
|
+
from ast import (
|
|
10
|
+
alias,
|
|
11
|
+
AnnAssign,
|
|
12
|
+
arg,
|
|
13
|
+
Assign,
|
|
14
|
+
AST,
|
|
15
|
+
AsyncFunctionDef,
|
|
16
|
+
Attribute,
|
|
17
|
+
Call,
|
|
18
|
+
ClassDef,
|
|
19
|
+
Constant,
|
|
20
|
+
copy_location,
|
|
21
|
+
DictComp,
|
|
22
|
+
expr,
|
|
23
|
+
FunctionDef,
|
|
24
|
+
GeneratorExp,
|
|
25
|
+
Global,
|
|
26
|
+
Import,
|
|
27
|
+
ImportFrom,
|
|
28
|
+
Lambda,
|
|
29
|
+
ListComp,
|
|
30
|
+
Module,
|
|
31
|
+
Name,
|
|
32
|
+
NodeVisitor,
|
|
33
|
+
SetComp,
|
|
34
|
+
stmt,
|
|
35
|
+
Try,
|
|
36
|
+
)
|
|
37
|
+
from symtable import SymbolTable
|
|
38
|
+
from types import ModuleType
|
|
39
|
+
from typing import (
|
|
40
|
+
cast,
|
|
41
|
+
final,
|
|
42
|
+
Generic,
|
|
43
|
+
Iterable,
|
|
44
|
+
List,
|
|
45
|
+
Mapping,
|
|
46
|
+
MutableMapping,
|
|
47
|
+
Optional,
|
|
48
|
+
Sequence,
|
|
49
|
+
TypeVar,
|
|
50
|
+
Union,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
from ..common import (
|
|
54
|
+
AstRewriter,
|
|
55
|
+
get_symbol_map,
|
|
56
|
+
imported_name,
|
|
57
|
+
lineinfo,
|
|
58
|
+
mangle_priv_name,
|
|
59
|
+
ScopeStack,
|
|
60
|
+
SymbolMap,
|
|
61
|
+
SymbolScope,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# We want to transform a module which looks something like:
|
|
66
|
+
#
|
|
67
|
+
# from strict_modules import cached_property
|
|
68
|
+
#
|
|
69
|
+
# x = 1
|
|
70
|
+
# def f():
|
|
71
|
+
# return min(x, 20)
|
|
72
|
+
#
|
|
73
|
+
# class C:
|
|
74
|
+
# @cached_property
|
|
75
|
+
# def f(self): return 42
|
|
76
|
+
#
|
|
77
|
+
# C.foo = 42
|
|
78
|
+
#
|
|
79
|
+
# def g():
|
|
80
|
+
# class C: pass
|
|
81
|
+
# return C
|
|
82
|
+
#
|
|
83
|
+
# Into something like:
|
|
84
|
+
#
|
|
85
|
+
# min = <builtins>.min # we have a fix set of builtins we execute against
|
|
86
|
+
#
|
|
87
|
+
# # We want some helper methods to be available to us:
|
|
88
|
+
# <strict-modules> = <fixed-modules>["strict_modules"]
|
|
89
|
+
#
|
|
90
|
+
# x = 1
|
|
91
|
+
# def f():
|
|
92
|
+
# return min(x, 20)
|
|
93
|
+
#
|
|
94
|
+
# @<init-cached-properties>({'f': '_f_impl'})
|
|
95
|
+
# class C:
|
|
96
|
+
# __slots__ = ('f', )
|
|
97
|
+
|
|
98
|
+
# def _f_impl(self):
|
|
99
|
+
# return 42
|
|
100
|
+
#
|
|
101
|
+
#
|
|
102
|
+
# C.foo = 42
|
|
103
|
+
#
|
|
104
|
+
# def g():
|
|
105
|
+
# class C: pass
|
|
106
|
+
# return C
|
|
107
|
+
# <classes> = []
|
|
108
|
+
#
|
|
109
|
+
#
|
|
110
|
+
#
|
|
111
|
+
# # Top-level assigned names
|
|
112
|
+
#
|
|
113
|
+
# Note: we use non-valid Python identifeirs for anything which we introduce like
|
|
114
|
+
# <strict-module> and use valid Python identifiers for variables which will be accessible
|
|
115
|
+
# from within the module.
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def make_arg(name: str) -> arg:
|
|
119
|
+
return lineinfo(ast.arg(name, None))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
TAst = TypeVar("TAst", bound=AST)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def make_assign(*a: object, **kw: object) -> Assign:
|
|
126
|
+
# pyre-fixme[6]: For 1st argument expected `List[expr]` but got `object`.
|
|
127
|
+
# pyre-fixme[6]: For 2nd argument expected `expr` but got `object`.
|
|
128
|
+
# pyre-fixme[6]: For 3rd argument expected `Optional[str]` but got `object`.
|
|
129
|
+
node = Assign(*a, **kw)
|
|
130
|
+
node.type_comment = None
|
|
131
|
+
return node
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def copyline(from_node: AST, to_node: TAst) -> TAst:
|
|
135
|
+
# pyre-fixme[16]: `AST` has no attribute `lineno`.
|
|
136
|
+
to_node.lineno = from_node.lineno
|
|
137
|
+
# pyre-fixme[16]: `AST` has no attribute `col_offset`.
|
|
138
|
+
to_node.col_offset = from_node.col_offset
|
|
139
|
+
return to_node
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
_IMPLICIT_GLOBALS = [
|
|
143
|
+
"__name__",
|
|
144
|
+
"__loader__",
|
|
145
|
+
"__package__",
|
|
146
|
+
"__spec__",
|
|
147
|
+
"__path__",
|
|
148
|
+
"__file__",
|
|
149
|
+
"__cached__",
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def make_function(name: str, pos_args: list[arg]) -> FunctionDef:
|
|
154
|
+
# pyre-fixme[20]: Argument `args` expected.
|
|
155
|
+
func = lineinfo(ast.FunctionDef())
|
|
156
|
+
func.name = name
|
|
157
|
+
# pyre-fixme[20]: Argument `args` expected.
|
|
158
|
+
args = ast.arguments()
|
|
159
|
+
args.kwonlyargs = []
|
|
160
|
+
args.kw_defaults = []
|
|
161
|
+
args.defaults = []
|
|
162
|
+
args.args = pos_args
|
|
163
|
+
args.posonlyargs = []
|
|
164
|
+
func.args = args
|
|
165
|
+
args.kwarg = None
|
|
166
|
+
args.vararg = None
|
|
167
|
+
func.decorator_list = []
|
|
168
|
+
func.returns = None
|
|
169
|
+
func.type_comment = ""
|
|
170
|
+
return func
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def make_assign_empty_list(name: str) -> Assign:
|
|
174
|
+
return lineinfo(
|
|
175
|
+
make_assign(
|
|
176
|
+
[lineinfo(ast.Name(name, ast.Store()))],
|
|
177
|
+
lineinfo(ast.List([], ast.Load())),
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
GLOBALS_HELPER_ALIAS = "<globals-helper>"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@final
|
|
186
|
+
class StrictModuleRewriter:
|
|
187
|
+
"""rewrites a module body so that all global variables are transformed into
|
|
188
|
+
local variables, and are closed over by the enclosing functions. This will
|
|
189
|
+
ultimately remove all LOAD_GLOBAL/STORE_GLOBAL opcodes, and therefore will
|
|
190
|
+
also have the side effect of making the module read-only as globals will
|
|
191
|
+
not be exposed."""
|
|
192
|
+
|
|
193
|
+
def __init__(
|
|
194
|
+
self,
|
|
195
|
+
root: Module,
|
|
196
|
+
table: SymbolTable,
|
|
197
|
+
filename: str,
|
|
198
|
+
modname: str,
|
|
199
|
+
mode: str,
|
|
200
|
+
optimize: int,
|
|
201
|
+
builtins: ModuleType | Mapping[str, object] = __builtins__,
|
|
202
|
+
is_static: bool = False,
|
|
203
|
+
) -> None:
|
|
204
|
+
if not isinstance(builtins, dict):
|
|
205
|
+
builtins = builtins.__dict__
|
|
206
|
+
|
|
207
|
+
self.root = root
|
|
208
|
+
self.table = table
|
|
209
|
+
self.filename = filename
|
|
210
|
+
self.modname = modname
|
|
211
|
+
self.mode = mode
|
|
212
|
+
self.optimize = optimize
|
|
213
|
+
self.builtins: Mapping[str, object] = builtins
|
|
214
|
+
self.symbol_map: SymbolMap = get_symbol_map(root, table)
|
|
215
|
+
scope: SymbolScope[None, None] = SymbolScope(table, None)
|
|
216
|
+
self.visitor = ImmutableVisitor(ScopeStack(scope, symbol_map=self.symbol_map))
|
|
217
|
+
# Top-level statements in the returned code object...
|
|
218
|
+
self.code_stmts: list[stmt] = []
|
|
219
|
+
self.is_static = is_static
|
|
220
|
+
|
|
221
|
+
def transform(self) -> ast.Module:
|
|
222
|
+
original_first_node = self.root.body[0] if self.root.body else None
|
|
223
|
+
self.visitor.visit(self.root)
|
|
224
|
+
self.visitor.global_sets.update(_IMPLICIT_GLOBALS)
|
|
225
|
+
|
|
226
|
+
for argname in _IMPLICIT_GLOBALS:
|
|
227
|
+
self.visitor.globals.add(argname)
|
|
228
|
+
|
|
229
|
+
# pyre-fixme[20]: Argument `type_ignores` expected.
|
|
230
|
+
mod = ast.Module(
|
|
231
|
+
[
|
|
232
|
+
*self.get_future_imports(),
|
|
233
|
+
*self.transform_body(),
|
|
234
|
+
]
|
|
235
|
+
)
|
|
236
|
+
if mod.body and original_first_node:
|
|
237
|
+
# this isn't obvious but the new mod body is empty
|
|
238
|
+
# if the original module body is empty. Therefore there
|
|
239
|
+
# is always a location to copy
|
|
240
|
+
copy_location(mod.body[0], original_first_node)
|
|
241
|
+
|
|
242
|
+
mod.type_ignores = []
|
|
243
|
+
return mod
|
|
244
|
+
|
|
245
|
+
def get_future_imports(self) -> Iterable[stmt]:
|
|
246
|
+
if self.visitor.future_imports:
|
|
247
|
+
yield lineinfo(
|
|
248
|
+
ImportFrom("__future__", list(self.visitor.future_imports), 0)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
def del_global(self, name: str) -> stmt:
|
|
252
|
+
return lineinfo(ast.Delete([lineinfo(ast.Name(name, ast.Del()))]))
|
|
253
|
+
|
|
254
|
+
def store_global(self, name: str, value: expr) -> stmt:
|
|
255
|
+
return lineinfo(make_assign([lineinfo(ast.Name(name, ast.Store()))], value))
|
|
256
|
+
|
|
257
|
+
def load_global(self, name: str) -> expr:
|
|
258
|
+
return lineinfo(ast.Name(name, ast.Load()))
|
|
259
|
+
|
|
260
|
+
def create_annotations(self) -> stmt:
|
|
261
|
+
return self.store_global("__annotations__", lineinfo(ast.Dict([], [])))
|
|
262
|
+
|
|
263
|
+
def make_transformer(
|
|
264
|
+
self,
|
|
265
|
+
scopes: ScopeStack[None, ScopeData],
|
|
266
|
+
) -> ImmutableTransformer:
|
|
267
|
+
return ImmutableTransformer(
|
|
268
|
+
scopes,
|
|
269
|
+
self.modname,
|
|
270
|
+
self.builtins,
|
|
271
|
+
self.visitor.globals,
|
|
272
|
+
self.visitor.global_sets,
|
|
273
|
+
self.visitor.global_dels,
|
|
274
|
+
self.visitor.future_imports,
|
|
275
|
+
self.is_static,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
def transform_body(self) -> Iterable[stmt]:
|
|
279
|
+
scopes = ScopeStack(
|
|
280
|
+
SymbolScope(self.table, ScopeData()), symbol_map=self.symbol_map
|
|
281
|
+
)
|
|
282
|
+
transformer = self.make_transformer(scopes)
|
|
283
|
+
body = transformer.visit(self.root).body
|
|
284
|
+
|
|
285
|
+
return body
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def rewrite(
|
|
289
|
+
root: Module,
|
|
290
|
+
table: SymbolTable,
|
|
291
|
+
filename: str,
|
|
292
|
+
modname: str,
|
|
293
|
+
mode: str = "exec",
|
|
294
|
+
optimize: int = -1,
|
|
295
|
+
builtins: ModuleType | Mapping[str, object] = __builtins__,
|
|
296
|
+
is_static: bool = False,
|
|
297
|
+
) -> Module:
|
|
298
|
+
return StrictModuleRewriter(
|
|
299
|
+
root,
|
|
300
|
+
table,
|
|
301
|
+
filename,
|
|
302
|
+
modname,
|
|
303
|
+
mode,
|
|
304
|
+
optimize,
|
|
305
|
+
builtins,
|
|
306
|
+
is_static=is_static,
|
|
307
|
+
).transform()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
TTransformedStmt = Union[Optional[AST], List[AST]]
|
|
311
|
+
TVar = TypeVar("TScope")
|
|
312
|
+
TScopeData = TypeVar("TData")
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class SymbolVisitor(Generic[TVar, TScopeData], NodeVisitor):
|
|
316
|
+
def __init__(self, scopes: ScopeStack[TVar, TScopeData]) -> None:
|
|
317
|
+
self.scopes = scopes
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def skip_annotations(self) -> bool:
|
|
321
|
+
return False
|
|
322
|
+
|
|
323
|
+
def is_global(self, name: str) -> bool:
|
|
324
|
+
return self.scopes.is_global(name)
|
|
325
|
+
|
|
326
|
+
def scope_for(self, name: str) -> SymbolScope[TVar, TScopeData]:
|
|
327
|
+
return self.scopes.scope_for(name)
|
|
328
|
+
|
|
329
|
+
def visit_Comp_Outer(
|
|
330
|
+
self,
|
|
331
|
+
node: ListComp | SetComp | GeneratorExp | DictComp,
|
|
332
|
+
update: bool = False,
|
|
333
|
+
) -> None:
|
|
334
|
+
iter = self.visit(node.generators[0].iter)
|
|
335
|
+
if update:
|
|
336
|
+
node.generators[0].iter = iter
|
|
337
|
+
|
|
338
|
+
def visit_Try(self, node: Try) -> TTransformedStmt:
|
|
339
|
+
# Need to match the order the symbol visitor constructs these in, which
|
|
340
|
+
# walks orelse before handlers.
|
|
341
|
+
for val in node.body:
|
|
342
|
+
self.visit(val)
|
|
343
|
+
|
|
344
|
+
for val in node.orelse:
|
|
345
|
+
self.visit(val)
|
|
346
|
+
|
|
347
|
+
for val in node.handlers:
|
|
348
|
+
self.visit(val)
|
|
349
|
+
|
|
350
|
+
for val in node.finalbody:
|
|
351
|
+
self.visit(val)
|
|
352
|
+
|
|
353
|
+
return node
|
|
354
|
+
|
|
355
|
+
def visit_Comp_Inner(
|
|
356
|
+
self,
|
|
357
|
+
node: ListComp | SetComp | GeneratorExp | DictComp,
|
|
358
|
+
update: bool = False,
|
|
359
|
+
scope_node: ListComp | SetComp | GeneratorExp | DictComp | None = None,
|
|
360
|
+
) -> None:
|
|
361
|
+
scope_node = scope_node or node
|
|
362
|
+
with self.scopes.with_node_scope(scope_node):
|
|
363
|
+
if isinstance(node, DictComp):
|
|
364
|
+
key = self.visit(node.key)
|
|
365
|
+
if update:
|
|
366
|
+
node.key = key
|
|
367
|
+
value = self.visit(node.value)
|
|
368
|
+
if update:
|
|
369
|
+
node.value = value
|
|
370
|
+
else:
|
|
371
|
+
elt = self.visit(node.elt)
|
|
372
|
+
if update:
|
|
373
|
+
node.elt = elt
|
|
374
|
+
|
|
375
|
+
self.walk_many(node.generators[0].ifs, update)
|
|
376
|
+
|
|
377
|
+
target = self.visit(node.generators[0].target)
|
|
378
|
+
if update:
|
|
379
|
+
# pyre-fixme[16]: `DictComp` has no attribute `target`.
|
|
380
|
+
# pyre-fixme[16]: `GeneratorExp` has no attribute `target`.
|
|
381
|
+
# pyre-fixme[16]: `ListComp` has no attribute `target`.
|
|
382
|
+
# pyre-fixme[16]: `SetComp` has no attribute `target`.
|
|
383
|
+
node.target = target
|
|
384
|
+
|
|
385
|
+
gens = node.generators[1:]
|
|
386
|
+
self.walk_many(gens, update)
|
|
387
|
+
if update:
|
|
388
|
+
node.generators[1:] = gens
|
|
389
|
+
|
|
390
|
+
def visit_Func_Outer(
|
|
391
|
+
self, node: AsyncFunctionDef | FunctionDef | Lambda, update: bool = False
|
|
392
|
+
) -> None:
|
|
393
|
+
args = self.visit(node.args)
|
|
394
|
+
if update:
|
|
395
|
+
node.args = args
|
|
396
|
+
|
|
397
|
+
if isinstance(node, (AsyncFunctionDef, FunctionDef)):
|
|
398
|
+
retnode = node.returns
|
|
399
|
+
if retnode and not self.skip_annotations:
|
|
400
|
+
returns = self.visit(retnode)
|
|
401
|
+
if update:
|
|
402
|
+
node.returns = returns
|
|
403
|
+
|
|
404
|
+
self.walk_many(node.decorator_list, update)
|
|
405
|
+
|
|
406
|
+
def visit_Func_Inner(
|
|
407
|
+
self,
|
|
408
|
+
node: AsyncFunctionDef | FunctionDef | Lambda,
|
|
409
|
+
update: bool = False,
|
|
410
|
+
scope_node: AsyncFunctionDef | FunctionDef | Lambda | None = None,
|
|
411
|
+
) -> SymbolScope[TVar, TScopeData]:
|
|
412
|
+
scope_node = scope_node or node
|
|
413
|
+
with self.scopes.with_node_scope(scope_node) as next:
|
|
414
|
+
assert next
|
|
415
|
+
assert isinstance(node, Lambda) or node.name == next.symbols.get_name()
|
|
416
|
+
|
|
417
|
+
# visit body in function scope
|
|
418
|
+
if isinstance(node, Lambda):
|
|
419
|
+
new = self.visit(node.body)
|
|
420
|
+
if update:
|
|
421
|
+
node.body = new
|
|
422
|
+
else:
|
|
423
|
+
self.walk_many(node.body, update)
|
|
424
|
+
|
|
425
|
+
return next
|
|
426
|
+
|
|
427
|
+
def visit_Class_Outer(self, node: ClassDef, update: bool = False) -> None:
|
|
428
|
+
self.walk_many(node.bases, update)
|
|
429
|
+
self.walk_many(node.keywords, update)
|
|
430
|
+
self.walk_many(node.decorator_list, update)
|
|
431
|
+
|
|
432
|
+
def visit_Class_Inner(
|
|
433
|
+
self,
|
|
434
|
+
node: ClassDef,
|
|
435
|
+
update: bool = False,
|
|
436
|
+
scope_node: ClassDef | None = None,
|
|
437
|
+
) -> SymbolScope[TVar, TScopeData]:
|
|
438
|
+
scope_node = scope_node or node
|
|
439
|
+
with self.scopes.with_node_scope(scope_node) as next:
|
|
440
|
+
assert next
|
|
441
|
+
assert node.name == next.symbols.get_name()
|
|
442
|
+
# visit body in class scope
|
|
443
|
+
self.walk_many(node.body, update)
|
|
444
|
+
|
|
445
|
+
return next
|
|
446
|
+
|
|
447
|
+
def walk_many(
|
|
448
|
+
self,
|
|
449
|
+
stmts: list[TAst],
|
|
450
|
+
update: bool = False,
|
|
451
|
+
) -> None:
|
|
452
|
+
new_stmts = []
|
|
453
|
+
for statement in stmts:
|
|
454
|
+
new = self.visit(statement)
|
|
455
|
+
if isinstance(new, ast.AST):
|
|
456
|
+
new_stmts.append(new)
|
|
457
|
+
elif new is not None:
|
|
458
|
+
new_stmts.extend(new)
|
|
459
|
+
if update:
|
|
460
|
+
stmts[:] = new_stmts
|
|
461
|
+
|
|
462
|
+
def visit_Lambda(self, node: Lambda) -> TTransformedStmt:
|
|
463
|
+
self.visit_Func_Outer(node)
|
|
464
|
+
|
|
465
|
+
self.visit_Func_Inner(node)
|
|
466
|
+
|
|
467
|
+
def visit_comp(
|
|
468
|
+
self, node: ListComp | SetComp | DictComp | GeneratorExp
|
|
469
|
+
) -> expr | None:
|
|
470
|
+
self.visit_Comp_Outer(node)
|
|
471
|
+
|
|
472
|
+
self.visit_Comp_Inner(node)
|
|
473
|
+
|
|
474
|
+
return node
|
|
475
|
+
|
|
476
|
+
def visit_ListComp(self, node: ListComp) -> expr | None:
|
|
477
|
+
return self.visit_comp(node)
|
|
478
|
+
|
|
479
|
+
def visit_SetComp(self, node: SetComp) -> expr | None:
|
|
480
|
+
return self.visit_comp(node)
|
|
481
|
+
|
|
482
|
+
def visit_GeneratorExp(self, node: GeneratorExp) -> expr | None:
|
|
483
|
+
return self.visit_comp(node)
|
|
484
|
+
|
|
485
|
+
def visit_DictComp(self, node: DictComp) -> expr | None:
|
|
486
|
+
return self.visit_comp(node)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
@final
|
|
490
|
+
class ImmutableVisitor(SymbolVisitor[None, None]):
|
|
491
|
+
def __init__(self, scopes: ScopeStack[None, None]) -> None:
|
|
492
|
+
super().__init__(scopes)
|
|
493
|
+
self.globals: set[str] = set()
|
|
494
|
+
self.global_sets: set[str] = set()
|
|
495
|
+
self.global_dels: set[str] = set()
|
|
496
|
+
self.future_imports: set[alias] = set()
|
|
497
|
+
|
|
498
|
+
def future_annotations(self) -> bool:
|
|
499
|
+
for a in self.future_imports:
|
|
500
|
+
if a.name == "annotations":
|
|
501
|
+
return True
|
|
502
|
+
return False
|
|
503
|
+
|
|
504
|
+
@property
|
|
505
|
+
def skip_annotations(self) -> bool:
|
|
506
|
+
return self.future_annotations() or sys.version_info >= (3, 14)
|
|
507
|
+
|
|
508
|
+
def load_name(self, name: str) -> None:
|
|
509
|
+
if self.is_global(name):
|
|
510
|
+
self.globals.add(name)
|
|
511
|
+
|
|
512
|
+
def store_name(self, name: str) -> None:
|
|
513
|
+
if self.is_global(name):
|
|
514
|
+
self.globals.add(name)
|
|
515
|
+
self.global_sets.add(name)
|
|
516
|
+
|
|
517
|
+
def del_name(self, name: str) -> None:
|
|
518
|
+
if self.is_global(name):
|
|
519
|
+
self.globals.add(name)
|
|
520
|
+
self.global_sets.add(name)
|
|
521
|
+
self.global_dels.add(name)
|
|
522
|
+
|
|
523
|
+
def visit_Global(self, node: Global) -> None:
|
|
524
|
+
for name in node.names:
|
|
525
|
+
self.globals.add(name)
|
|
526
|
+
|
|
527
|
+
def visit_Name(self, node: Name) -> None:
|
|
528
|
+
if isinstance(node.ctx, ast.Load):
|
|
529
|
+
self.load_name(node.id)
|
|
530
|
+
elif isinstance(node.ctx, ast.Store):
|
|
531
|
+
self.store_name(node.id)
|
|
532
|
+
elif isinstance(node.ctx, ast.Del):
|
|
533
|
+
self.del_name(node.id)
|
|
534
|
+
|
|
535
|
+
def visit_ImportFrom(self, node: ImportFrom) -> None:
|
|
536
|
+
if node.module == "__future__":
|
|
537
|
+
self.future_imports.update(node.names)
|
|
538
|
+
|
|
539
|
+
for name in node.names:
|
|
540
|
+
self.store_name(name.asname or name.name)
|
|
541
|
+
|
|
542
|
+
def visit_Import(self, node: Import) -> None:
|
|
543
|
+
for name in node.names:
|
|
544
|
+
self.store_name(imported_name(name))
|
|
545
|
+
|
|
546
|
+
def visit_Call(self, node: Call) -> None:
|
|
547
|
+
func = node.func
|
|
548
|
+
if isinstance(func, ast.Name):
|
|
549
|
+
# We don't currently allow aliasing or shadowing exec/eval
|
|
550
|
+
# so this check is currently sufficient.
|
|
551
|
+
if (func.id == "exec" or func.id == "eval") and len(node.args) < 2:
|
|
552
|
+
# We'll need access to our globals() helper when we transform
|
|
553
|
+
# the ast
|
|
554
|
+
self.globals.add("globals")
|
|
555
|
+
self.globals.add("locals")
|
|
556
|
+
self.generic_visit(node)
|
|
557
|
+
|
|
558
|
+
def visit_Try(self, node: Try) -> None:
|
|
559
|
+
super().visit_Try(node)
|
|
560
|
+
|
|
561
|
+
for handler in node.handlers:
|
|
562
|
+
name = handler.name
|
|
563
|
+
if name is None:
|
|
564
|
+
continue
|
|
565
|
+
self.del_name(name)
|
|
566
|
+
|
|
567
|
+
def visit_ClassDef(self, node: ClassDef) -> None:
|
|
568
|
+
self.visit_Class_Outer(node)
|
|
569
|
+
|
|
570
|
+
self.store_name(node.name)
|
|
571
|
+
|
|
572
|
+
self.visit_Class_Inner(node)
|
|
573
|
+
|
|
574
|
+
def visit_FunctionDef(self, node: FunctionDef) -> None:
|
|
575
|
+
self.visit_Func_Outer(node)
|
|
576
|
+
|
|
577
|
+
self.store_name(node.name)
|
|
578
|
+
|
|
579
|
+
self.visit_Func_Inner(node)
|
|
580
|
+
|
|
581
|
+
def visit_arg(self, node: ast.arg) -> None:
|
|
582
|
+
if not self.skip_annotations:
|
|
583
|
+
return self.generic_visit(node)
|
|
584
|
+
|
|
585
|
+
def visit_AsyncFunctionDef(self, node: AsyncFunctionDef) -> None:
|
|
586
|
+
self.visit_Func_Outer(node)
|
|
587
|
+
|
|
588
|
+
self.store_name(node.name)
|
|
589
|
+
|
|
590
|
+
self.visit_Func_Inner(node)
|
|
591
|
+
|
|
592
|
+
def visit_AnnAssign(self, node: AnnAssign) -> None:
|
|
593
|
+
self.visit(node.target)
|
|
594
|
+
if not self.skip_annotations:
|
|
595
|
+
self.visit(node.annotation)
|
|
596
|
+
value = node.value
|
|
597
|
+
if value:
|
|
598
|
+
self.visit(value)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
class ScopeData:
|
|
602
|
+
def __init__(self) -> None:
|
|
603
|
+
self.has_classdefs = False
|
|
604
|
+
|
|
605
|
+
def visit_Assign(self, node: Assign) -> None:
|
|
606
|
+
pass
|
|
607
|
+
|
|
608
|
+
def visit_AnnAssign(self, node: AnnAssign) -> None:
|
|
609
|
+
pass
|
|
610
|
+
|
|
611
|
+
def visit_decorators(self, node: ClassDef | FunctionDef | AsyncFunctionDef) -> None:
|
|
612
|
+
# visit the current node held by the scope data
|
|
613
|
+
pass
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
@final
|
|
617
|
+
class ClassScope(ScopeData):
|
|
618
|
+
def __init__(self, node: ClassDef) -> None:
|
|
619
|
+
super().__init__()
|
|
620
|
+
self.node = node
|
|
621
|
+
self.instance_fields: set[str] = set()
|
|
622
|
+
self.cached_props: dict[str, object] = {}
|
|
623
|
+
self.slots_enabled: bool = False
|
|
624
|
+
self.loose_slots: bool = False
|
|
625
|
+
self.extra_slots: list[str] | None = None
|
|
626
|
+
|
|
627
|
+
def visit_AnnAssign(self, node: AnnAssign) -> None:
|
|
628
|
+
target = node.target
|
|
629
|
+
if node.value is None and isinstance(target, Name):
|
|
630
|
+
self.instance_fields.add(target.id)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
@final
|
|
634
|
+
class FunctionScope(ScopeData):
|
|
635
|
+
def __init__(self, node: AsyncFunctionDef | FunctionDef, parent: ScopeData) -> None:
|
|
636
|
+
super().__init__()
|
|
637
|
+
self.node = node
|
|
638
|
+
self.parent = parent
|
|
639
|
+
self.is_cached_prop: bool = False
|
|
640
|
+
self.cached_prop_value: object = None
|
|
641
|
+
|
|
642
|
+
def visit_AnnAssign(self, node: AnnAssign) -> None:
|
|
643
|
+
parent = self.parent
|
|
644
|
+
target = node.target
|
|
645
|
+
if (
|
|
646
|
+
self.node.name == "__init__"
|
|
647
|
+
and self.node.args.args
|
|
648
|
+
and isinstance(parent, ClassScope)
|
|
649
|
+
and isinstance(target, Attribute)
|
|
650
|
+
):
|
|
651
|
+
self.add_attr_name(target, parent)
|
|
652
|
+
|
|
653
|
+
def add_attr_name(self, target: Attribute, parent: ClassScope) -> None:
|
|
654
|
+
"""records self.name = ... when salf matches the 1st parameter"""
|
|
655
|
+
value = target.value
|
|
656
|
+
node = self.node
|
|
657
|
+
if isinstance(value, Name) and value.id == node.args.args[0].arg:
|
|
658
|
+
parent.instance_fields.add(target.attr)
|
|
659
|
+
|
|
660
|
+
def assign_worker(self, targets: Sequence[AST], parent: ClassScope) -> None:
|
|
661
|
+
for target in targets:
|
|
662
|
+
if isinstance(target, Attribute):
|
|
663
|
+
self.add_attr_name(target, parent)
|
|
664
|
+
elif isinstance(target, (ast.Tuple, ast.List)):
|
|
665
|
+
self.assign_worker(target.elts, parent)
|
|
666
|
+
|
|
667
|
+
def visit_Assign(self, node: Assign) -> None:
|
|
668
|
+
parent = self.parent
|
|
669
|
+
if (
|
|
670
|
+
self.node.name == "__init__"
|
|
671
|
+
and isinstance(parent, ClassScope)
|
|
672
|
+
and self.node.args.args
|
|
673
|
+
):
|
|
674
|
+
self.assign_worker(node.targets, parent)
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
@final
|
|
678
|
+
class ImmutableTransformer(SymbolVisitor[None, ScopeData], AstRewriter):
|
|
679
|
+
def __init__(
|
|
680
|
+
self,
|
|
681
|
+
symbols: ScopeStack[None, ScopeData],
|
|
682
|
+
modname: str,
|
|
683
|
+
builtins: Mapping[str, object],
|
|
684
|
+
globals: set[str],
|
|
685
|
+
global_sets: set[str],
|
|
686
|
+
global_dels: set[str],
|
|
687
|
+
future_imports: set[alias],
|
|
688
|
+
is_static: bool,
|
|
689
|
+
) -> None:
|
|
690
|
+
super().__init__(symbols)
|
|
691
|
+
symbols.scope_factory = self.make_scope
|
|
692
|
+
self.modname = modname
|
|
693
|
+
self.future_imports = future_imports
|
|
694
|
+
self.is_static = is_static
|
|
695
|
+
|
|
696
|
+
def make_scope(
|
|
697
|
+
self,
|
|
698
|
+
symtable: SymbolTable,
|
|
699
|
+
node: AST,
|
|
700
|
+
vars: MutableMapping[str, None] | None = None,
|
|
701
|
+
) -> SymbolScope[None, ScopeData]:
|
|
702
|
+
if isinstance(node, (FunctionDef, AsyncFunctionDef)):
|
|
703
|
+
data: ScopeData = FunctionScope(node, self.scopes.scopes[-1].scope_data)
|
|
704
|
+
elif isinstance(node, ClassDef):
|
|
705
|
+
data: ScopeData = ClassScope(node)
|
|
706
|
+
else:
|
|
707
|
+
data: ScopeData = ScopeData()
|
|
708
|
+
return SymbolScope(symtable, data)
|
|
709
|
+
|
|
710
|
+
def visit_Assign(self, node: Assign) -> TTransformedStmt:
|
|
711
|
+
self.scopes.scopes[-1].scope_data.visit_Assign(node)
|
|
712
|
+
node = self.update_node(
|
|
713
|
+
node,
|
|
714
|
+
targets=self.walk_list(node.targets),
|
|
715
|
+
value=self.visit(node.value),
|
|
716
|
+
)
|
|
717
|
+
return node
|
|
718
|
+
|
|
719
|
+
def visit_AnnAssign(self, node: AnnAssign) -> TTransformedStmt:
|
|
720
|
+
self.scopes.scopes[-1].scope_data.visit_AnnAssign(node)
|
|
721
|
+
return self.generic_visit(node)
|
|
722
|
+
|
|
723
|
+
def make_base_class_dict_test_stmts(
|
|
724
|
+
self, bases: list[TAst], instance_fields: set[str], location_node: ast.ClassDef
|
|
725
|
+
) -> stmt:
|
|
726
|
+
slots_stmt = self.make_slots_stmt(instance_fields)
|
|
727
|
+
# if there are non-names in the bases of the class, give up and just create slots
|
|
728
|
+
if not all(isinstance(b, ast.Name) for b in bases):
|
|
729
|
+
return slots_stmt
|
|
730
|
+
# if __dict__ is not added to instance fields (no loose slots), just slotify
|
|
731
|
+
if "__dict__" not in instance_fields:
|
|
732
|
+
return slots_stmt
|
|
733
|
+
# generate code that decide whether __dict__ should be included
|
|
734
|
+
# if any('__dict__' in getattr(_t, '__dict__', ()) for b in <bases> for _t in b.mro()):
|
|
735
|
+
# __slots__ = <slots without __dict__>
|
|
736
|
+
# else:
|
|
737
|
+
# __slots__ = <slots with __dict__>
|
|
738
|
+
names = [lineinfo(ast.Name(cast(ast.Name, n).id, ast.Load())) for n in bases]
|
|
739
|
+
condition = lineinfo(
|
|
740
|
+
ast.Call(
|
|
741
|
+
lineinfo(ast.Name("any", ast.Load())),
|
|
742
|
+
[
|
|
743
|
+
lineinfo(
|
|
744
|
+
ast.GeneratorExp(
|
|
745
|
+
lineinfo(
|
|
746
|
+
ast.Compare(
|
|
747
|
+
lineinfo(Constant("__dict__")),
|
|
748
|
+
[lineinfo(ast.In())],
|
|
749
|
+
[
|
|
750
|
+
lineinfo(
|
|
751
|
+
ast.Call(
|
|
752
|
+
lineinfo(
|
|
753
|
+
ast.Name("getattr", ast.Load())
|
|
754
|
+
),
|
|
755
|
+
[
|
|
756
|
+
lineinfo(
|
|
757
|
+
ast.Name("_t", ast.Load())
|
|
758
|
+
),
|
|
759
|
+
lineinfo(Constant("__dict__")),
|
|
760
|
+
lineinfo(ast.Tuple([], ast.Load())),
|
|
761
|
+
],
|
|
762
|
+
[],
|
|
763
|
+
)
|
|
764
|
+
)
|
|
765
|
+
],
|
|
766
|
+
)
|
|
767
|
+
),
|
|
768
|
+
[
|
|
769
|
+
lineinfo(
|
|
770
|
+
ast.comprehension(
|
|
771
|
+
lineinfo(ast.Name("b", ast.Store())),
|
|
772
|
+
# pyre-fixme[6]: For 1st argument expected
|
|
773
|
+
# `List[expr]` but got `List[Name]`.
|
|
774
|
+
lineinfo(ast.List(names, ast.Load())),
|
|
775
|
+
[],
|
|
776
|
+
0,
|
|
777
|
+
)
|
|
778
|
+
),
|
|
779
|
+
lineinfo(
|
|
780
|
+
ast.comprehension(
|
|
781
|
+
lineinfo(ast.Name("_t", ast.Store())),
|
|
782
|
+
lineinfo(
|
|
783
|
+
ast.Call(
|
|
784
|
+
lineinfo(
|
|
785
|
+
ast.Attribute(
|
|
786
|
+
lineinfo(
|
|
787
|
+
ast.Name("b", ast.Load())
|
|
788
|
+
),
|
|
789
|
+
"mro",
|
|
790
|
+
ast.Load(),
|
|
791
|
+
)
|
|
792
|
+
),
|
|
793
|
+
[],
|
|
794
|
+
[],
|
|
795
|
+
)
|
|
796
|
+
),
|
|
797
|
+
[],
|
|
798
|
+
0,
|
|
799
|
+
)
|
|
800
|
+
),
|
|
801
|
+
],
|
|
802
|
+
),
|
|
803
|
+
target=location_node,
|
|
804
|
+
)
|
|
805
|
+
],
|
|
806
|
+
[],
|
|
807
|
+
)
|
|
808
|
+
)
|
|
809
|
+
slots_stmt_without_dict = self.make_slots_stmt(instance_fields - {"__dict__"})
|
|
810
|
+
return lineinfo(ast.If(condition, [slots_stmt_without_dict], [slots_stmt]))
|
|
811
|
+
|
|
812
|
+
def make_slots_stmt(self, instance_fields: set[str]) -> Assign:
|
|
813
|
+
return lineinfo(
|
|
814
|
+
make_assign(
|
|
815
|
+
[lineinfo(Name("__slots__", ast.Store()))],
|
|
816
|
+
lineinfo(
|
|
817
|
+
ast.Tuple(
|
|
818
|
+
[lineinfo(Constant(name)) for name in instance_fields],
|
|
819
|
+
ast.Load(),
|
|
820
|
+
)
|
|
821
|
+
),
|
|
822
|
+
)
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
def visit_ClassDef(self, node: ClassDef) -> TTransformedStmt:
|
|
826
|
+
outer_scope_data = self.scopes.scopes[-1].scope_data
|
|
827
|
+
outer_scope_data.has_classdefs = True
|
|
828
|
+
orig_node = node
|
|
829
|
+
node = self.clone_node(node)
|
|
830
|
+
self.visit_Class_Outer(node, True)
|
|
831
|
+
class_scope = self.visit_Class_Inner(node, True, scope_node=orig_node)
|
|
832
|
+
scope_data = class_scope.scope_data
|
|
833
|
+
assert isinstance(scope_data, ClassScope), type(class_scope).__name__
|
|
834
|
+
scope_data.visit_decorators(node)
|
|
835
|
+
|
|
836
|
+
slots_enabled = scope_data.slots_enabled and not self.is_static
|
|
837
|
+
if slots_enabled:
|
|
838
|
+
if scope_data.loose_slots:
|
|
839
|
+
scope_data.instance_fields.add("__dict__")
|
|
840
|
+
scope_data.instance_fields.add("__loose_slots__")
|
|
841
|
+
extra_slots = scope_data.extra_slots
|
|
842
|
+
if extra_slots:
|
|
843
|
+
scope_data.instance_fields.update(extra_slots)
|
|
844
|
+
|
|
845
|
+
node.body.append(
|
|
846
|
+
self.make_base_class_dict_test_stmts(
|
|
847
|
+
node.bases, scope_data.instance_fields, node
|
|
848
|
+
)
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
if scope_data.cached_props and slots_enabled:
|
|
852
|
+
# Add a decorator which replaces our name-mangled cache properties
|
|
853
|
+
# with a class-level decorator that converts them. We apply it as
|
|
854
|
+
# a decorator so that we get to initialize these before other
|
|
855
|
+
# decorators see the class.
|
|
856
|
+
node.decorator_list.append(
|
|
857
|
+
self.make_cached_property_init_decorator(scope_data, class_scope)
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
return node
|
|
861
|
+
|
|
862
|
+
def make_cached_property_init_decorator(
|
|
863
|
+
self,
|
|
864
|
+
scope_data: ClassScope,
|
|
865
|
+
class_scope: SymbolScope[TVar, TScopeData],
|
|
866
|
+
) -> expr:
|
|
867
|
+
return lineinfo(
|
|
868
|
+
ast.Call(
|
|
869
|
+
lineinfo(ast.Name("<init-cached-properties>", ast.Load())),
|
|
870
|
+
[
|
|
871
|
+
lineinfo(
|
|
872
|
+
ast.Dict(
|
|
873
|
+
[
|
|
874
|
+
lineinfo(
|
|
875
|
+
Constant(mangle_priv_name(name, [class_scope]))
|
|
876
|
+
)
|
|
877
|
+
for name in scope_data.cached_props
|
|
878
|
+
],
|
|
879
|
+
[
|
|
880
|
+
lineinfo(
|
|
881
|
+
ast.Tuple(
|
|
882
|
+
[
|
|
883
|
+
lineinfo(
|
|
884
|
+
Constant(
|
|
885
|
+
mangle_priv_name(
|
|
886
|
+
self.mangle_cached_prop(name),
|
|
887
|
+
[class_scope],
|
|
888
|
+
)
|
|
889
|
+
)
|
|
890
|
+
),
|
|
891
|
+
lineinfo(Constant(value)),
|
|
892
|
+
],
|
|
893
|
+
ast.Load(),
|
|
894
|
+
)
|
|
895
|
+
)
|
|
896
|
+
for name, value in scope_data.cached_props.items()
|
|
897
|
+
],
|
|
898
|
+
)
|
|
899
|
+
)
|
|
900
|
+
],
|
|
901
|
+
[],
|
|
902
|
+
)
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
def mangle_cached_prop(self, name: str) -> str:
|
|
906
|
+
return "_" + name + "_impl"
|
|
907
|
+
|
|
908
|
+
def visit_FunctionDef(self, node: FunctionDef) -> TTransformedStmt:
|
|
909
|
+
outer_scope = self.scopes.scopes[-1].scope_data
|
|
910
|
+
orig_node = node
|
|
911
|
+
node = self.clone_node(node)
|
|
912
|
+
self.visit_Func_Outer(node, True)
|
|
913
|
+
|
|
914
|
+
scope_data = self.visit_Func_Inner(node, True, scope_node=orig_node).scope_data
|
|
915
|
+
scope_data.visit_decorators(node)
|
|
916
|
+
|
|
917
|
+
res: TTransformedStmt = node
|
|
918
|
+
|
|
919
|
+
self.check_cached_prop(node, scope_data, outer_scope)
|
|
920
|
+
|
|
921
|
+
return res
|
|
922
|
+
|
|
923
|
+
def visit_AsyncFunctionDef(self, node: AsyncFunctionDef) -> TTransformedStmt:
|
|
924
|
+
outer_scope = self.scopes.scopes[-1].scope_data
|
|
925
|
+
orig_node = node
|
|
926
|
+
node = self.clone_node(node)
|
|
927
|
+
self.visit_Func_Outer(node, True)
|
|
928
|
+
|
|
929
|
+
scope_data = self.visit_Func_Inner(node, True, scope_node=orig_node).scope_data
|
|
930
|
+
scope_data.visit_decorators(node)
|
|
931
|
+
|
|
932
|
+
self.check_cached_prop(node, scope_data, outer_scope)
|
|
933
|
+
|
|
934
|
+
return node
|
|
935
|
+
|
|
936
|
+
def check_cached_prop(
|
|
937
|
+
self,
|
|
938
|
+
node: AsyncFunctionDef | FunctionDef,
|
|
939
|
+
func_scope: ScopeData,
|
|
940
|
+
outer_scope: ScopeData,
|
|
941
|
+
) -> None:
|
|
942
|
+
if isinstance(func_scope, FunctionScope) and isinstance(
|
|
943
|
+
outer_scope, ClassScope
|
|
944
|
+
):
|
|
945
|
+
if func_scope.is_cached_prop:
|
|
946
|
+
# preprocessor already checked that outer scope is slotified
|
|
947
|
+
outer_scope.instance_fields.add(node.name)
|
|
948
|
+
outer_scope.cached_props[node.name] = func_scope.cached_prop_value
|
|
949
|
+
node.name = self.mangle_cached_prop(node.name)
|
|
950
|
+
|
|
951
|
+
def visit_Lambda(self, node: Lambda) -> TTransformedStmt:
|
|
952
|
+
orig_node = node
|
|
953
|
+
node = self.clone_node(node)
|
|
954
|
+
self.visit_Func_Outer(node, True)
|
|
955
|
+
|
|
956
|
+
self.visit_Func_Inner(node, True, scope_node=orig_node)
|
|
957
|
+
|
|
958
|
+
return node
|
|
959
|
+
|
|
960
|
+
def visit_ImportFrom(self, node: ImportFrom) -> TTransformedStmt:
|
|
961
|
+
if node.module == "__future__":
|
|
962
|
+
# We push these to the top of the module where they're required to be
|
|
963
|
+
return None
|
|
964
|
+
return node
|
|
965
|
+
|
|
966
|
+
def visit_comp(
|
|
967
|
+
self, node: ListComp | SetComp | DictComp | GeneratorExp
|
|
968
|
+
) -> expr | None:
|
|
969
|
+
orig_node = node
|
|
970
|
+
node = self.clone_node(node)
|
|
971
|
+
self.visit_Comp_Outer(node, True)
|
|
972
|
+
|
|
973
|
+
self.visit_Comp_Inner(node, True, scope_node=orig_node)
|
|
974
|
+
|
|
975
|
+
return node
|