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,1754 @@
|
|
|
1
|
+
# Portions copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
# pyre-strict
|
|
3
|
+
|
|
4
|
+
"""Module symbol-table generator"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import ast
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
from typing import Generator, Iterable
|
|
13
|
+
|
|
14
|
+
from .consts import (
|
|
15
|
+
CO_FUTURE_ANNOTATIONS,
|
|
16
|
+
SC_CELL,
|
|
17
|
+
SC_FREE,
|
|
18
|
+
SC_GLOBAL_EXPLICIT,
|
|
19
|
+
SC_GLOBAL_IMPLICIT,
|
|
20
|
+
SC_LOCAL,
|
|
21
|
+
SC_UNKNOWN,
|
|
22
|
+
)
|
|
23
|
+
from .misc import mangle
|
|
24
|
+
from .visitor import ASTVisitor
|
|
25
|
+
|
|
26
|
+
if sys.version_info[0] >= 3:
|
|
27
|
+
long = int
|
|
28
|
+
|
|
29
|
+
MANGLE_LEN = 256
|
|
30
|
+
|
|
31
|
+
DEF_GLOBAL = 1
|
|
32
|
+
DEF_LOCAL = 2
|
|
33
|
+
DEF_COMP_ITER = 2
|
|
34
|
+
DEF_PARAM: int = 2 << 1
|
|
35
|
+
USE: int = 2 << 3
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
NodeWithTypeParams: object = None
|
|
39
|
+
if sys.version_info >= (3, 12):
|
|
40
|
+
NodeWithTypeParams = (
|
|
41
|
+
ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.TypeAlias
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TypeParams(ast.AST):
|
|
46
|
+
"""Artificial node to store a tuple of type params."""
|
|
47
|
+
|
|
48
|
+
_fields = ("params",)
|
|
49
|
+
|
|
50
|
+
# pyre-ignore[11]: Annotation `NodeWithTypeParams` is not defined as a type.
|
|
51
|
+
def __init__(self, node: NodeWithTypeParams) -> None:
|
|
52
|
+
params = getattr(node, "type_params", None)
|
|
53
|
+
assert params, "TypeParams needs a node with a type_params field"
|
|
54
|
+
first = params[0]
|
|
55
|
+
last = params[-1]
|
|
56
|
+
# pyre-ignore[11]: ast.type_param added in 3.12, pyre still running in 3.10 mode.
|
|
57
|
+
self.params: tuple[ast.type_param] = tuple(params)
|
|
58
|
+
self.lineno: int = first.lineno
|
|
59
|
+
self.end_lineno: int = last.end_lineno
|
|
60
|
+
self.col_offset: int = first.col_offset
|
|
61
|
+
self.end_col_offset: int = last.end_col_offset
|
|
62
|
+
|
|
63
|
+
def __eq__(self, other: object) -> bool:
|
|
64
|
+
return isinstance(other, TypeParams) and self.params == other.params
|
|
65
|
+
|
|
66
|
+
def __hash__(self) -> int:
|
|
67
|
+
return hash(self.params)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Annotations(ast.AST):
|
|
71
|
+
"""Artificial node to store the scope for annotations"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, parent: Scope) -> None:
|
|
74
|
+
self.parent = parent
|
|
75
|
+
|
|
76
|
+
def __eq__(self, other: object) -> bool:
|
|
77
|
+
return isinstance(other, Annotations) and self.parent == other.parent
|
|
78
|
+
|
|
79
|
+
def __hash__(self) -> int:
|
|
80
|
+
return hash(self.parent)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TypeVarDefault(ast.AST):
|
|
84
|
+
"""Artificial node to store the scope for a TypeParam w/ default values"""
|
|
85
|
+
|
|
86
|
+
# pyre-ignore[11]: Annotation `ast.TypeVar` is not defined as a type.
|
|
87
|
+
def __init__(self, parent: ast.TypeVar) -> None:
|
|
88
|
+
self.parent: ast.TypeVar = parent
|
|
89
|
+
|
|
90
|
+
def __eq__(self, other: object) -> bool:
|
|
91
|
+
return isinstance(other, TypeVarDefault) and self.parent == other.parent
|
|
92
|
+
|
|
93
|
+
def __hash__(self) -> int:
|
|
94
|
+
return hash(self.parent)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Scope:
|
|
98
|
+
is_function_scope = False
|
|
99
|
+
has_docstring = False
|
|
100
|
+
|
|
101
|
+
# XXX how much information do I need about each name?
|
|
102
|
+
def __init__(
|
|
103
|
+
self, name: str, module: Scope, klass: str | None = None, lineno: int = 0
|
|
104
|
+
) -> None:
|
|
105
|
+
self.name: str = name
|
|
106
|
+
self.module: Scope = module
|
|
107
|
+
self.lineno: int = lineno
|
|
108
|
+
self.defs: dict[str, int] = {}
|
|
109
|
+
self.uses: dict[str, int] = {}
|
|
110
|
+
# Defs and uses
|
|
111
|
+
self.symbols: dict[str, int] = {}
|
|
112
|
+
self.globals: dict[str, int] = {}
|
|
113
|
+
self.explicit_globals: dict[str, int] = {}
|
|
114
|
+
self.nonlocals: dict[str, int] = {}
|
|
115
|
+
self.params: dict[str, int] = {}
|
|
116
|
+
self.frees: dict[str, int] = {}
|
|
117
|
+
self.cells: dict[str, int] = {}
|
|
118
|
+
self.type_params: set[str] = set()
|
|
119
|
+
self.children: list[Scope] = []
|
|
120
|
+
# Names imported in this scope (the symbols, not the modules)
|
|
121
|
+
self.imports: set[str] = set()
|
|
122
|
+
self.parent: Scope | None = None
|
|
123
|
+
self.coroutine: bool = False
|
|
124
|
+
self.comp_iter_target: int = 0
|
|
125
|
+
self.comp_iter_expr: int = 0
|
|
126
|
+
# nested is true if the class could contain free variables,
|
|
127
|
+
# i.e. if it is nested within another function.
|
|
128
|
+
self.nested: bool = False
|
|
129
|
+
# It's possible to define a scope (class, function) at the nested level,
|
|
130
|
+
# but explicitly mark it as global. Bytecode-wise, this is handled
|
|
131
|
+
# automagically, but we need to generate proper __qualname__ for these.
|
|
132
|
+
self.global_scope: bool = False
|
|
133
|
+
self.generator: bool = False
|
|
134
|
+
self.klass: str | None = None
|
|
135
|
+
self.suppress_jit: bool = False
|
|
136
|
+
self.can_see_class_scope: bool = False
|
|
137
|
+
self.child_free: bool = False
|
|
138
|
+
self.free: bool = False
|
|
139
|
+
self.annotations: AnnotationScope | None = None
|
|
140
|
+
self.has_conditional_annotations = False
|
|
141
|
+
self.in_unevaluated_annotation = False
|
|
142
|
+
if klass is not None:
|
|
143
|
+
for i in range(len(klass)):
|
|
144
|
+
if klass[i] != "_":
|
|
145
|
+
self.klass = klass[i:]
|
|
146
|
+
break
|
|
147
|
+
|
|
148
|
+
def __repr__(self) -> str:
|
|
149
|
+
return "<{}: {}>".format(self.__class__.__name__, self.name)
|
|
150
|
+
|
|
151
|
+
def mangle(self, name: str) -> str:
|
|
152
|
+
if self.klass is None:
|
|
153
|
+
return name
|
|
154
|
+
return mangle(name, self.klass)
|
|
155
|
+
|
|
156
|
+
def add_def(self, name: str, kind: int = DEF_LOCAL) -> None:
|
|
157
|
+
mangled = self.mangle(name)
|
|
158
|
+
if name not in self.nonlocals:
|
|
159
|
+
self.defs[mangled] = kind | self.defs.get(mangled, 1)
|
|
160
|
+
self.symbols[mangled] = kind | self.defs.get(mangled, 1)
|
|
161
|
+
|
|
162
|
+
def add_import(self, name: str) -> None:
|
|
163
|
+
self.imports.add(name)
|
|
164
|
+
|
|
165
|
+
def add_use(self, name: str) -> None:
|
|
166
|
+
self.uses[self.mangle(name)] = 1
|
|
167
|
+
self.symbols[self.mangle(name)] = USE
|
|
168
|
+
|
|
169
|
+
def add_global(self, name: str) -> None:
|
|
170
|
+
name = self.mangle(name)
|
|
171
|
+
if name in self.uses or name in self.defs:
|
|
172
|
+
pass # XXX warn about global following def/use
|
|
173
|
+
if name in self.params:
|
|
174
|
+
raise SyntaxError(
|
|
175
|
+
"{} in {} is global and parameter".format(name, self.name)
|
|
176
|
+
)
|
|
177
|
+
self.explicit_globals[name] = 1
|
|
178
|
+
self.module.add_def(name, DEF_GLOBAL)
|
|
179
|
+
# Seems to be behavior of Py3.5, "global foo" sets foo as
|
|
180
|
+
# explicit global for module too
|
|
181
|
+
self.module.explicit_globals[name] = 1
|
|
182
|
+
|
|
183
|
+
def add_param(self, name: str) -> None:
|
|
184
|
+
name = self.mangle(name)
|
|
185
|
+
self.defs[name] = 1
|
|
186
|
+
self.params[name] = 1
|
|
187
|
+
self.symbols[name] = DEF_PARAM
|
|
188
|
+
|
|
189
|
+
def add_type_param(self, name: str) -> None:
|
|
190
|
+
name = self.mangle(name)
|
|
191
|
+
if name in self.type_params:
|
|
192
|
+
raise SyntaxError("duplicated type parameter: {!r}".format(name))
|
|
193
|
+
self.type_params.add(name)
|
|
194
|
+
|
|
195
|
+
def add_child(self, child: Scope) -> None:
|
|
196
|
+
self.children.append(child)
|
|
197
|
+
child.parent = self
|
|
198
|
+
|
|
199
|
+
def get_children(self) -> list[Scope]:
|
|
200
|
+
return self.children
|
|
201
|
+
|
|
202
|
+
def DEBUG(self) -> None:
|
|
203
|
+
print(
|
|
204
|
+
self.name,
|
|
205
|
+
type(self),
|
|
206
|
+
self.nested and "nested" or "",
|
|
207
|
+
"global scope" if self.global_scope else "",
|
|
208
|
+
)
|
|
209
|
+
print("\tglobals: ", self.globals)
|
|
210
|
+
print("\texplicit_globals: ", self.explicit_globals)
|
|
211
|
+
print("\tcells: ", self.cells)
|
|
212
|
+
print("\tdefs: ", self.defs)
|
|
213
|
+
print("\tuses: ", self.uses)
|
|
214
|
+
print("\tfrees:", self.frees)
|
|
215
|
+
print("\tnonlocals:", self.nonlocals)
|
|
216
|
+
print("\tparams:", self.params)
|
|
217
|
+
for child in self.children:
|
|
218
|
+
child.DEBUG()
|
|
219
|
+
|
|
220
|
+
def check_name(self, name: str) -> int:
|
|
221
|
+
"""Return scope of name.
|
|
222
|
+
|
|
223
|
+
The scope of a name could be LOCAL, GLOBAL, FREE, or CELL.
|
|
224
|
+
"""
|
|
225
|
+
if name in self.explicit_globals:
|
|
226
|
+
return SC_GLOBAL_EXPLICIT
|
|
227
|
+
if name in self.globals:
|
|
228
|
+
return SC_GLOBAL_IMPLICIT
|
|
229
|
+
if name in self.cells:
|
|
230
|
+
return SC_CELL
|
|
231
|
+
if name in self.frees:
|
|
232
|
+
if isinstance(self, ClassScope) and name in self.defs:
|
|
233
|
+
# When a class has a free variable for something it
|
|
234
|
+
# defines it's not really a free variable in the class
|
|
235
|
+
# scope. CPython handles this with DEF_FREE_CLASS
|
|
236
|
+
return SC_LOCAL
|
|
237
|
+
return SC_FREE
|
|
238
|
+
if name in self.defs:
|
|
239
|
+
return SC_LOCAL
|
|
240
|
+
if self.nested and name in self.uses:
|
|
241
|
+
return SC_FREE
|
|
242
|
+
if self.nested:
|
|
243
|
+
return SC_UNKNOWN
|
|
244
|
+
else:
|
|
245
|
+
return SC_GLOBAL_IMPLICIT
|
|
246
|
+
|
|
247
|
+
def is_import(self, name: str) -> bool:
|
|
248
|
+
return name in self.imports
|
|
249
|
+
|
|
250
|
+
def get_free_vars(self) -> list[str]:
|
|
251
|
+
return sorted(self.frees.keys())
|
|
252
|
+
|
|
253
|
+
def update_symbols(self, bound: set[str] | None, new_free: set[str]) -> None:
|
|
254
|
+
# Record not yet resolved free variables from children (if any)
|
|
255
|
+
for name in new_free:
|
|
256
|
+
if isinstance(self, ClassScope) and (
|
|
257
|
+
name in self.defs or name in self.globals
|
|
258
|
+
):
|
|
259
|
+
# Handle a free variable in a method of
|
|
260
|
+
# the class that has the same name as a local
|
|
261
|
+
# or global in the class scope.
|
|
262
|
+
self.frees[name] = 1
|
|
263
|
+
continue
|
|
264
|
+
if bound and name not in bound:
|
|
265
|
+
# it's a global
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
# Propagate new free symbol up the lexical stack
|
|
269
|
+
self.frees[name] = 1
|
|
270
|
+
|
|
271
|
+
def local_names_include_defs(self, local_names: set[str]) -> bool:
|
|
272
|
+
overlap = local_names.intersection(self.defs.keys())
|
|
273
|
+
|
|
274
|
+
# skip non-locals as they are defined in enclosing scope
|
|
275
|
+
overlap.difference_update(self.nonlocals.keys())
|
|
276
|
+
return len(overlap) != 0 and overlap != {".0"}
|
|
277
|
+
|
|
278
|
+
def analyze_cells(self, free: set[str]) -> None:
|
|
279
|
+
for name in self.defs:
|
|
280
|
+
if name in free and name not in self.explicit_globals:
|
|
281
|
+
self.cells[name] = 1
|
|
282
|
+
if name in free:
|
|
283
|
+
free.remove(name)
|
|
284
|
+
|
|
285
|
+
def analyze_names(
|
|
286
|
+
self,
|
|
287
|
+
bound: set[str] | None,
|
|
288
|
+
local: set[str],
|
|
289
|
+
free: set[str],
|
|
290
|
+
global_vars: set[str],
|
|
291
|
+
class_entry: Scope | None = None,
|
|
292
|
+
) -> None:
|
|
293
|
+
# Go through all the known symbols in the block and analyze them
|
|
294
|
+
for name in self.explicit_globals:
|
|
295
|
+
if name in self.nonlocals:
|
|
296
|
+
err = SyntaxError(f"name {name} is nonlocal and global")
|
|
297
|
+
err.lineno = self.lineno
|
|
298
|
+
raise err
|
|
299
|
+
global_vars.add(name)
|
|
300
|
+
if bound and name in bound:
|
|
301
|
+
bound.remove(name)
|
|
302
|
+
|
|
303
|
+
for name in self.nonlocals:
|
|
304
|
+
if not bound:
|
|
305
|
+
# TODO: We should flow in and set the filename too
|
|
306
|
+
err = SyntaxError(f"nonlocal declaration not allowed at module level")
|
|
307
|
+
err.lineno = self.lineno
|
|
308
|
+
raise err
|
|
309
|
+
if name not in bound:
|
|
310
|
+
err = SyntaxError(f"no binding for nonlocal '{name}' found")
|
|
311
|
+
err.lineno = self.lineno
|
|
312
|
+
raise err
|
|
313
|
+
self.frees[name] = 1
|
|
314
|
+
self.free = True
|
|
315
|
+
free.add(name)
|
|
316
|
+
|
|
317
|
+
for name in self.defs:
|
|
318
|
+
if name in self.explicit_globals or name in self.globals:
|
|
319
|
+
continue
|
|
320
|
+
local.add(name)
|
|
321
|
+
global_vars.discard(name)
|
|
322
|
+
|
|
323
|
+
for name in self.uses:
|
|
324
|
+
# If we were passed class_entry (i.e., we're in an ste_can_see_class_scope scope)
|
|
325
|
+
# and the bound name is in that set, then the name is potentially bound both by
|
|
326
|
+
# the immediately enclosing class namespace, and also by an outer function namespace.
|
|
327
|
+
# In that case, we want the runtime name resolution to look at only the class
|
|
328
|
+
# namespace and the globals (not the namespace providing the bound).
|
|
329
|
+
# Similarly, if the name is explicitly global in the class namespace (through the
|
|
330
|
+
# global statement), we want to also treat it as a global in this scope.
|
|
331
|
+
if class_entry is not None and name != "__classdict__":
|
|
332
|
+
if name in class_entry.explicit_globals:
|
|
333
|
+
self.explicit_globals[name] = 1
|
|
334
|
+
continue
|
|
335
|
+
elif name in class_entry.defs and name not in class_entry.nonlocals:
|
|
336
|
+
self.globals[name] = 1
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
if name in self.defs or name in self.explicit_globals:
|
|
340
|
+
continue
|
|
341
|
+
if bound is not None and name in bound:
|
|
342
|
+
self.frees[name] = 1
|
|
343
|
+
self.free = True
|
|
344
|
+
free.add(name)
|
|
345
|
+
elif name in global_vars:
|
|
346
|
+
self.globals[name] = 1
|
|
347
|
+
else:
|
|
348
|
+
if self.nested:
|
|
349
|
+
self.free = True
|
|
350
|
+
self.globals[name] = 1
|
|
351
|
+
|
|
352
|
+
def get_cell_vars(self) -> Iterable[str]:
|
|
353
|
+
keys = list(self.cells.keys())
|
|
354
|
+
if self.has_conditional_annotations:
|
|
355
|
+
keys.append("__conditional_annotations__")
|
|
356
|
+
return sorted(keys)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class ModuleScope(Scope):
|
|
360
|
+
def __init__(self) -> None:
|
|
361
|
+
# Set lineno to 0 so it sorted guaranteedly before any other scope
|
|
362
|
+
super().__init__("global", self, lineno=0)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class FunctionScope(Scope):
|
|
366
|
+
is_function_scope = True
|
|
367
|
+
is_method = False
|
|
368
|
+
_inline_comprehensions = False
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
class GenExprScope(FunctionScope):
|
|
372
|
+
is_function_scope = False
|
|
373
|
+
inlined = False
|
|
374
|
+
|
|
375
|
+
__counter = 1
|
|
376
|
+
|
|
377
|
+
def __init__(
|
|
378
|
+
self, name: str, module: ModuleScope, klass: str | None = None, lineno: int = 0
|
|
379
|
+
) -> None:
|
|
380
|
+
self.__counter += 1
|
|
381
|
+
super().__init__(name, module, klass, lineno)
|
|
382
|
+
self.add_param(".0")
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class LambdaScope(FunctionScope):
|
|
386
|
+
__counter = 1
|
|
387
|
+
|
|
388
|
+
def __init__(
|
|
389
|
+
self, module: ModuleScope, klass: str | None = None, lineno: int = 0
|
|
390
|
+
) -> None:
|
|
391
|
+
self.__counter += 1
|
|
392
|
+
super().__init__("<lambda>", module, klass, lineno=lineno)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class ClassScope(Scope):
|
|
396
|
+
def __init__(self, name: str, module: ModuleScope, lineno: int = 0) -> None:
|
|
397
|
+
super().__init__(name, module, name, lineno=lineno)
|
|
398
|
+
self.needs_class_closure = False
|
|
399
|
+
self.needs_classdict = False
|
|
400
|
+
|
|
401
|
+
def get_cell_vars(self) -> Iterable[str]:
|
|
402
|
+
yield from self.cells.keys()
|
|
403
|
+
if self.needs_class_closure:
|
|
404
|
+
yield "__class__"
|
|
405
|
+
if self.needs_classdict:
|
|
406
|
+
yield "__classdict__"
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class TypeParamScope(Scope):
|
|
410
|
+
pass
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class TypeAliasScope(Scope):
|
|
414
|
+
pass
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
class TypeVarBoundScope(Scope):
|
|
418
|
+
pass
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class AnnotationScope(Scope):
|
|
422
|
+
annotations_used = False
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
FUNCTION_LIKE_SCOPES = (
|
|
426
|
+
FunctionScope,
|
|
427
|
+
TypeVarBoundScope,
|
|
428
|
+
TypeParamScope,
|
|
429
|
+
TypeAliasScope,
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class BaseSymbolVisitor(ASTVisitor):
|
|
434
|
+
_FunctionScope = FunctionScope
|
|
435
|
+
_GenExprScope = GenExprScope
|
|
436
|
+
_LambdaScope = LambdaScope
|
|
437
|
+
|
|
438
|
+
def __init__(self, future_flags: int) -> None:
|
|
439
|
+
super().__init__()
|
|
440
|
+
self.future_annotations: int = future_flags & CO_FUTURE_ANNOTATIONS
|
|
441
|
+
self.scopes: dict[ast.AST, Scope] = {}
|
|
442
|
+
self.klass: str | None = None
|
|
443
|
+
self.module = ModuleScope()
|
|
444
|
+
self.in_conditional_block = False
|
|
445
|
+
|
|
446
|
+
@contextmanager
|
|
447
|
+
def conditional_block(self) -> Generator[None, None, None]:
|
|
448
|
+
in_conditional_block = self.in_conditional_block
|
|
449
|
+
self.in_conditional_block = True
|
|
450
|
+
try:
|
|
451
|
+
yield
|
|
452
|
+
finally:
|
|
453
|
+
self.in_conditional_block = in_conditional_block
|
|
454
|
+
|
|
455
|
+
def enter_type_params(
|
|
456
|
+
self,
|
|
457
|
+
# pyre-ignore[11]: Pyre doesn't know TypeAlias
|
|
458
|
+
node: ast.ClassDef | ast.FunctionDef | ast.TypeAlias | ast.AsyncFunctionDef,
|
|
459
|
+
parent: Scope,
|
|
460
|
+
) -> TypeParamScope:
|
|
461
|
+
# pyre-fixme[6]: For 1st argument expected `str` but got `Union[Name, str]`.
|
|
462
|
+
scope = TypeParamScope(node.name, self.module, self.klass, lineno=node.lineno)
|
|
463
|
+
if parent.nested or isinstance(parent, FUNCTION_LIKE_SCOPES):
|
|
464
|
+
scope.nested = True
|
|
465
|
+
parent.add_child(scope)
|
|
466
|
+
scope.parent = parent
|
|
467
|
+
self.scopes[TypeParams(node)] = scope
|
|
468
|
+
if isinstance(parent, ClassScope):
|
|
469
|
+
scope.can_see_class_scope = True
|
|
470
|
+
scope.add_use("__classdict__")
|
|
471
|
+
parent.add_def("__classdict__")
|
|
472
|
+
|
|
473
|
+
if isinstance(node, ast.ClassDef):
|
|
474
|
+
scope.add_def(".type_params")
|
|
475
|
+
scope.add_use(".type_params")
|
|
476
|
+
scope.add_def(".generic_base")
|
|
477
|
+
scope.add_use(".generic_base")
|
|
478
|
+
|
|
479
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
480
|
+
scope.add_def(".defaults")
|
|
481
|
+
scope.add_param(".defaults")
|
|
482
|
+
if node.args.kw_defaults:
|
|
483
|
+
scope.add_def(".kwdefaults")
|
|
484
|
+
return scope
|
|
485
|
+
|
|
486
|
+
def visit_type_param_bound_or_default(
|
|
487
|
+
self, bound_or_default: ast.expr, name: str, type_param: ast.AST, parent: Scope
|
|
488
|
+
) -> None:
|
|
489
|
+
if bound_or_default:
|
|
490
|
+
is_in_class = parent.can_see_class_scope
|
|
491
|
+
scope = TypeVarBoundScope(name, self.module)
|
|
492
|
+
scope.parent = parent
|
|
493
|
+
scope.nested = True
|
|
494
|
+
scope.can_see_class_scope = is_in_class
|
|
495
|
+
if is_in_class:
|
|
496
|
+
scope.add_use("__classdict__")
|
|
497
|
+
|
|
498
|
+
self.visit(bound_or_default, scope)
|
|
499
|
+
self.scopes[type_param] = scope
|
|
500
|
+
parent.add_child(scope)
|
|
501
|
+
|
|
502
|
+
# pyre-ignore[11]: Pyre doesn't know TypeVar
|
|
503
|
+
def visitTypeVar(self, node: ast.TypeVar, parent: Scope) -> None:
|
|
504
|
+
parent.add_def(node.name)
|
|
505
|
+
parent.add_type_param(node.name)
|
|
506
|
+
|
|
507
|
+
# pyre-fixme[6]: For 1st argument expected `expr` but got `Optional[expr]`.
|
|
508
|
+
self.visit_type_param_bound_or_default(node.bound, node.name, node, parent)
|
|
509
|
+
if default_value := getattr(node, "default_value", None):
|
|
510
|
+
self.visit_type_param_bound_or_default(
|
|
511
|
+
default_value, node.name, TypeVarDefault(node), parent
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
# pyre-ignore[11]: Pyre doesn't know TypeVarTuple
|
|
515
|
+
def visitTypeVarTuple(self, node: ast.TypeVarTuple, parent: Scope) -> None:
|
|
516
|
+
parent.add_def(node.name)
|
|
517
|
+
parent.add_type_param(node.name)
|
|
518
|
+
|
|
519
|
+
if default_value := getattr(node, "default_value", None):
|
|
520
|
+
self.visit_type_param_bound_or_default(
|
|
521
|
+
default_value, node.name, node, parent
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
# pyre-ignore[11]: Pyre doesn't know ParamSpec
|
|
525
|
+
def visitParamSpec(self, node: ast.ParamSpec, parent: Scope) -> None:
|
|
526
|
+
parent.add_def(node.name)
|
|
527
|
+
parent.add_type_param(node.name)
|
|
528
|
+
|
|
529
|
+
if default_value := getattr(node, "default_value", None):
|
|
530
|
+
self.visit_type_param_bound_or_default(
|
|
531
|
+
default_value, node.name, node, parent
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
def visitFunctionDef(self, node: ast.FunctionDef, parent: Scope) -> None:
|
|
535
|
+
self._visit_func_impl(node, parent)
|
|
536
|
+
|
|
537
|
+
def visitAsyncFunctionDef(self, node: ast.AsyncFunctionDef, parent: Scope) -> None:
|
|
538
|
+
self._visit_func_impl(node, parent)
|
|
539
|
+
|
|
540
|
+
def _visit_func_impl(
|
|
541
|
+
self, node: ast.FunctionDef | ast.AsyncFunctionDef, parent: Scope
|
|
542
|
+
) -> None:
|
|
543
|
+
if node.decorator_list:
|
|
544
|
+
self.visit_list(node.decorator_list, parent)
|
|
545
|
+
parent.add_def(node.name)
|
|
546
|
+
|
|
547
|
+
type_params = getattr(node, "type_params", ())
|
|
548
|
+
if type_params:
|
|
549
|
+
parent = self.enter_type_params(node, parent)
|
|
550
|
+
for param in type_params:
|
|
551
|
+
self.visit(param, parent)
|
|
552
|
+
|
|
553
|
+
scope = self._FunctionScope(
|
|
554
|
+
node.name, self.module, self.klass, lineno=node.lineno
|
|
555
|
+
)
|
|
556
|
+
scope.coroutine = isinstance(node, ast.AsyncFunctionDef)
|
|
557
|
+
scope.parent = parent
|
|
558
|
+
if parent.nested or isinstance(parent, FUNCTION_LIKE_SCOPES):
|
|
559
|
+
scope.nested = True
|
|
560
|
+
self.scopes[node] = scope
|
|
561
|
+
self._do_args(scope, node.args)
|
|
562
|
+
if returns := node.returns:
|
|
563
|
+
if not self.future_annotations:
|
|
564
|
+
self.visit(returns, parent)
|
|
565
|
+
self.visit_list(node.body, scope)
|
|
566
|
+
|
|
567
|
+
parent.add_child(scope)
|
|
568
|
+
|
|
569
|
+
_scope_names = {
|
|
570
|
+
ast.GeneratorExp: "<genexpr>",
|
|
571
|
+
ast.ListComp: "<listcomp>",
|
|
572
|
+
ast.DictComp: "<dictcomp>",
|
|
573
|
+
ast.SetComp: "<setcomp>",
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
def visitAwait(self, node: ast.Await, scope: Scope) -> None:
|
|
577
|
+
scope.coroutine = True
|
|
578
|
+
self.visit(node.value, scope)
|
|
579
|
+
|
|
580
|
+
def visit_gen_impl(
|
|
581
|
+
self,
|
|
582
|
+
node: ast.GeneratorExp | ast.ListComp | ast.DictComp | ast.SetComp,
|
|
583
|
+
parent: Scope,
|
|
584
|
+
) -> None:
|
|
585
|
+
scope = self._GenExprScope(
|
|
586
|
+
self._scope_names[type(node)],
|
|
587
|
+
self.module,
|
|
588
|
+
self.klass,
|
|
589
|
+
lineno=node.lineno,
|
|
590
|
+
)
|
|
591
|
+
scope.parent = parent
|
|
592
|
+
|
|
593
|
+
# bpo-37757: For now, disallow *all* assignment expressions in the
|
|
594
|
+
# outermost iterator expression of a comprehension, even those inside
|
|
595
|
+
# a nested comprehension or a lambda expression.
|
|
596
|
+
scope.comp_iter_expr = parent.comp_iter_expr
|
|
597
|
+
if isinstance(node, ast.GeneratorExp):
|
|
598
|
+
scope.generator = True
|
|
599
|
+
|
|
600
|
+
if (
|
|
601
|
+
parent.nested
|
|
602
|
+
or isinstance(parent, FUNCTION_LIKE_SCOPES)
|
|
603
|
+
or isinstance(parent, GenExprScope)
|
|
604
|
+
):
|
|
605
|
+
scope.nested = True
|
|
606
|
+
|
|
607
|
+
if isinstance(parent, ClassScope):
|
|
608
|
+
scope.is_method = True
|
|
609
|
+
|
|
610
|
+
parent.comp_iter_expr += 1
|
|
611
|
+
self.visit(node.generators[0].iter, parent)
|
|
612
|
+
parent.comp_iter_expr -= 1
|
|
613
|
+
|
|
614
|
+
self.visitcomprehension(node.generators[0], scope, True)
|
|
615
|
+
|
|
616
|
+
for comp in node.generators[1:]:
|
|
617
|
+
self.visit(comp, scope, False)
|
|
618
|
+
|
|
619
|
+
if isinstance(node, ast.DictComp):
|
|
620
|
+
self.visit(node.value, scope)
|
|
621
|
+
self.visit(node.key, scope)
|
|
622
|
+
else:
|
|
623
|
+
self.visit(node.elt, scope)
|
|
624
|
+
|
|
625
|
+
self.scopes[node] = scope
|
|
626
|
+
|
|
627
|
+
if scope.coroutine and not isinstance(node, ast.GeneratorExp):
|
|
628
|
+
parent.coroutine = True
|
|
629
|
+
|
|
630
|
+
parent.add_child(scope)
|
|
631
|
+
|
|
632
|
+
# Whether to generate code for comprehensions inline or as nested scope
|
|
633
|
+
# is configurable, but we compute nested scopes for them unconditionally
|
|
634
|
+
# TODO: this may be not correct, check.
|
|
635
|
+
|
|
636
|
+
def visitGeneratorExp(self, node: ast.GeneratorExp, scope: Scope) -> None:
|
|
637
|
+
return self.visit_gen_impl(node, scope)
|
|
638
|
+
|
|
639
|
+
def visitSetComp(self, node: ast.SetComp, scope: Scope) -> None:
|
|
640
|
+
return self.visit_gen_impl(node, scope)
|
|
641
|
+
|
|
642
|
+
def visitListComp(self, node: ast.ListComp, scope: Scope) -> None:
|
|
643
|
+
return self.visit_gen_impl(node, scope)
|
|
644
|
+
|
|
645
|
+
def visitDictComp(self, node: ast.DictComp, scope: Scope) -> None:
|
|
646
|
+
return self.visit_gen_impl(node, scope)
|
|
647
|
+
|
|
648
|
+
def visitcomprehension(
|
|
649
|
+
self, node: ast.comprehension, scope: Scope, is_outmost: bool
|
|
650
|
+
) -> None:
|
|
651
|
+
if node.is_async:
|
|
652
|
+
scope.coroutine = True
|
|
653
|
+
|
|
654
|
+
scope.comp_iter_target = 1
|
|
655
|
+
self.visit(node.target, scope)
|
|
656
|
+
scope.comp_iter_target = 0
|
|
657
|
+
if is_outmost:
|
|
658
|
+
scope.add_use(".0")
|
|
659
|
+
else:
|
|
660
|
+
scope.comp_iter_expr += 1
|
|
661
|
+
self.visit(node.iter, scope)
|
|
662
|
+
scope.comp_iter_expr -= 1
|
|
663
|
+
for if_ in node.ifs:
|
|
664
|
+
self.visit(if_, scope)
|
|
665
|
+
|
|
666
|
+
def visitLambda(self, node: ast.Lambda, parent: Scope) -> None:
|
|
667
|
+
scope = self._LambdaScope(self.module, self.klass, lineno=node.lineno)
|
|
668
|
+
scope.parent = parent
|
|
669
|
+
# bpo-37757: For now, disallow *all* assignment expressions in the
|
|
670
|
+
# outermost iterator expression of a comprehension, even those inside
|
|
671
|
+
# a nested comprehension or a lambda expression.
|
|
672
|
+
scope.comp_iter_expr = parent.comp_iter_expr
|
|
673
|
+
if parent.nested or isinstance(parent, FUNCTION_LIKE_SCOPES):
|
|
674
|
+
scope.nested = True
|
|
675
|
+
self.scopes[node] = scope
|
|
676
|
+
self._do_args(scope, node.args)
|
|
677
|
+
self.visit(node.body, scope)
|
|
678
|
+
parent.add_child(scope)
|
|
679
|
+
|
|
680
|
+
def _do_args(self, scope: Scope, args: ast.arguments) -> None:
|
|
681
|
+
for n in args.defaults:
|
|
682
|
+
self.visit(n, scope.parent)
|
|
683
|
+
for n in args.kw_defaults:
|
|
684
|
+
if n:
|
|
685
|
+
self.visit(n, scope.parent)
|
|
686
|
+
|
|
687
|
+
for arg in args.posonlyargs:
|
|
688
|
+
name = arg.arg
|
|
689
|
+
scope.add_param(name)
|
|
690
|
+
if arg.annotation and not self.future_annotations:
|
|
691
|
+
self.visit(arg.annotation, scope.parent)
|
|
692
|
+
for arg in args.args:
|
|
693
|
+
name = arg.arg
|
|
694
|
+
scope.add_param(name)
|
|
695
|
+
if arg.annotation and not self.future_annotations:
|
|
696
|
+
self.visit(arg.annotation, scope.parent)
|
|
697
|
+
for arg in args.kwonlyargs:
|
|
698
|
+
name = arg.arg
|
|
699
|
+
scope.add_param(name)
|
|
700
|
+
if arg.annotation and not self.future_annotations:
|
|
701
|
+
self.visit(arg.annotation, scope.parent)
|
|
702
|
+
if vararg := args.vararg:
|
|
703
|
+
scope.add_param(vararg.arg)
|
|
704
|
+
if (annotation := vararg.annotation) and not self.future_annotations:
|
|
705
|
+
self.visit(annotation, scope.parent)
|
|
706
|
+
if kwarg := args.kwarg:
|
|
707
|
+
scope.add_param(kwarg.arg)
|
|
708
|
+
if (annotation := kwarg.annotation) and not self.future_annotations:
|
|
709
|
+
self.visit(annotation, scope.parent)
|
|
710
|
+
|
|
711
|
+
def visitClassDef(self, node: ast.ClassDef, parent: Scope) -> None:
|
|
712
|
+
if node.decorator_list:
|
|
713
|
+
self.visit_list(node.decorator_list, parent)
|
|
714
|
+
|
|
715
|
+
parent.add_def(node.name)
|
|
716
|
+
|
|
717
|
+
type_params = getattr(node, "type_params", ())
|
|
718
|
+
|
|
719
|
+
if type_params:
|
|
720
|
+
prev = self.klass
|
|
721
|
+
self.klass = node.name
|
|
722
|
+
parent = self.enter_type_params(node, parent)
|
|
723
|
+
for param in type_params:
|
|
724
|
+
self.visit(param, parent)
|
|
725
|
+
self.klass = prev
|
|
726
|
+
|
|
727
|
+
for kw in node.keywords:
|
|
728
|
+
self.visit(kw.value, parent)
|
|
729
|
+
|
|
730
|
+
for n in node.bases:
|
|
731
|
+
self.visit(n, parent)
|
|
732
|
+
scope = ClassScope(node.name, self.module, lineno=node.lineno)
|
|
733
|
+
# Set parent ASAP. TODO: Probably makes sense to do that for
|
|
734
|
+
# other scope types either.
|
|
735
|
+
scope.parent = parent
|
|
736
|
+
if type_params:
|
|
737
|
+
scope.add_def("__type_params__")
|
|
738
|
+
scope.add_use(".type_params")
|
|
739
|
+
|
|
740
|
+
if parent.nested or isinstance(parent, FUNCTION_LIKE_SCOPES):
|
|
741
|
+
scope.nested = True
|
|
742
|
+
doc = ast.get_docstring(node, False)
|
|
743
|
+
if doc is not None:
|
|
744
|
+
scope.add_def("__doc__")
|
|
745
|
+
scope.has_docstring = True
|
|
746
|
+
scope.add_def("__module__")
|
|
747
|
+
scope.add_def("__qualname__")
|
|
748
|
+
self.scopes[node] = scope
|
|
749
|
+
prev = self.klass
|
|
750
|
+
self.klass = node.name
|
|
751
|
+
self.visit_list(node.body, scope)
|
|
752
|
+
self.klass = prev
|
|
753
|
+
parent.add_child(scope)
|
|
754
|
+
|
|
755
|
+
def visitTypeAlias(self, node: ast.TypeAlias, parent: Scope) -> None:
|
|
756
|
+
self.visit(node.name, parent)
|
|
757
|
+
|
|
758
|
+
in_class = isinstance(parent, ClassScope)
|
|
759
|
+
is_generic = len(node.type_params) > 0
|
|
760
|
+
alias_parent = parent
|
|
761
|
+
if is_generic:
|
|
762
|
+
alias_parent = self.enter_type_params(node, parent)
|
|
763
|
+
self.visit_list(node.type_params, alias_parent)
|
|
764
|
+
|
|
765
|
+
scope = TypeAliasScope(node.name.id, self.module)
|
|
766
|
+
scope.klass = self.klass
|
|
767
|
+
if alias_parent.nested or isinstance(alias_parent, FUNCTION_LIKE_SCOPES):
|
|
768
|
+
scope.nested = True
|
|
769
|
+
scope.parent = alias_parent
|
|
770
|
+
scope.can_see_class_scope = in_class
|
|
771
|
+
if in_class:
|
|
772
|
+
scope.add_use("__classdict__")
|
|
773
|
+
parent.add_def("__classdict__")
|
|
774
|
+
|
|
775
|
+
self.scopes[node] = scope
|
|
776
|
+
self.visit(node.value, scope)
|
|
777
|
+
alias_parent.add_child(scope)
|
|
778
|
+
|
|
779
|
+
# name can be a def or a use
|
|
780
|
+
def visitName(self, node: ast.Name, scope: Scope) -> None:
|
|
781
|
+
if isinstance(node.ctx, ast.Store):
|
|
782
|
+
if scope.comp_iter_target:
|
|
783
|
+
# This name is an iteration variable in a comprehension,
|
|
784
|
+
# so check for a binding conflict with any named expressions.
|
|
785
|
+
# Otherwise, mark it as an iteration variable so subsequent
|
|
786
|
+
# named expressions can check for conflicts.
|
|
787
|
+
if node.id in scope.nonlocals or node.id in scope.globals:
|
|
788
|
+
raise SyntaxError(
|
|
789
|
+
f"comprehension inner loop cannot rebind assignment expression target '{node.id}'"
|
|
790
|
+
)
|
|
791
|
+
scope.add_def(node.id, DEF_COMP_ITER)
|
|
792
|
+
|
|
793
|
+
scope.add_def(node.id)
|
|
794
|
+
elif isinstance(node.ctx, ast.Del):
|
|
795
|
+
# We do something to var, so even if we "undefine" it, it's a def.
|
|
796
|
+
# Implementation-wise, delete is storing special value (usually
|
|
797
|
+
# NULL) to var.
|
|
798
|
+
scope.add_def(node.id)
|
|
799
|
+
else:
|
|
800
|
+
scope.add_use(node.id)
|
|
801
|
+
|
|
802
|
+
if node.id == "super" and isinstance(scope, FUNCTION_LIKE_SCOPES):
|
|
803
|
+
# If super() is used, and special cell var __class__ to class
|
|
804
|
+
# definition, and free var to the method. This is complicated
|
|
805
|
+
# by the fact that original Python2 implementation supports
|
|
806
|
+
# free->cell var relationship only if free var is defined in
|
|
807
|
+
# a scope marked as "nested", which normal method in a class
|
|
808
|
+
# isn't.
|
|
809
|
+
scope.add_use("__class__")
|
|
810
|
+
|
|
811
|
+
# operations that bind new names
|
|
812
|
+
|
|
813
|
+
def visitMatch(self, node: ast.Match, scope: Scope) -> None:
|
|
814
|
+
self.visit(node.subject, scope)
|
|
815
|
+
with self.conditional_block():
|
|
816
|
+
self.visit_list(node.cases, scope)
|
|
817
|
+
|
|
818
|
+
def visitMatchAs(self, node: ast.MatchAs, scope: Scope) -> None:
|
|
819
|
+
if node.pattern:
|
|
820
|
+
self.visit(node.pattern, scope)
|
|
821
|
+
if node.name:
|
|
822
|
+
scope.add_def(node.name)
|
|
823
|
+
|
|
824
|
+
def visitMatchStar(self, node: ast.MatchStar, scope: Scope) -> None:
|
|
825
|
+
if node.name:
|
|
826
|
+
scope.add_def(node.name)
|
|
827
|
+
|
|
828
|
+
def visitMatchMapping(self, node: ast.MatchMapping, scope: Scope) -> None:
|
|
829
|
+
self.visit_list(node.keys, scope)
|
|
830
|
+
self.visit_list(node.patterns, scope)
|
|
831
|
+
if node.rest:
|
|
832
|
+
scope.add_def(node.rest)
|
|
833
|
+
|
|
834
|
+
def visitNamedExpr(self, node: ast.NamedExpr, scope: Scope) -> None:
|
|
835
|
+
if scope.comp_iter_expr:
|
|
836
|
+
# Assignment isn't allowed in a comprehension iterable expression
|
|
837
|
+
raise SyntaxError(
|
|
838
|
+
"assignment expression cannot be used in a comprehension iterable expression"
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
name = node.target.id
|
|
842
|
+
if isinstance(scope, GenExprScope):
|
|
843
|
+
cur = scope
|
|
844
|
+
mangled = scope.mangle(name)
|
|
845
|
+
while cur:
|
|
846
|
+
if isinstance(cur, GenExprScope):
|
|
847
|
+
if cur.defs.get(mangled, 0) & DEF_COMP_ITER:
|
|
848
|
+
raise SyntaxError(
|
|
849
|
+
f"assignment expression cannot rebind comprehension iteration variable '{name}'"
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
elif isinstance(cur, FunctionScope):
|
|
853
|
+
# If we find a FunctionBlock entry, add as GLOBAL/LOCAL or NONLOCAL/LOCAL
|
|
854
|
+
if mangled not in cur.explicit_globals:
|
|
855
|
+
scope.frees[mangled] = 1
|
|
856
|
+
scope.nonlocals[mangled] = 1
|
|
857
|
+
else:
|
|
858
|
+
scope.explicit_globals[mangled] = 1
|
|
859
|
+
scope.add_use(mangled)
|
|
860
|
+
cur.add_def(mangled)
|
|
861
|
+
break
|
|
862
|
+
elif isinstance(cur, ModuleScope):
|
|
863
|
+
scope.globals[mangled] = 1
|
|
864
|
+
scope.add_use(mangled)
|
|
865
|
+
cur.add_def(mangled)
|
|
866
|
+
break
|
|
867
|
+
elif isinstance(cur, ClassScope):
|
|
868
|
+
raise SyntaxError(
|
|
869
|
+
"assignment expression within a comprehension cannot be used in a class body"
|
|
870
|
+
)
|
|
871
|
+
cur = cur.parent
|
|
872
|
+
|
|
873
|
+
self.visit(node.value, scope)
|
|
874
|
+
self.visit(node.target, scope)
|
|
875
|
+
|
|
876
|
+
def visitWhile(self, node: ast.While, scope: Scope) -> None:
|
|
877
|
+
self.visit(node.test, scope)
|
|
878
|
+
with self.conditional_block():
|
|
879
|
+
self.visit_list(node.body, scope)
|
|
880
|
+
if node.orelse:
|
|
881
|
+
self.visit_list(node.orelse, scope)
|
|
882
|
+
|
|
883
|
+
def visitFor(self, node: ast.For, scope: Scope) -> None:
|
|
884
|
+
self.visit_for_impl(node, scope)
|
|
885
|
+
|
|
886
|
+
def visitAsyncFor(self, node: ast.AsyncFor, scope: Scope) -> None:
|
|
887
|
+
self.visit_for_impl(node, scope)
|
|
888
|
+
|
|
889
|
+
def visit_for_impl(self, node: ast.For | ast.AsyncFor, scope: Scope) -> None:
|
|
890
|
+
self.visit(node.target, scope)
|
|
891
|
+
self.visit(node.iter, scope)
|
|
892
|
+
with self.conditional_block():
|
|
893
|
+
self.visit_list(node.body, scope)
|
|
894
|
+
if node.orelse:
|
|
895
|
+
self.visit_list(node.orelse, scope)
|
|
896
|
+
|
|
897
|
+
def visitImportFrom(self, node: ast.ImportFrom, scope: Scope) -> None:
|
|
898
|
+
for alias in node.names:
|
|
899
|
+
if alias.name == "*":
|
|
900
|
+
continue
|
|
901
|
+
impname = alias.asname or alias.name
|
|
902
|
+
scope.add_def(impname)
|
|
903
|
+
scope.add_import(impname)
|
|
904
|
+
|
|
905
|
+
def visitImport(self, node: ast.Import, scope: Scope) -> None:
|
|
906
|
+
for alias in node.names:
|
|
907
|
+
name = alias.name
|
|
908
|
+
i = name.find(".")
|
|
909
|
+
if i > -1:
|
|
910
|
+
name = name[:i]
|
|
911
|
+
impname = alias.asname or name
|
|
912
|
+
scope.add_def(impname)
|
|
913
|
+
scope.add_import(impname)
|
|
914
|
+
|
|
915
|
+
def visitGlobal(self, node: ast.Global, scope: Scope) -> None:
|
|
916
|
+
for name in node.names:
|
|
917
|
+
scope.add_global(name)
|
|
918
|
+
|
|
919
|
+
def visitNonlocal(self, node: ast.Nonlocal, scope: Scope) -> None:
|
|
920
|
+
# TODO: Check that var exists in outer scope
|
|
921
|
+
for name in node.names:
|
|
922
|
+
scope.frees[name] = 1
|
|
923
|
+
scope.nonlocals[name] = 1
|
|
924
|
+
|
|
925
|
+
def visitAssign(self, node: ast.Assign, scope: Scope) -> None:
|
|
926
|
+
"""Propagate assignment flag down to child nodes.
|
|
927
|
+
|
|
928
|
+
The Assign node doesn't itself contains the variables being
|
|
929
|
+
assigned to. Instead, the children in node.nodes are visited
|
|
930
|
+
with the assign flag set to true. When the names occur in
|
|
931
|
+
those nodes, they are marked as defs.
|
|
932
|
+
|
|
933
|
+
Some names that occur in an assignment target are not bound by
|
|
934
|
+
the assignment, e.g. a name occurring inside a slice. The
|
|
935
|
+
visitor handles these nodes specially; they do not propagate
|
|
936
|
+
the assign flag to their children.
|
|
937
|
+
"""
|
|
938
|
+
for n in node.targets:
|
|
939
|
+
self.visit(n, scope)
|
|
940
|
+
self.visit(node.value, scope)
|
|
941
|
+
|
|
942
|
+
def visitAnnAssign(self, node: ast.AnnAssign, scope: Scope) -> None:
|
|
943
|
+
target = node.target
|
|
944
|
+
if isinstance(target, ast.Name):
|
|
945
|
+
if not isinstance(scope, ModuleScope) and (
|
|
946
|
+
target.id in scope.nonlocals or target.id in scope.explicit_globals
|
|
947
|
+
):
|
|
948
|
+
is_nonlocal = target.id in scope.nonlocals
|
|
949
|
+
raise SyntaxError(
|
|
950
|
+
f"annotated name '{target.id}' can't be {'nonlocal' if is_nonlocal else 'global'}"
|
|
951
|
+
)
|
|
952
|
+
if node.simple or node.value:
|
|
953
|
+
scope.add_def(target.id)
|
|
954
|
+
else:
|
|
955
|
+
self.visit(node.target, scope)
|
|
956
|
+
if annotation := node.annotation:
|
|
957
|
+
self.visit_annotation(annotation, scope)
|
|
958
|
+
if node.value:
|
|
959
|
+
self.visit(node.value, scope)
|
|
960
|
+
|
|
961
|
+
def visit_annotation(self, annotation: ast.expr, scope: Scope) -> None:
|
|
962
|
+
if not self.future_annotations:
|
|
963
|
+
self.visit(annotation, scope)
|
|
964
|
+
|
|
965
|
+
def visitSubscript(self, node: ast.Subscript, scope: Scope) -> None:
|
|
966
|
+
self.visit(node.value, scope)
|
|
967
|
+
self.visit(node.slice, scope)
|
|
968
|
+
|
|
969
|
+
def visitAttribute(self, node: ast.Attribute, scope: Scope) -> None:
|
|
970
|
+
self.visit(node.value, scope)
|
|
971
|
+
|
|
972
|
+
def visitSlice(self, node: ast.Slice, scope: Scope) -> None:
|
|
973
|
+
if node.lower:
|
|
974
|
+
self.visit(node.lower, scope)
|
|
975
|
+
if node.upper:
|
|
976
|
+
self.visit(node.upper, scope)
|
|
977
|
+
if node.step:
|
|
978
|
+
self.visit(node.step, scope)
|
|
979
|
+
|
|
980
|
+
def visitAugAssign(self, node: ast.AugAssign, scope: Scope) -> None:
|
|
981
|
+
# If the LHS is a name, then this counts as assignment.
|
|
982
|
+
# Otherwise, it's just use.
|
|
983
|
+
self.visit(node.target, scope)
|
|
984
|
+
if isinstance(node.target, ast.Name):
|
|
985
|
+
self.visit(node.target, scope)
|
|
986
|
+
self.visit(node.value, scope)
|
|
987
|
+
|
|
988
|
+
# prune if statements if tests are false
|
|
989
|
+
|
|
990
|
+
_const_types = str, bytes, int, long, float
|
|
991
|
+
|
|
992
|
+
def visitIf(self, node: ast.If, scope: Scope) -> None:
|
|
993
|
+
self.visit(node.test, scope)
|
|
994
|
+
with self.conditional_block():
|
|
995
|
+
self.visit_list(node.body, scope)
|
|
996
|
+
if node.orelse:
|
|
997
|
+
self.visit_list(node.orelse, scope)
|
|
998
|
+
|
|
999
|
+
# a yield statement signals a generator
|
|
1000
|
+
|
|
1001
|
+
def visitYield(self, node: ast.Yield, scope: Scope) -> None:
|
|
1002
|
+
scope.generator = True
|
|
1003
|
+
if node.value:
|
|
1004
|
+
self.visit(node.value, scope)
|
|
1005
|
+
|
|
1006
|
+
def visitYieldFrom(self, node: ast.YieldFrom, scope: Scope) -> None:
|
|
1007
|
+
scope.generator = True
|
|
1008
|
+
if node.value:
|
|
1009
|
+
self.visit(node.value, scope)
|
|
1010
|
+
|
|
1011
|
+
def visitTry(self, node: ast.Try, scope: Scope) -> None:
|
|
1012
|
+
with self.conditional_block():
|
|
1013
|
+
self.visit_list(node.body, scope)
|
|
1014
|
+
# Handle exception capturing vars
|
|
1015
|
+
for handler in node.handlers:
|
|
1016
|
+
if handler.type:
|
|
1017
|
+
self.visit(handler.type, scope)
|
|
1018
|
+
if handler.name:
|
|
1019
|
+
scope.add_def(handler.name)
|
|
1020
|
+
self.visit_list(handler.body, scope)
|
|
1021
|
+
self.visit_list(node.orelse, scope)
|
|
1022
|
+
self.visit_list(node.finalbody, scope)
|
|
1023
|
+
|
|
1024
|
+
def visitWith(self, node: ast.With, scope: Scope) -> None:
|
|
1025
|
+
with self.conditional_block():
|
|
1026
|
+
self.visit_list(node.items, scope)
|
|
1027
|
+
self.visit_list(node.body, scope)
|
|
1028
|
+
|
|
1029
|
+
def visitAsyncWith(self, node: ast.AsyncWith, scope: Scope) -> None:
|
|
1030
|
+
with self.conditional_block():
|
|
1031
|
+
self.visit_list(node.items, scope)
|
|
1032
|
+
self.visit_list(node.body, scope)
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
class SymbolVisitor310(BaseSymbolVisitor):
|
|
1036
|
+
def visit_node_with_new_scope(
|
|
1037
|
+
self, node: ast.Expression | ast.Interactive | ast.Module
|
|
1038
|
+
) -> None:
|
|
1039
|
+
scope = self.module = self.scopes[node] = self.module
|
|
1040
|
+
body = node.body
|
|
1041
|
+
if isinstance(body, list):
|
|
1042
|
+
self.visit_list(body, scope)
|
|
1043
|
+
else:
|
|
1044
|
+
self.visit(body, scope)
|
|
1045
|
+
self.analyze_block(scope, free=set(), global_vars=set())
|
|
1046
|
+
|
|
1047
|
+
def visitModule(self, node: ast.Module) -> None:
|
|
1048
|
+
doc = ast.get_docstring(node)
|
|
1049
|
+
if doc is not None:
|
|
1050
|
+
self.module.has_docstring = True
|
|
1051
|
+
self.visit_node_with_new_scope(node)
|
|
1052
|
+
|
|
1053
|
+
def visitInteractive(self, node: ast.Interactive) -> None:
|
|
1054
|
+
self.visit_node_with_new_scope(node)
|
|
1055
|
+
|
|
1056
|
+
def visitExpression(self, node: ast.Expression) -> None:
|
|
1057
|
+
self.visit_node_with_new_scope(node)
|
|
1058
|
+
|
|
1059
|
+
def analyze_block(
|
|
1060
|
+
self,
|
|
1061
|
+
scope: Scope,
|
|
1062
|
+
free: set[str],
|
|
1063
|
+
global_vars: set[str],
|
|
1064
|
+
bound: set[str] | None = None,
|
|
1065
|
+
implicit_globals: set[str] | None = None,
|
|
1066
|
+
) -> None:
|
|
1067
|
+
local: set[str] = set()
|
|
1068
|
+
implicit_globals_in_block: set[str] = set()
|
|
1069
|
+
inlinable_comprehensions = []
|
|
1070
|
+
# Allocate new global and bound variable dictionaries. These
|
|
1071
|
+
# dictionaries hold the names visible in nested blocks. For
|
|
1072
|
+
# ClassScopes, the bound and global names are initialized
|
|
1073
|
+
# before analyzing names, because class bindings aren't
|
|
1074
|
+
# visible in methods. For other blocks, they are initialized
|
|
1075
|
+
# after names are analyzed.
|
|
1076
|
+
|
|
1077
|
+
new_global: set[str] = set()
|
|
1078
|
+
new_free: set[str] = set()
|
|
1079
|
+
new_bound: set[str] = set()
|
|
1080
|
+
|
|
1081
|
+
if isinstance(scope, ClassScope):
|
|
1082
|
+
new_global |= global_vars
|
|
1083
|
+
if bound is not None:
|
|
1084
|
+
new_bound |= bound
|
|
1085
|
+
|
|
1086
|
+
scope.analyze_names(bound, local, free, global_vars)
|
|
1087
|
+
# Populate global and bound sets to be passed to children.
|
|
1088
|
+
if not isinstance(scope, ClassScope):
|
|
1089
|
+
if isinstance(scope, FUNCTION_LIKE_SCOPES):
|
|
1090
|
+
new_bound |= local
|
|
1091
|
+
if bound:
|
|
1092
|
+
new_bound |= bound
|
|
1093
|
+
|
|
1094
|
+
new_global |= global_vars
|
|
1095
|
+
else:
|
|
1096
|
+
# Special case __class__/__classdict__
|
|
1097
|
+
new_bound.add("__class__")
|
|
1098
|
+
new_bound.add("__classdict__")
|
|
1099
|
+
|
|
1100
|
+
# create set of names that inlined comprehension should never stomp on
|
|
1101
|
+
# collect all local defs and params
|
|
1102
|
+
local_names = set(scope.defs.keys()) | set(scope.uses.keys())
|
|
1103
|
+
|
|
1104
|
+
# in case comprehension will be inlined, track its set of free locals separately
|
|
1105
|
+
all_free: set[str] = set()
|
|
1106
|
+
|
|
1107
|
+
for child in scope.children:
|
|
1108
|
+
if child.name in scope.explicit_globals:
|
|
1109
|
+
child.global_scope = True
|
|
1110
|
+
|
|
1111
|
+
child_free = all_free
|
|
1112
|
+
|
|
1113
|
+
maybe_inline_comp = (
|
|
1114
|
+
isinstance(scope, FunctionScope)
|
|
1115
|
+
and scope._inline_comprehensions
|
|
1116
|
+
and isinstance(child, GenExprScope)
|
|
1117
|
+
and not child.generator
|
|
1118
|
+
)
|
|
1119
|
+
if maybe_inline_comp:
|
|
1120
|
+
child_free = set()
|
|
1121
|
+
|
|
1122
|
+
self.analyze_child_block(
|
|
1123
|
+
child,
|
|
1124
|
+
new_bound,
|
|
1125
|
+
new_free,
|
|
1126
|
+
new_global,
|
|
1127
|
+
child_free,
|
|
1128
|
+
implicit_globals_in_block,
|
|
1129
|
+
)
|
|
1130
|
+
inline_comp = maybe_inline_comp and not child.child_free
|
|
1131
|
+
|
|
1132
|
+
if inline_comp:
|
|
1133
|
+
# record the comprehension
|
|
1134
|
+
inlinable_comprehensions.append((child, child_free))
|
|
1135
|
+
elif maybe_inline_comp:
|
|
1136
|
+
all_free.update(child_free)
|
|
1137
|
+
child_free = all_free
|
|
1138
|
+
|
|
1139
|
+
local_names |= child_free
|
|
1140
|
+
|
|
1141
|
+
local_names |= implicit_globals_in_block
|
|
1142
|
+
|
|
1143
|
+
if implicit_globals is not None:
|
|
1144
|
+
# merge collected implicit globals into set for the outer scope
|
|
1145
|
+
implicit_globals |= implicit_globals_in_block
|
|
1146
|
+
implicit_globals.update(scope.globals.keys())
|
|
1147
|
+
|
|
1148
|
+
# collect inlinable comprehensions
|
|
1149
|
+
if inlinable_comprehensions:
|
|
1150
|
+
for comp, comp_all_free in reversed(inlinable_comprehensions):
|
|
1151
|
+
exists = comp.local_names_include_defs(local_names)
|
|
1152
|
+
if not exists:
|
|
1153
|
+
# comprehension can be inlined
|
|
1154
|
+
self.merge_comprehension_symbols(scope, comp, comp_all_free)
|
|
1155
|
+
# remove child from parent
|
|
1156
|
+
scope.children.remove(comp)
|
|
1157
|
+
for c in comp.children:
|
|
1158
|
+
c.parent = scope
|
|
1159
|
+
# mark comprehension as inlined
|
|
1160
|
+
comp.inlined = True
|
|
1161
|
+
all_free |= comp_all_free
|
|
1162
|
+
|
|
1163
|
+
for child in scope.children:
|
|
1164
|
+
if child.free or child.child_free:
|
|
1165
|
+
scope.child_free = True
|
|
1166
|
+
|
|
1167
|
+
new_free |= all_free
|
|
1168
|
+
|
|
1169
|
+
if isinstance(scope, FUNCTION_LIKE_SCOPES):
|
|
1170
|
+
scope.analyze_cells(new_free)
|
|
1171
|
+
elif isinstance(scope, ClassScope):
|
|
1172
|
+
# drop class free
|
|
1173
|
+
if "__class__" in new_free:
|
|
1174
|
+
new_free.remove("__class__")
|
|
1175
|
+
scope.needs_class_closure = True
|
|
1176
|
+
if "__classdict__" in new_free:
|
|
1177
|
+
new_free.remove("__classdict__")
|
|
1178
|
+
scope.needs_classdict = True
|
|
1179
|
+
|
|
1180
|
+
scope.update_symbols(bound, new_free)
|
|
1181
|
+
free |= new_free
|
|
1182
|
+
|
|
1183
|
+
def analyze_child_block(
|
|
1184
|
+
self,
|
|
1185
|
+
scope: Scope,
|
|
1186
|
+
bound: set[str],
|
|
1187
|
+
free: set[str],
|
|
1188
|
+
global_vars: set[str],
|
|
1189
|
+
child_free: set[str],
|
|
1190
|
+
implicit_globals: set[str],
|
|
1191
|
+
) -> None:
|
|
1192
|
+
temp_bound = set(bound)
|
|
1193
|
+
temp_free = set(free)
|
|
1194
|
+
temp_global = set(free)
|
|
1195
|
+
|
|
1196
|
+
self.analyze_block(scope, temp_free, temp_global, temp_bound, implicit_globals)
|
|
1197
|
+
child_free |= temp_free
|
|
1198
|
+
|
|
1199
|
+
def merge_comprehension_symbols(
|
|
1200
|
+
self, scope: Scope, comp: Scope, comp_all_free: set[str]
|
|
1201
|
+
) -> None:
|
|
1202
|
+
# merge defs from comprehension scope into current scope
|
|
1203
|
+
for v in comp.defs:
|
|
1204
|
+
if v != ".0":
|
|
1205
|
+
scope.add_def(v)
|
|
1206
|
+
|
|
1207
|
+
# for names that are free in comprehension
|
|
1208
|
+
# and not present in defs of current scope -
|
|
1209
|
+
# add them as free in current scope
|
|
1210
|
+
for d in comp.uses:
|
|
1211
|
+
if comp.check_name(d) == SC_FREE and d not in scope.defs:
|
|
1212
|
+
sc = scope.check_name(d)
|
|
1213
|
+
if sc == SC_UNKNOWN:
|
|
1214
|
+
# name is missing in current scope - add it
|
|
1215
|
+
scope.frees[d] = 1
|
|
1216
|
+
elif comp.check_name(d) == SC_GLOBAL_IMPLICIT:
|
|
1217
|
+
scope.globals[d] = 1
|
|
1218
|
+
|
|
1219
|
+
# go through free names in comprehension
|
|
1220
|
+
# and check if current scope has corresponding def
|
|
1221
|
+
# if yes - name is no longer free after inlining
|
|
1222
|
+
for f in list(comp.frees.keys()):
|
|
1223
|
+
if f in scope.defs:
|
|
1224
|
+
comp_all_free.remove(f)
|
|
1225
|
+
|
|
1226
|
+
# move names uses in comprehension to current scope
|
|
1227
|
+
for u in comp.uses.keys():
|
|
1228
|
+
if u != ".0" or u == "__classdict__":
|
|
1229
|
+
scope.add_use(u)
|
|
1230
|
+
|
|
1231
|
+
# cell vars in comprehension become cells in current scope
|
|
1232
|
+
for c in comp.cells.keys():
|
|
1233
|
+
if c != ".0":
|
|
1234
|
+
scope.cells[c] = 1
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
# Alias for the default 3.10 visitor
|
|
1238
|
+
SymbolVisitor = SymbolVisitor310
|
|
1239
|
+
|
|
1240
|
+
|
|
1241
|
+
class CinderFunctionScope(FunctionScope):
|
|
1242
|
+
def __init__(
|
|
1243
|
+
self, name: str, module: ModuleScope, klass: str | None = None, lineno: int = 0
|
|
1244
|
+
) -> None:
|
|
1245
|
+
super().__init__(name=name, module=module, klass=klass, lineno=lineno)
|
|
1246
|
+
self._inline_comprehensions: bool = bool(
|
|
1247
|
+
os.getenv("PYTHONINLINECOMPREHENSIONS")
|
|
1248
|
+
)
|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
class CinderGenExprScope(GenExprScope, CinderFunctionScope):
|
|
1252
|
+
inlined = False
|
|
1253
|
+
|
|
1254
|
+
|
|
1255
|
+
class CinderLambdaScope(LambdaScope, CinderFunctionScope):
|
|
1256
|
+
pass
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
class CinderSymbolVisitor(SymbolVisitor):
|
|
1260
|
+
_FunctionScope = CinderFunctionScope
|
|
1261
|
+
_GenExprScope = CinderGenExprScope
|
|
1262
|
+
_LambdaScope = CinderLambdaScope
|
|
1263
|
+
|
|
1264
|
+
def visit_gen_impl(
|
|
1265
|
+
self,
|
|
1266
|
+
node: ast.GeneratorExp | ast.ListComp | ast.DictComp | ast.SetComp,
|
|
1267
|
+
parent: Scope,
|
|
1268
|
+
) -> None:
|
|
1269
|
+
scope = self._GenExprScope(
|
|
1270
|
+
self._scope_names[type(node)],
|
|
1271
|
+
self.module,
|
|
1272
|
+
self.klass,
|
|
1273
|
+
lineno=node.lineno,
|
|
1274
|
+
)
|
|
1275
|
+
scope.parent = parent
|
|
1276
|
+
|
|
1277
|
+
# bpo-37757: For now, disallow *all* assignment expressions in the
|
|
1278
|
+
# outermost iterator expression of a comprehension, even those inside
|
|
1279
|
+
# a nested comprehension or a lambda expression.
|
|
1280
|
+
scope.comp_iter_expr = parent.comp_iter_expr
|
|
1281
|
+
if isinstance(node, ast.GeneratorExp):
|
|
1282
|
+
scope.generator = True
|
|
1283
|
+
|
|
1284
|
+
if (
|
|
1285
|
+
parent.nested
|
|
1286
|
+
or isinstance(parent, FunctionScope)
|
|
1287
|
+
or isinstance(parent, GenExprScope)
|
|
1288
|
+
):
|
|
1289
|
+
scope.nested = True
|
|
1290
|
+
|
|
1291
|
+
parent.comp_iter_expr += 1
|
|
1292
|
+
self.visit(node.generators[0].iter, parent)
|
|
1293
|
+
parent.comp_iter_expr -= 1
|
|
1294
|
+
|
|
1295
|
+
self.visitcomprehension(node.generators[0], scope, True)
|
|
1296
|
+
|
|
1297
|
+
for comp in node.generators[1:]:
|
|
1298
|
+
self.visit(comp, scope, False)
|
|
1299
|
+
|
|
1300
|
+
if isinstance(node, ast.DictComp):
|
|
1301
|
+
self.visit(node.value, scope)
|
|
1302
|
+
self.visit(node.key, scope)
|
|
1303
|
+
else:
|
|
1304
|
+
self.visit(node.elt, scope)
|
|
1305
|
+
|
|
1306
|
+
self.scopes[node] = scope
|
|
1307
|
+
|
|
1308
|
+
parent.add_child(scope)
|
|
1309
|
+
|
|
1310
|
+
def visitLambda(self, node: ast.Lambda, parent: Scope) -> None:
|
|
1311
|
+
scope = self._LambdaScope(self.module, self.klass, lineno=node.lineno)
|
|
1312
|
+
scope.parent = parent
|
|
1313
|
+
# bpo-37757: For now, disallow *all* assignment expressions in the
|
|
1314
|
+
# outermost iterator expression of a comprehension, even those inside
|
|
1315
|
+
# a nested comprehension or a lambda expression.
|
|
1316
|
+
scope.comp_iter_expr = parent.comp_iter_expr
|
|
1317
|
+
if parent.nested or isinstance(parent, FunctionScope):
|
|
1318
|
+
scope.nested = True
|
|
1319
|
+
self.scopes[node] = scope
|
|
1320
|
+
self._do_args(scope, node.args)
|
|
1321
|
+
self.visit(node.body, scope)
|
|
1322
|
+
|
|
1323
|
+
parent.add_child(scope)
|
|
1324
|
+
|
|
1325
|
+
def visitFunctionDef(self, node: ast.FunctionDef, parent: Scope) -> None:
|
|
1326
|
+
self._visit_func_impl(node, parent)
|
|
1327
|
+
|
|
1328
|
+
def visitAsyncFunctionDef(self, node: ast.AsyncFunctionDef, parent: Scope) -> None:
|
|
1329
|
+
self._visit_func_impl(node, parent)
|
|
1330
|
+
|
|
1331
|
+
def _visit_func_impl(
|
|
1332
|
+
self, node: ast.FunctionDef | ast.AsyncFunctionDef, parent: Scope
|
|
1333
|
+
) -> None:
|
|
1334
|
+
if node.decorator_list:
|
|
1335
|
+
self.visit_list(node.decorator_list, parent)
|
|
1336
|
+
parent.add_def(node.name)
|
|
1337
|
+
scope = self._FunctionScope(
|
|
1338
|
+
node.name, self.module, self.klass, lineno=node.lineno
|
|
1339
|
+
)
|
|
1340
|
+
scope.coroutine = isinstance(node, ast.AsyncFunctionDef)
|
|
1341
|
+
scope.parent = parent
|
|
1342
|
+
if parent.nested or isinstance(parent, FunctionScope):
|
|
1343
|
+
scope.nested = True
|
|
1344
|
+
self.scopes[node] = scope
|
|
1345
|
+
self._do_args(scope, node.args)
|
|
1346
|
+
if returns := node.returns:
|
|
1347
|
+
if not self.future_annotations:
|
|
1348
|
+
self.visit(returns, parent)
|
|
1349
|
+
self.visit_list(node.body, scope)
|
|
1350
|
+
|
|
1351
|
+
parent.add_child(scope)
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
class SymbolVisitor312(BaseSymbolVisitor):
|
|
1355
|
+
def visit_node_with_new_scope(
|
|
1356
|
+
self, node: ast.Expression | ast.Interactive | ast.Module
|
|
1357
|
+
) -> None:
|
|
1358
|
+
scope = self.module = self.scopes[node] = self.module
|
|
1359
|
+
body = node.body
|
|
1360
|
+
if isinstance(body, list):
|
|
1361
|
+
self.visit_list(body, scope)
|
|
1362
|
+
else:
|
|
1363
|
+
self.visit(body, scope)
|
|
1364
|
+
self.analyze_block(scope, free=set(), global_vars=set())
|
|
1365
|
+
|
|
1366
|
+
def visitModule(self, node: ast.Module) -> None:
|
|
1367
|
+
self.visit_node_with_new_scope(node)
|
|
1368
|
+
|
|
1369
|
+
def visitInteractive(self, node: ast.Interactive) -> None:
|
|
1370
|
+
self.visit_node_with_new_scope(node)
|
|
1371
|
+
|
|
1372
|
+
def visitExpression(self, node: ast.Expression) -> None:
|
|
1373
|
+
self.visit_node_with_new_scope(node)
|
|
1374
|
+
|
|
1375
|
+
# pyre-ignore[11]: TryStar added in 3.11, pyre still running in 3.10 mode.
|
|
1376
|
+
def visitTryStar(self, node: ast.TryStar, scope: Scope) -> None:
|
|
1377
|
+
# pyre-fixme[6]: For 1st argument expected `Try` but got `TryStar`.
|
|
1378
|
+
return self.visitTry(node, scope)
|
|
1379
|
+
|
|
1380
|
+
def analyze_block(
|
|
1381
|
+
self,
|
|
1382
|
+
scope: Scope,
|
|
1383
|
+
free: set[str],
|
|
1384
|
+
global_vars: set[str],
|
|
1385
|
+
bound: set[str] | None = None,
|
|
1386
|
+
class_entry: Scope | None = None,
|
|
1387
|
+
) -> None:
|
|
1388
|
+
local: set[str] = set()
|
|
1389
|
+
# Allocate new global and bound variable dictionaries. These
|
|
1390
|
+
# dictionaries hold the names visible in nested blocks. For
|
|
1391
|
+
# ClassScopes, the bound and global names are initialized
|
|
1392
|
+
# before analyzing names, because class bindings aren't
|
|
1393
|
+
# visible in methods. For other blocks, they are initialized
|
|
1394
|
+
# after names are analyzed.
|
|
1395
|
+
|
|
1396
|
+
new_global: set[str] = set()
|
|
1397
|
+
new_free: set[str] = set()
|
|
1398
|
+
new_bound: set[str] = set()
|
|
1399
|
+
inlined_cells: set[str] = set()
|
|
1400
|
+
|
|
1401
|
+
if isinstance(scope, ClassScope):
|
|
1402
|
+
new_global |= global_vars
|
|
1403
|
+
if bound is not None:
|
|
1404
|
+
new_bound |= bound
|
|
1405
|
+
|
|
1406
|
+
scope.analyze_names(bound, local, free, global_vars, class_entry)
|
|
1407
|
+
# Populate global and bound sets to be passed to children.
|
|
1408
|
+
if not isinstance(scope, ClassScope):
|
|
1409
|
+
if isinstance(scope, FUNCTION_LIKE_SCOPES):
|
|
1410
|
+
new_bound |= local
|
|
1411
|
+
if bound:
|
|
1412
|
+
new_bound |= bound
|
|
1413
|
+
|
|
1414
|
+
new_global |= global_vars
|
|
1415
|
+
else:
|
|
1416
|
+
# Special case __class__/__classdict__
|
|
1417
|
+
new_bound.add("__class__")
|
|
1418
|
+
new_bound.add("__classdict__")
|
|
1419
|
+
|
|
1420
|
+
# in case comprehension will be inlined, track its set of free locals separately
|
|
1421
|
+
all_free: set[str] = set()
|
|
1422
|
+
|
|
1423
|
+
for child in scope.children:
|
|
1424
|
+
if child.name in scope.explicit_globals:
|
|
1425
|
+
child.global_scope = True
|
|
1426
|
+
|
|
1427
|
+
child_free = all_free
|
|
1428
|
+
|
|
1429
|
+
inline_comp = isinstance(child, GenExprScope) and not child.generator
|
|
1430
|
+
if inline_comp:
|
|
1431
|
+
child_free = set()
|
|
1432
|
+
|
|
1433
|
+
new_class_entry = None
|
|
1434
|
+
if child.can_see_class_scope:
|
|
1435
|
+
if isinstance(scope, ClassScope):
|
|
1436
|
+
new_class_entry = scope
|
|
1437
|
+
else:
|
|
1438
|
+
new_class_entry = class_entry
|
|
1439
|
+
|
|
1440
|
+
self.analyze_child_block(
|
|
1441
|
+
child, new_bound, new_free, new_global, child_free, new_class_entry
|
|
1442
|
+
)
|
|
1443
|
+
|
|
1444
|
+
if inline_comp:
|
|
1445
|
+
assert isinstance(child, GenExprScope)
|
|
1446
|
+
self.inline_comprehension(scope, child, child_free, inlined_cells)
|
|
1447
|
+
|
|
1448
|
+
new_free |= child_free
|
|
1449
|
+
|
|
1450
|
+
for child in scope.children:
|
|
1451
|
+
if child.free or child.child_free:
|
|
1452
|
+
scope.child_free = True
|
|
1453
|
+
|
|
1454
|
+
# Splice children of inlined comprehensions into our children list
|
|
1455
|
+
for i, child in enumerate(scope.children):
|
|
1456
|
+
if isinstance(child, GenExprScope) and child.inlined:
|
|
1457
|
+
scope.children[i : i + 1] = child.children
|
|
1458
|
+
|
|
1459
|
+
new_free |= all_free
|
|
1460
|
+
|
|
1461
|
+
if isinstance(scope, FUNCTION_LIKE_SCOPES):
|
|
1462
|
+
self.analyze_cells(scope, new_free, inlined_cells)
|
|
1463
|
+
elif isinstance(scope, ClassScope):
|
|
1464
|
+
# drop class free
|
|
1465
|
+
self.drop_class_free(scope, new_free)
|
|
1466
|
+
|
|
1467
|
+
scope.update_symbols(bound, new_free)
|
|
1468
|
+
free |= new_free
|
|
1469
|
+
|
|
1470
|
+
def drop_class_free(self, scope: ClassScope, new_free: set[str]) -> None:
|
|
1471
|
+
if "__class__" in new_free:
|
|
1472
|
+
new_free.remove("__class__")
|
|
1473
|
+
scope.needs_class_closure = True
|
|
1474
|
+
if "__classdict__" in new_free:
|
|
1475
|
+
new_free.remove("__classdict__")
|
|
1476
|
+
scope.needs_classdict = True
|
|
1477
|
+
|
|
1478
|
+
def analyze_child_block(
|
|
1479
|
+
self,
|
|
1480
|
+
scope: Scope,
|
|
1481
|
+
bound: set[str],
|
|
1482
|
+
free: set[str],
|
|
1483
|
+
global_vars: set[str],
|
|
1484
|
+
child_free: set[str],
|
|
1485
|
+
class_entry: Scope | None = None,
|
|
1486
|
+
) -> None:
|
|
1487
|
+
temp_bound = set(bound)
|
|
1488
|
+
temp_free = set(free)
|
|
1489
|
+
temp_global = set(free)
|
|
1490
|
+
|
|
1491
|
+
self.analyze_block(scope, temp_free, temp_global, temp_bound, class_entry)
|
|
1492
|
+
child_free |= temp_free
|
|
1493
|
+
|
|
1494
|
+
def analyze_cells(
|
|
1495
|
+
self, scope: Scope, free: set[str], inlined_cells: set[str]
|
|
1496
|
+
) -> None:
|
|
1497
|
+
for name in scope.defs:
|
|
1498
|
+
if (
|
|
1499
|
+
name in free or name in inlined_cells
|
|
1500
|
+
) and name not in scope.explicit_globals:
|
|
1501
|
+
scope.cells[name] = 1
|
|
1502
|
+
if name in free:
|
|
1503
|
+
free.remove(name)
|
|
1504
|
+
|
|
1505
|
+
def is_free_in_any_child(self, comp: GenExprScope, name: str) -> bool:
|
|
1506
|
+
for child in comp.children:
|
|
1507
|
+
if name in child.frees:
|
|
1508
|
+
return True
|
|
1509
|
+
return False
|
|
1510
|
+
|
|
1511
|
+
def inline_comprehension(
|
|
1512
|
+
self,
|
|
1513
|
+
scope: Scope,
|
|
1514
|
+
comp: GenExprScope,
|
|
1515
|
+
comp_free: set[str],
|
|
1516
|
+
inlined_cells: set[str],
|
|
1517
|
+
) -> None:
|
|
1518
|
+
# merge defs from comprehension scope into current scope
|
|
1519
|
+
comp.inlined = True
|
|
1520
|
+
for v in comp.defs:
|
|
1521
|
+
if v != ".0":
|
|
1522
|
+
scope.add_def(v)
|
|
1523
|
+
|
|
1524
|
+
# for names that are free in comprehension
|
|
1525
|
+
# and not present in defs of current scope -
|
|
1526
|
+
# add them as free in current scope
|
|
1527
|
+
for d in comp.uses:
|
|
1528
|
+
if comp.check_name(d) == SC_FREE and d not in scope.defs:
|
|
1529
|
+
sc = scope.check_name(d)
|
|
1530
|
+
if sc == SC_UNKNOWN:
|
|
1531
|
+
# name is missing in current scope - add it
|
|
1532
|
+
scope.frees[d] = 1
|
|
1533
|
+
elif comp.check_name(d) == SC_GLOBAL_IMPLICIT:
|
|
1534
|
+
scope.globals[d] = 1
|
|
1535
|
+
|
|
1536
|
+
remove_dunder_class = False
|
|
1537
|
+
# go through free names in comprehension
|
|
1538
|
+
# and check if current scope has corresponding def
|
|
1539
|
+
# if yes - name is no longer free after inlining
|
|
1540
|
+
for f in list(comp.frees.keys()):
|
|
1541
|
+
if f in scope.defs:
|
|
1542
|
+
# free vars in comprehension that are locals in outer scope can
|
|
1543
|
+
# now simply be locals, unless they are free in comp children,
|
|
1544
|
+
# or if the outer scope is a class block
|
|
1545
|
+
if not self.is_free_in_any_child(comp, f) and not isinstance(
|
|
1546
|
+
scope, ClassScope
|
|
1547
|
+
):
|
|
1548
|
+
comp_free.remove(f)
|
|
1549
|
+
elif isinstance(scope, ClassScope) and f == "__class__":
|
|
1550
|
+
scope.globals[f] = 1
|
|
1551
|
+
remove_dunder_class = True
|
|
1552
|
+
comp_free.remove(f)
|
|
1553
|
+
|
|
1554
|
+
# move names uses in comprehension to current scope
|
|
1555
|
+
for u in comp.uses.keys():
|
|
1556
|
+
if u != ".0" or u == "__classdict__":
|
|
1557
|
+
scope.add_use(u)
|
|
1558
|
+
|
|
1559
|
+
# cell vars in comprehension become cells in current scope
|
|
1560
|
+
for c in comp.cells.keys():
|
|
1561
|
+
if c != ".0":
|
|
1562
|
+
inlined_cells.add(c)
|
|
1563
|
+
|
|
1564
|
+
if remove_dunder_class:
|
|
1565
|
+
del comp.frees["__class__"]
|
|
1566
|
+
|
|
1567
|
+
|
|
1568
|
+
class SymbolVisitor314(SymbolVisitor312):
|
|
1569
|
+
def enter_block(self, scope: Scope) -> None:
|
|
1570
|
+
if isinstance(scope, (AnnotationScope, TypeAliasScope, TypeVarBoundScope)):
|
|
1571
|
+
scope.add_param(".format")
|
|
1572
|
+
scope.add_use(".format")
|
|
1573
|
+
|
|
1574
|
+
def enter_type_params(
|
|
1575
|
+
self,
|
|
1576
|
+
node: ast.ClassDef | ast.FunctionDef | ast.TypeAlias | ast.AsyncFunctionDef,
|
|
1577
|
+
parent: Scope,
|
|
1578
|
+
) -> TypeParamScope:
|
|
1579
|
+
res = super().enter_type_params(node, parent)
|
|
1580
|
+
self.enter_block(res)
|
|
1581
|
+
return res
|
|
1582
|
+
|
|
1583
|
+
def visitName(self, node: ast.Name, scope: Scope) -> None:
|
|
1584
|
+
if not scope.in_unevaluated_annotation:
|
|
1585
|
+
super().visitName(node, scope)
|
|
1586
|
+
|
|
1587
|
+
def visit_argannotations(self, args: list[ast.arg], scope: AnnotationScope) -> None:
|
|
1588
|
+
for arg in args:
|
|
1589
|
+
if arg.annotation is not None:
|
|
1590
|
+
self.visit(arg.annotation, scope)
|
|
1591
|
+
scope.annotations_used = True
|
|
1592
|
+
|
|
1593
|
+
def _do_args(self, scope: Scope, args: ast.arguments) -> None:
|
|
1594
|
+
for n in args.defaults:
|
|
1595
|
+
self.visit(n, scope.parent)
|
|
1596
|
+
for n in args.kw_defaults:
|
|
1597
|
+
if n:
|
|
1598
|
+
self.visit(n, scope.parent)
|
|
1599
|
+
|
|
1600
|
+
for arg in args.posonlyargs:
|
|
1601
|
+
name = arg.arg
|
|
1602
|
+
scope.add_param(name)
|
|
1603
|
+
for arg in args.args:
|
|
1604
|
+
name = arg.arg
|
|
1605
|
+
scope.add_param(name)
|
|
1606
|
+
for arg in args.kwonlyargs:
|
|
1607
|
+
name = arg.arg
|
|
1608
|
+
scope.add_param(name)
|
|
1609
|
+
if vararg := args.vararg:
|
|
1610
|
+
scope.add_param(vararg.arg)
|
|
1611
|
+
if kwarg := args.kwarg:
|
|
1612
|
+
scope.add_param(kwarg.arg)
|
|
1613
|
+
|
|
1614
|
+
def drop_class_free(self, scope: ClassScope, new_free: set[str]) -> None:
|
|
1615
|
+
super().drop_class_free(scope, new_free)
|
|
1616
|
+
if "__conditional_annotations__" in new_free:
|
|
1617
|
+
new_free.remove("__conditional_annotations__")
|
|
1618
|
+
scope.has_conditional_annotations = True
|
|
1619
|
+
|
|
1620
|
+
def visit_annotation(self, annotation: ast.expr, scope: Scope) -> None:
|
|
1621
|
+
# Annotations in local scopes are not executed and should not affect the symtable
|
|
1622
|
+
is_unevaluated = isinstance(scope, FunctionScope)
|
|
1623
|
+
|
|
1624
|
+
# Module-level annotations are always considered conditional because the module
|
|
1625
|
+
# may be partially executed.
|
|
1626
|
+
if (
|
|
1627
|
+
(isinstance(scope, ClassScope) and self.in_conditional_block)
|
|
1628
|
+
or isinstance(scope, ModuleScope)
|
|
1629
|
+
) and not scope.has_conditional_annotations:
|
|
1630
|
+
scope.has_conditional_annotations = True
|
|
1631
|
+
scope.add_use("__conditional_annotations__")
|
|
1632
|
+
|
|
1633
|
+
if (annotations := scope.annotations) is None:
|
|
1634
|
+
annotations = scope.annotations = AnnotationScope(
|
|
1635
|
+
"__annotate__", scope.module
|
|
1636
|
+
)
|
|
1637
|
+
if scope.parent is not None and (
|
|
1638
|
+
scope.parent.nested or isinstance(scope.parent, FUNCTION_LIKE_SCOPES)
|
|
1639
|
+
):
|
|
1640
|
+
annotations.nested = True
|
|
1641
|
+
annotations.parent = scope
|
|
1642
|
+
if not self.future_annotations:
|
|
1643
|
+
scope.children.append(annotations)
|
|
1644
|
+
self.scopes[Annotations(scope)] = annotations
|
|
1645
|
+
if isinstance(scope, ClassScope) and not self.future_annotations:
|
|
1646
|
+
annotations.can_see_class_scope = True
|
|
1647
|
+
annotations.add_use("__classdict__")
|
|
1648
|
+
|
|
1649
|
+
if is_unevaluated:
|
|
1650
|
+
annotations.in_unevaluated_annotation = True
|
|
1651
|
+
|
|
1652
|
+
self.visit(annotation, annotations)
|
|
1653
|
+
|
|
1654
|
+
if is_unevaluated:
|
|
1655
|
+
annotations.in_unevaluated_annotation = False
|
|
1656
|
+
|
|
1657
|
+
def visit_annotations(
|
|
1658
|
+
self,
|
|
1659
|
+
args: ast.arguments,
|
|
1660
|
+
returns: ast.expr | None,
|
|
1661
|
+
parent: Scope,
|
|
1662
|
+
) -> AnnotationScope:
|
|
1663
|
+
scope = AnnotationScope("__annotate__", self.module, self.klass)
|
|
1664
|
+
self.scopes[args] = scope
|
|
1665
|
+
|
|
1666
|
+
if not self.future_annotations:
|
|
1667
|
+
# If "from __future__ import annotations" is active,
|
|
1668
|
+
# annotation blocks shouldn't have any affect on the symbol table since in
|
|
1669
|
+
# the compilation stage, they will all be transformed to strings.
|
|
1670
|
+
parent.add_child(scope)
|
|
1671
|
+
if parent.nested or isinstance(parent, FUNCTION_LIKE_SCOPES):
|
|
1672
|
+
scope.nested = True
|
|
1673
|
+
|
|
1674
|
+
scope.parent = parent
|
|
1675
|
+
self.enter_block(scope)
|
|
1676
|
+
is_in_class = parent.can_see_class_scope
|
|
1677
|
+
if is_in_class or isinstance(parent, ClassScope):
|
|
1678
|
+
scope.can_see_class_scope = True
|
|
1679
|
+
scope.add_use("__classdict__")
|
|
1680
|
+
|
|
1681
|
+
if args.posonlyargs:
|
|
1682
|
+
self.visit_argannotations(args.posonlyargs, scope)
|
|
1683
|
+
if args.args:
|
|
1684
|
+
self.visit_argannotations(args.args, scope)
|
|
1685
|
+
if args.vararg and args.vararg.annotation:
|
|
1686
|
+
scope.annotations_used = True
|
|
1687
|
+
self.visit(args.vararg, scope)
|
|
1688
|
+
if args.kwarg and args.kwarg.annotation:
|
|
1689
|
+
scope.annotations_used = True
|
|
1690
|
+
self.visit(args.kwarg, scope)
|
|
1691
|
+
if args.kwonlyargs:
|
|
1692
|
+
self.visit_argannotations(args.kwonlyargs, scope)
|
|
1693
|
+
if returns:
|
|
1694
|
+
scope.annotations_used = True
|
|
1695
|
+
self.visit(returns, scope)
|
|
1696
|
+
return scope
|
|
1697
|
+
|
|
1698
|
+
def visitLambda(self, node: ast.Lambda, parent: Scope) -> None:
|
|
1699
|
+
scope = self._LambdaScope(self.module, self.klass, lineno=node.lineno)
|
|
1700
|
+
scope.parent = parent
|
|
1701
|
+
|
|
1702
|
+
# bpo-37757: For now, disallow *all* assignment expressions in the
|
|
1703
|
+
# outermost iterator expression of a comprehension, even those inside
|
|
1704
|
+
# a nested comprehension or a lambda expression.
|
|
1705
|
+
scope.comp_iter_expr = parent.comp_iter_expr
|
|
1706
|
+
if parent.nested or isinstance(parent, FunctionScope):
|
|
1707
|
+
scope.nested = True
|
|
1708
|
+
self.scopes[node] = scope
|
|
1709
|
+
self._do_args(scope, node.args)
|
|
1710
|
+
self.visit(node.body, scope)
|
|
1711
|
+
if isinstance(parent, ClassScope):
|
|
1712
|
+
scope.is_method = True
|
|
1713
|
+
|
|
1714
|
+
parent.add_child(scope)
|
|
1715
|
+
|
|
1716
|
+
def _visit_func_impl(
|
|
1717
|
+
self, node: ast.FunctionDef | ast.AsyncFunctionDef, parent: Scope
|
|
1718
|
+
) -> None:
|
|
1719
|
+
if node.decorator_list:
|
|
1720
|
+
self.visit_list(node.decorator_list, parent)
|
|
1721
|
+
parent.add_def(node.name)
|
|
1722
|
+
|
|
1723
|
+
type_params = getattr(node, "type_params", ())
|
|
1724
|
+
if type_params:
|
|
1725
|
+
parent = self.enter_type_params(node, parent)
|
|
1726
|
+
for param in type_params:
|
|
1727
|
+
self.visit(param, parent)
|
|
1728
|
+
|
|
1729
|
+
scope = self._FunctionScope(
|
|
1730
|
+
node.name, self.module, self.klass, lineno=node.lineno
|
|
1731
|
+
)
|
|
1732
|
+
|
|
1733
|
+
doc = ast.get_docstring(node)
|
|
1734
|
+
if doc is not None:
|
|
1735
|
+
scope.has_docstring = True
|
|
1736
|
+
|
|
1737
|
+
if isinstance(parent, ClassScope):
|
|
1738
|
+
scope.is_method = True
|
|
1739
|
+
|
|
1740
|
+
scope.annotations = self.visit_annotations(node.args, node.returns, parent)
|
|
1741
|
+
|
|
1742
|
+
scope.coroutine = isinstance(node, ast.AsyncFunctionDef)
|
|
1743
|
+
scope.parent = parent
|
|
1744
|
+
if parent.nested or isinstance(parent, FUNCTION_LIKE_SCOPES):
|
|
1745
|
+
scope.nested = True
|
|
1746
|
+
self.scopes[node] = scope
|
|
1747
|
+
self._do_args(scope, node.args)
|
|
1748
|
+
self.visit_list(node.body, scope)
|
|
1749
|
+
|
|
1750
|
+
parent.add_child(scope)
|
|
1751
|
+
|
|
1752
|
+
|
|
1753
|
+
class SymbolVisitor315(SymbolVisitor314):
|
|
1754
|
+
pass
|