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,414 @@
1
+ # Portions copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # pyre-strict
3
+
4
+ import ast
5
+ import sys
6
+ import warnings
7
+ from collections.abc import Callable
8
+ from typing import Optional
9
+
10
+
11
+ PR_TUPLE = 0
12
+ PR_TEST = 1 # 'if'-'else', 'lambda'
13
+ PR_OR = 2 # 'or'
14
+ PR_AND = 3 # 'and'
15
+ PR_NOT = 4 # 'not'
16
+ PR_CMP = 5 # '<', '>', '==', '>=', '<=', '!=' 'in', 'not in', 'is', 'is not'
17
+ PR_EXPR = 6
18
+ PR_BOR: int = PR_EXPR # '|'
19
+ PR_BXOR = 7 # '^'
20
+ PR_BAND = 8 # '&'
21
+ PR_SHIFT = 9 # '<<', '>>'
22
+ PR_ARITH = 10 # '+', '-'
23
+ PR_TERM = 11 # '*', '@', '/', '%', '//'
24
+ PR_FACTOR = 12 # unary '+', '-', '~'
25
+ PR_POWER = 13 # '**'
26
+ PR_AWAIT = 14 # 'await'
27
+ PR_ATOM = 15
28
+
29
+
30
+ def get_op(node: ast.cmpop) -> str:
31
+ if isinstance(node, ast.Is):
32
+ return " is "
33
+ elif isinstance(node, ast.IsNot):
34
+ return " is not "
35
+ elif isinstance(node, ast.In):
36
+ return " in "
37
+ elif isinstance(node, ast.NotIn):
38
+ return " not in "
39
+ elif isinstance(node, ast.Lt):
40
+ return " < "
41
+ elif isinstance(node, ast.Gt):
42
+ return " > "
43
+ elif isinstance(node, ast.LtE):
44
+ return " <= "
45
+ elif isinstance(node, ast.GtE):
46
+ return " >= "
47
+ elif isinstance(node, ast.Eq):
48
+ return " == "
49
+ elif isinstance(node, ast.NotEq):
50
+ return " != "
51
+ else:
52
+ return "unknown op: " + type(node).__name__
53
+
54
+
55
+ def get_unop(node: ast.unaryop) -> str:
56
+ if isinstance(node, ast.UAdd):
57
+ return "+"
58
+ elif isinstance(node, ast.USub):
59
+ return "-"
60
+ elif isinstance(node, ast.Not):
61
+ return "not "
62
+ elif isinstance(node, ast.Invert):
63
+ return "~"
64
+ return "<unknown unary op>"
65
+
66
+
67
+ def _format_name(node: ast.Name, level: int) -> str:
68
+ return node.id
69
+
70
+
71
+ def _format_compare(node: ast.Compare, level: int) -> str:
72
+ return parens(
73
+ level,
74
+ PR_CMP,
75
+ to_expr(node.left, PR_CMP + 1)
76
+ + "".join(
77
+ (
78
+ get_op(op) + to_expr(comp, PR_CMP + 1)
79
+ for comp, op in zip(node.comparators, node.ops)
80
+ )
81
+ ),
82
+ )
83
+
84
+
85
+ def _format_attribute(node: ast.Attribute, level: int) -> str:
86
+ value = to_expr(node.value, PR_ATOM)
87
+ const = node.value
88
+ if isinstance(const, ast.Constant) and isinstance(const.value, int):
89
+ value += " ."
90
+ else:
91
+ value += "."
92
+ return value + node.attr
93
+
94
+
95
+ def _format_tuple(node: ast.Tuple, level: int) -> str:
96
+ if not node.elts:
97
+ return "()"
98
+ elif len(node.elts) == 1:
99
+ return parens(level, PR_TUPLE, to_expr(node.elts[0]) + ",")
100
+
101
+ return parens(level, PR_TUPLE, ", ".join(to_expr(elm) for elm in node.elts))
102
+
103
+
104
+ def _format_list(node: ast.List, level: int) -> str:
105
+ return "[" + ", ".join(to_expr(elm) for elm in node.elts) + "]"
106
+
107
+
108
+ def _format_kw(node: ast.keyword) -> str:
109
+ if node.arg:
110
+ return f"{node.arg}={to_expr(node.value)}"
111
+ return f"**{to_expr(node.value)}"
112
+
113
+
114
+ def _format_call(node: ast.Call, level: int) -> str:
115
+ args = [to_expr(arg) for arg in node.args] + [
116
+ _format_kw(arg) for arg in node.keywords
117
+ ]
118
+ return to_expr(node.func, PR_TEST) + "(" + ", ".join(args) + ")"
119
+
120
+
121
+ def _format_unaryop(node: ast.UnaryOp, level: int) -> str:
122
+ tgt_level = PR_FACTOR
123
+ if isinstance(node.op, ast.Not):
124
+ tgt_level = PR_NOT
125
+ return parens(
126
+ level, tgt_level, get_unop(node.op) + to_expr(node.operand, tgt_level)
127
+ )
128
+
129
+
130
+ BIN_OPS: dict[type[ast.AST], tuple[str, int]] = {
131
+ ast.Add: (" + ", PR_ARITH),
132
+ ast.Sub: (" - ", PR_ARITH),
133
+ ast.Mult: (" * ", PR_TERM),
134
+ ast.MatMult: (" @ ", PR_TERM),
135
+ ast.Div: (" / ", PR_TERM),
136
+ ast.Mod: (" % ", PR_TERM),
137
+ ast.LShift: (" << ", PR_SHIFT),
138
+ ast.RShift: (" >> ", PR_SHIFT),
139
+ ast.BitOr: (" | ", PR_BOR),
140
+ ast.BitXor: (" ^ ", PR_BXOR),
141
+ ast.BitAnd: (" & ", PR_BAND),
142
+ ast.FloorDiv: (" // ", PR_TERM),
143
+ ast.Pow: (" ** ", PR_POWER),
144
+ }
145
+
146
+
147
+ def _format_binaryop(node: ast.BinOp, level: int) -> str:
148
+ tgt_level = PR_FACTOR
149
+
150
+ # pyre-fixme[6]: For 1st argument expected `Type[Union[Add, BitAnd, BitOr,
151
+ # BitXor, Div, FloorDiv, LShift, MatMult, Mod, Mult, Pow, RShift, Sub]]` but got
152
+ # `Type[operator]`.
153
+ op, tgt_level = BIN_OPS[type(node.op)]
154
+ rassoc = 0
155
+ if isinstance(node.op, ast.Pow):
156
+ rassoc = 1
157
+ return parens(
158
+ level,
159
+ tgt_level,
160
+ to_expr(node.left, tgt_level + rassoc)
161
+ + op
162
+ + to_expr(node.right, tgt_level + (1 - rassoc)),
163
+ )
164
+
165
+
166
+ def _format_subscript(node: ast.Subscript, level: int) -> str:
167
+ return f"{to_expr(node.value, PR_ATOM)}[{to_expr(node.slice, PR_TUPLE)}]"
168
+
169
+
170
+ def _format_yield(node: ast.Yield, level: int) -> str:
171
+ raise SyntaxError("'yield expression' can not be used within an annotation")
172
+
173
+
174
+ def _format_yield_from(node: ast.YieldFrom, level: int) -> str:
175
+ raise SyntaxError("'yield expression' can not be used within an annotation")
176
+
177
+
178
+ def _format_dict(node: ast.Dict, level: int) -> str:
179
+ return (
180
+ "{"
181
+ + ", ".join(
182
+ to_expr(k) + ": " + to_expr(v) for k, v in zip(node.keys, node.values)
183
+ )
184
+ + "}"
185
+ )
186
+
187
+
188
+ def _format_comprehension(node: ast.comprehension) -> str:
189
+ header = " for "
190
+ if node.is_async:
191
+ header = " async for "
192
+
193
+ res = (
194
+ header
195
+ + to_expr(node.target, PR_TUPLE)
196
+ + " in "
197
+ + to_expr(node.iter, PR_TEST + 1)
198
+ )
199
+ for if_ in node.ifs:
200
+ res += " if " + to_expr(if_, PR_TEST + 1)
201
+ return res
202
+
203
+
204
+ def parens(level: int, target_lvl: int, value: str) -> str:
205
+ if level > target_lvl:
206
+ return f"({value})"
207
+ return value
208
+
209
+
210
+ def _format_await(node: ast.Await, level: int) -> str:
211
+ return parens(level, PR_AWAIT, "await " + to_expr(node.value, PR_ATOM))
212
+
213
+
214
+ def _format_starred(node: ast.Starred, level: int) -> str:
215
+ return "*" + to_expr(node.value, PR_EXPR)
216
+
217
+
218
+ def _format_boolop(node: ast.BoolOp, level: int) -> str:
219
+ if isinstance(node.op, ast.And):
220
+ name = " and "
221
+ tgt_level = PR_AND
222
+ else:
223
+ name = " or "
224
+ tgt_level = PR_OR
225
+
226
+ return parens(
227
+ level, tgt_level, name.join(to_expr(n, tgt_level + 1) for n in node.values)
228
+ )
229
+
230
+
231
+ def _format_arguments(node: ast.arguments) -> str:
232
+ res = []
233
+ for i, arg in enumerate(node.args):
234
+ if i:
235
+ res.append(", ")
236
+ res.append(arg.arg)
237
+ if i < len(node.defaults):
238
+ res.append("=")
239
+ res.append(to_expr(node.defaults[i]))
240
+
241
+ if node.vararg or node.kwonlyargs:
242
+ if node.args:
243
+ res.append(", ")
244
+ res.append("*")
245
+ vararg = node.vararg
246
+ if vararg:
247
+ res.append(vararg.arg)
248
+
249
+ for i, arg in enumerate(node.kwonlyargs):
250
+ if res:
251
+ res.append(", ")
252
+ res.append(arg.arg)
253
+ if i < len(node.kw_defaults) and node.kw_defaults[i]:
254
+ res.append("=")
255
+ res.append(to_expr(node.kw_defaults[i]))
256
+
257
+ return "".join(res)
258
+
259
+
260
+ def _format_lambda(node: ast.Lambda, level: int) -> str:
261
+ value = "lambda "
262
+ if not node.args.args:
263
+ value = "lambda"
264
+ value += _format_arguments(node.args)
265
+ value += ": " + to_expr(node.body, PR_TEST)
266
+
267
+ return parens(level, PR_TEST, value)
268
+
269
+
270
+ def _format_if_exp(node: ast.IfExp, level: int) -> str:
271
+ body = to_expr(node.body, PR_TEST + 1)
272
+ orelse = to_expr(node.orelse, PR_TEST)
273
+ test = to_expr(node.test, PR_TEST + 1)
274
+ return parens(level, PR_TEST, f"{body} if {test} else {orelse}")
275
+
276
+
277
+ def _format_set(node: ast.Set, level: int) -> str:
278
+ return "{" + ", ".join(to_expr(elt, PR_TEST) for elt in node.elts) + "}"
279
+
280
+
281
+ def _format_comprehensions(nodes: list[ast.comprehension]) -> str:
282
+ return "".join(_format_comprehension(n) for n in nodes)
283
+
284
+
285
+ def _format_set_comp(node: ast.SetComp, level: int) -> str:
286
+ return "{" + to_expr(node.elt) + _format_comprehensions(node.generators) + "}"
287
+
288
+
289
+ def _format_list_comp(node: ast.ListComp, level: int) -> str:
290
+ return "[" + to_expr(node.elt) + _format_comprehensions(node.generators) + "]"
291
+
292
+
293
+ def _format_dict_comp(node: ast.DictComp, level: int) -> str:
294
+ return (
295
+ "{"
296
+ + to_expr(node.key)
297
+ + ": "
298
+ + to_expr(node.value)
299
+ + _format_comprehensions(node.generators)
300
+ + "}"
301
+ )
302
+
303
+
304
+ def _format_gen_exp(node: ast.GeneratorExp, level: int) -> str:
305
+ return "(" + to_expr(node.elt) + _format_comprehensions(node.generators) + ")"
306
+
307
+
308
+ def format_fstring_elt(res: list[str], elt: ast.expr, is_format_spec: bool) -> None:
309
+ if isinstance(elt, ast.Constant):
310
+ res.append(elt.value)
311
+ elif isinstance(elt, ast.JoinedStr):
312
+ res.append(format_joinedstr(elt, PR_TEST, is_format_spec))
313
+ elif isinstance(elt, ast.FormattedValue):
314
+ expr = to_expr(elt.value, PR_TEST + 1)
315
+ if expr.startswith("{"):
316
+ # Expression starts with a brace, we need an extra space
317
+ res.append("{ ")
318
+ else:
319
+ res.append("{")
320
+ res.append(expr)
321
+ conversion = elt.conversion
322
+ if conversion is not None and conversion != -1:
323
+ res.append("!")
324
+ res.append(chr(conversion))
325
+ format_spec = elt.format_spec
326
+ if format_spec is not None:
327
+ res.append(":")
328
+ format_fstring_elt(res, format_spec, True)
329
+ res.append("}")
330
+
331
+
332
+ def format_joinedstr(
333
+ node: ast.JoinedStr, level: int, is_format_spec: bool = False
334
+ ) -> str:
335
+ res = []
336
+ for elt in node.values:
337
+ format_fstring_elt(res, elt, is_format_spec)
338
+
339
+ joined = "".join(res)
340
+ if is_format_spec:
341
+ return joined
342
+ return f"f{repr(joined)}"
343
+
344
+
345
+ def _format_slice(node: ast.Slice, level: int) -> str:
346
+ res = ""
347
+ if node.lower is not None:
348
+ res += to_expr(node.lower)
349
+ res += ":"
350
+ if node.upper is not None:
351
+ res += to_expr(node.upper)
352
+ if node.step:
353
+ res += ":"
354
+ res += to_expr(node.step)
355
+ return res
356
+
357
+
358
+ def _format_constant(node: ast.Constant, level: int) -> str:
359
+ if node.value is Ellipsis:
360
+ return "..."
361
+
362
+ return repr(node.value)
363
+
364
+
365
+ with warnings.catch_warnings():
366
+ warnings.simplefilter("ignore", category=DeprecationWarning)
367
+
368
+ # pyre-ignore[9]: Pyre tries to union all the keys and values into concrete types.
369
+ _FORMATTERS: dict[type[ast.AST], Callable[[ast.AST, int], str]] = {
370
+ ast.Attribute: _format_attribute,
371
+ ast.Await: _format_await,
372
+ ast.BinOp: _format_binaryop,
373
+ ast.BoolOp: _format_boolop,
374
+ ast.Call: _format_call,
375
+ ast.Compare: _format_compare,
376
+ ast.Constant: _format_constant,
377
+ ast.Dict: _format_dict,
378
+ ast.DictComp: _format_dict_comp,
379
+ ast.FormattedValue: None,
380
+ ast.GeneratorExp: _format_gen_exp,
381
+ ast.IfExp: _format_if_exp,
382
+ ast.JoinedStr: format_joinedstr,
383
+ ast.Lambda: _format_lambda,
384
+ ast.List: _format_list,
385
+ ast.ListComp: _format_list_comp,
386
+ ast.Name: _format_name,
387
+ ast.Set: _format_set,
388
+ ast.SetComp: _format_set_comp,
389
+ ast.Slice: _format_slice,
390
+ ast.Starred: _format_starred,
391
+ ast.Subscript: _format_subscript,
392
+ ast.Tuple: _format_tuple,
393
+ ast.UnaryOp: _format_unaryop,
394
+ ast.Yield: _format_yield,
395
+ ast.YieldFrom: _format_yield_from,
396
+ **(
397
+ {
398
+ ast.Bytes: lambda node, level: repr(node.s),
399
+ ast.Ellipsis: lambda node, level: "...",
400
+ }
401
+ if sys.version_info < (3, 14)
402
+ else {}
403
+ ),
404
+ }
405
+
406
+
407
+ def to_expr(node: Optional[ast.AST], level: int = PR_TEST) -> str:
408
+ if node is None:
409
+ return ""
410
+ formatter = _FORMATTERS.get(type(node))
411
+ if formatter is not None:
412
+ return formatter(node, level)
413
+
414
+ return "<unsupported node: " + type(node).__name__ + ">"
@@ -0,0 +1,194 @@
1
+ # Portions copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # pyre-strict
3
+
4
+ from ast import AST, copy_location, iter_fields
5
+ from typing import Sequence, TypeVar
6
+
7
+ # XXX should probably rename ASTVisitor to ASTWalker
8
+ # XXX can it be made even more generic?
9
+
10
+
11
+ TAst = TypeVar("TAst", bound=AST)
12
+
13
+
14
+ class ASTVisitor:
15
+ """Performs a depth-first walk of the AST
16
+
17
+ The ASTVisitor is responsible for walking over the tree in the
18
+ correct order. For each node, it checks the visitor argument for
19
+ a method named 'visitNodeType' where NodeType is the name of the
20
+ node's class, e.g. Class. If the method exists, it is called
21
+ with the node as its sole argument.
22
+
23
+ This is basically the same as the built-in ast.NodeVisitor except
24
+ for the following differences:
25
+ It accepts extra parameters through the visit methods for flowing state
26
+ It uses "visitNodeName" instead of "visit_NodeName"
27
+ It accepts a list to the generic_visit function rather than just nodes
28
+ """
29
+
30
+ def __init__(self) -> None:
31
+ self.node: AST | None = None
32
+ self._cache: dict[object, object] = {}
33
+
34
+ def generic_visit(self, node: TAst, *args: object) -> object:
35
+ """Called if no explicit visitor function exists for a node."""
36
+ for _field, value in iter_fields(node):
37
+ if isinstance(value, list):
38
+ for item in value:
39
+ if isinstance(item, AST):
40
+ self.visit(item, *args)
41
+ elif isinstance(value, AST):
42
+ self.visit(value, *args)
43
+
44
+ def visit(self, node: TAst, *args: object) -> object:
45
+ if not isinstance(node, AST):
46
+ raise TypeError(f"Expected AST node, got {node!r}")
47
+
48
+ self.node = node
49
+ klass = node.__class__
50
+ meth = self._cache.get(klass, None)
51
+ if meth is None:
52
+ className = klass.__name__
53
+ meth = getattr(type(self), "visit" + className, type(self).generic_visit)
54
+ self._cache[klass] = meth
55
+ if not args:
56
+ return meth(self, node)
57
+ return meth(self, node, *args)
58
+
59
+ def visit_list(self, nodes: Sequence[TAst], *args: object) -> None:
60
+ if not isinstance(nodes, list):
61
+ raise TypeError(f"Expected a list of AST nodes, got {nodes!r}")
62
+
63
+ for node in nodes:
64
+ self.visit(node, *args)
65
+
66
+
67
+ class ASTRewriter(ASTVisitor):
68
+ """performs rewrites on the AST, rewriting parent nodes when child nodes
69
+ are replaced."""
70
+
71
+ @staticmethod
72
+ def update_node(node: TAst, **replacement: object) -> TAst:
73
+ res = node
74
+ for name, val in replacement.items():
75
+ existing = getattr(res, name)
76
+ if existing is val:
77
+ continue
78
+
79
+ if node is res:
80
+ res = ASTRewriter.clone_node(node)
81
+
82
+ setattr(res, name, val)
83
+ return res
84
+
85
+ @staticmethod
86
+ def clone_node(node: TAst) -> TAst:
87
+ attrs = []
88
+ for name in node._fields:
89
+ attr = getattr(node, name, None)
90
+ if isinstance(attr, list):
91
+ attr = list(attr)
92
+ attrs.append(attr)
93
+
94
+ new = type(node)(*attrs)
95
+ return copy_location(new, node)
96
+
97
+ def skip_field(self, node: TAst, field: str) -> bool:
98
+ return False
99
+
100
+ def generic_visit(self, node: TAst, *args: object) -> TAst:
101
+ ret_node = node
102
+ for field, old_value in iter_fields(node):
103
+ if self.skip_field(node, field):
104
+ continue
105
+
106
+ if isinstance(old_value, AST):
107
+ new_value = self.visit(old_value, *args)
108
+ elif isinstance(old_value, list):
109
+ new_value = self.walk_list(old_value, *args)
110
+ else:
111
+ continue
112
+
113
+ assert new_value is not None, (
114
+ f"can't remove AST nodes that aren't part of a list {old_value!r}"
115
+ )
116
+ if new_value is not old_value:
117
+ if ret_node is node:
118
+ ret_node = self.clone_node(node)
119
+
120
+ setattr(ret_node, field, new_value)
121
+
122
+ return ret_node
123
+
124
+ def walk_list(self, values: Sequence[object], *args: object) -> Sequence[object]:
125
+ """
126
+ Like visit_list(), but it also walks values returned by ast.iter_fields()
127
+ so it has to handle non-AST node values.
128
+ """
129
+
130
+ new_values = []
131
+ changed = False
132
+
133
+ for value in values:
134
+ if not isinstance(value, AST):
135
+ new_values.append(value)
136
+ continue
137
+
138
+ new_value = self.visit(value, *args)
139
+ if new_value is not None:
140
+ new_values.append(new_value)
141
+ changed = True
142
+
143
+ # Reuse the existing list when possible.
144
+ return new_values if changed else values
145
+
146
+
147
+ class ExampleASTVisitor(ASTVisitor):
148
+ """Prints examples of the nodes that aren't visited
149
+
150
+ This visitor-driver is only useful for development, when it's
151
+ helpful to develop a visitor incrementally, and get feedback on what
152
+ you still have to do.
153
+ """
154
+
155
+ VERBOSE: int = 0
156
+
157
+ examples: dict[object, object] = {}
158
+
159
+ def visit(self, node: TAst, *args: object) -> object:
160
+ self.node = node
161
+ meth = self._cache.get(node.__class__, None)
162
+ className = node.__class__.__name__
163
+ if meth is None:
164
+ meth = getattr(self, "visit" + className, 0)
165
+ self._cache[node.__class__] = meth
166
+ if self.VERBOSE > 1:
167
+ print("visit", className, meth and meth.__name__ or "")
168
+ if meth:
169
+ meth(node, *args)
170
+ elif self.VERBOSE > 0:
171
+ klass = node.__class__
172
+ if klass not in self.examples:
173
+ self.examples[klass] = klass
174
+ print()
175
+ print(self)
176
+ print(klass)
177
+ for attr in dir(node):
178
+ if attr[0] != "_":
179
+ print("\t", "%-12.12s" % attr, getattr(node, attr))
180
+ print()
181
+ return self.default(node, *args)
182
+
183
+ def default(self, node: TAst, *args: object) -> TAst:
184
+ return node
185
+
186
+
187
+ # XXX this is an API change
188
+
189
+
190
+ def dumpNode(node: object) -> None:
191
+ print(node.__class__)
192
+ for attr in dir(node):
193
+ if attr[0] != "_":
194
+ print("\t", "%-10.10s" % attr, getattr(node, attr))