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.
Files changed (68) hide show
  1. __static__/__init__.py +641 -0
  2. __static__/compiler_flags.py +8 -0
  3. __static__/enum.py +160 -0
  4. __static__/native_utils.py +77 -0
  5. __static__/type_code.py +48 -0
  6. __strict__/__init__.py +39 -0
  7. _cinderx.so +0 -0
  8. cinderx/__init__.py +577 -0
  9. cinderx/__pycache__/__init__.cpython-314.pyc +0 -0
  10. cinderx/_asyncio.py +156 -0
  11. cinderx/compileall.py +710 -0
  12. cinderx/compiler/__init__.py +40 -0
  13. cinderx/compiler/__main__.py +137 -0
  14. cinderx/compiler/config.py +7 -0
  15. cinderx/compiler/consts.py +72 -0
  16. cinderx/compiler/debug.py +70 -0
  17. cinderx/compiler/dis_stable.py +283 -0
  18. cinderx/compiler/errors.py +151 -0
  19. cinderx/compiler/flow_graph_optimizer.py +1287 -0
  20. cinderx/compiler/future.py +91 -0
  21. cinderx/compiler/misc.py +32 -0
  22. cinderx/compiler/opcode_cinder.py +18 -0
  23. cinderx/compiler/opcode_static.py +100 -0
  24. cinderx/compiler/opcodebase.py +158 -0
  25. cinderx/compiler/opcodes.py +991 -0
  26. cinderx/compiler/optimizer.py +547 -0
  27. cinderx/compiler/pyassem.py +3711 -0
  28. cinderx/compiler/pycodegen.py +7660 -0
  29. cinderx/compiler/pysourceloader.py +62 -0
  30. cinderx/compiler/static/__init__.py +1404 -0
  31. cinderx/compiler/static/compiler.py +629 -0
  32. cinderx/compiler/static/declaration_visitor.py +335 -0
  33. cinderx/compiler/static/definite_assignment_checker.py +280 -0
  34. cinderx/compiler/static/effects.py +160 -0
  35. cinderx/compiler/static/module_table.py +666 -0
  36. cinderx/compiler/static/type_binder.py +2176 -0
  37. cinderx/compiler/static/types.py +10580 -0
  38. cinderx/compiler/static/util.py +81 -0
  39. cinderx/compiler/static/visitor.py +91 -0
  40. cinderx/compiler/strict/__init__.py +69 -0
  41. cinderx/compiler/strict/class_conflict_checker.py +249 -0
  42. cinderx/compiler/strict/code_gen_base.py +409 -0
  43. cinderx/compiler/strict/common.py +507 -0
  44. cinderx/compiler/strict/compiler.py +352 -0
  45. cinderx/compiler/strict/feature_extractor.py +130 -0
  46. cinderx/compiler/strict/flag_extractor.py +97 -0
  47. cinderx/compiler/strict/loader.py +827 -0
  48. cinderx/compiler/strict/preprocessor.py +11 -0
  49. cinderx/compiler/strict/rewriter/__init__.py +5 -0
  50. cinderx/compiler/strict/rewriter/remove_annotations.py +84 -0
  51. cinderx/compiler/strict/rewriter/rewriter.py +975 -0
  52. cinderx/compiler/strict/runtime.py +77 -0
  53. cinderx/compiler/symbols.py +1754 -0
  54. cinderx/compiler/unparse.py +414 -0
  55. cinderx/compiler/visitor.py +194 -0
  56. cinderx/jit.py +230 -0
  57. cinderx/opcode.py +202 -0
  58. cinderx/static.py +113 -0
  59. cinderx/strictmodule.py +6 -0
  60. cinderx/test_support.py +341 -0
  61. cinderx-2026.1.16.2.dist-info/METADATA +15 -0
  62. cinderx-2026.1.16.2.dist-info/RECORD +68 -0
  63. cinderx-2026.1.16.2.dist-info/WHEEL +6 -0
  64. cinderx-2026.1.16.2.dist-info/licenses/LICENSE +21 -0
  65. cinderx-2026.1.16.2.dist-info/top_level.txt +5 -0
  66. opcodes/__init__.py +0 -0
  67. opcodes/assign_opcode_numbers.py +272 -0
  68. 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