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,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))
|