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,547 @@
|
|
|
1
|
+
# Portions copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
# pyre-strict
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
import operator
|
|
9
|
+
from ast import cmpop, Constant, copy_location
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Callable, Iterable, Mapping
|
|
12
|
+
|
|
13
|
+
from .visitor import ASTRewriter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PyLimits:
|
|
17
|
+
MAX_INT_SIZE = 128
|
|
18
|
+
MAX_COLLECTION_SIZE = 256
|
|
19
|
+
MAX_STR_SIZE = 4096
|
|
20
|
+
MAX_TOTAL_ITEMS = 1024
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# pyre-fixme[5]: Global annotation cannot contain `Any`.
|
|
24
|
+
UNARY_OPS: Mapping[type[ast.unaryop], Callable[[Any], object]] = {
|
|
25
|
+
ast.Invert: operator.invert,
|
|
26
|
+
ast.Not: operator.not_,
|
|
27
|
+
ast.UAdd: operator.pos,
|
|
28
|
+
ast.USub: operator.neg,
|
|
29
|
+
}
|
|
30
|
+
INVERSE_OPS: Mapping[type[cmpop], type[cmpop]] = {
|
|
31
|
+
ast.Is: ast.IsNot,
|
|
32
|
+
ast.IsNot: ast.Is,
|
|
33
|
+
ast.In: ast.NotIn,
|
|
34
|
+
ast.NotIn: ast.In,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
BIN_OPS: Mapping[type[ast.operator], Callable[[object, object], object]] = {
|
|
38
|
+
ast.Add: operator.add,
|
|
39
|
+
ast.Sub: operator.sub,
|
|
40
|
+
ast.Mult: lambda lhs, rhs: safe_multiply(lhs, rhs, PyLimits),
|
|
41
|
+
ast.Div: operator.truediv,
|
|
42
|
+
ast.FloorDiv: operator.floordiv,
|
|
43
|
+
ast.Mod: lambda lhs, rhs: safe_mod(lhs, rhs, PyLimits),
|
|
44
|
+
ast.Pow: lambda lhs, rhs: safe_power(lhs, rhs, PyLimits),
|
|
45
|
+
ast.LShift: lambda lhs, rhs: safe_lshift(lhs, rhs, PyLimits),
|
|
46
|
+
ast.RShift: operator.rshift,
|
|
47
|
+
ast.BitOr: operator.or_,
|
|
48
|
+
ast.BitXor: operator.xor,
|
|
49
|
+
ast.BitAnd: operator.and_,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DefaultLimits:
|
|
54
|
+
MAX_INT_SIZE = 128
|
|
55
|
+
MAX_COLLECTION_SIZE = 20
|
|
56
|
+
MAX_STR_SIZE = 20
|
|
57
|
+
MAX_TOTAL_ITEMS = 1024
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
LimitsType = type[PyLimits] | type[DefaultLimits]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# pyre-fixme[2]: Parameter annotation cannot be `Any`.
|
|
64
|
+
def safe_lshift(left: Any, right: Any, limits: LimitsType = DefaultLimits) -> object:
|
|
65
|
+
if isinstance(left, int) and isinstance(right, int) and left and right:
|
|
66
|
+
lbits = left.bit_length()
|
|
67
|
+
if (
|
|
68
|
+
right < 0
|
|
69
|
+
or right > limits.MAX_INT_SIZE
|
|
70
|
+
or lbits > limits.MAX_INT_SIZE - right
|
|
71
|
+
):
|
|
72
|
+
raise OverflowError()
|
|
73
|
+
|
|
74
|
+
return left << right
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def check_complexity(obj: object, limit: int) -> int:
|
|
78
|
+
if isinstance(obj, (frozenset, tuple)):
|
|
79
|
+
limit -= len(obj)
|
|
80
|
+
for item in obj:
|
|
81
|
+
limit = check_complexity(item, limit)
|
|
82
|
+
if limit < 0:
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
return limit
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# pyre-fixme[2]: Parameter annotation cannot be `Any`.
|
|
89
|
+
def safe_multiply(left: Any, right: Any, limits: LimitsType = DefaultLimits) -> object:
|
|
90
|
+
if isinstance(left, int) and isinstance(right, int) and left and right:
|
|
91
|
+
lbits = left.bit_length()
|
|
92
|
+
rbits = right.bit_length()
|
|
93
|
+
if lbits + rbits > limits.MAX_INT_SIZE:
|
|
94
|
+
raise OverflowError()
|
|
95
|
+
elif isinstance(left, int) and isinstance(right, (tuple, frozenset)):
|
|
96
|
+
rsize = len(right)
|
|
97
|
+
if rsize:
|
|
98
|
+
if left < 0 or left > limits.MAX_COLLECTION_SIZE / rsize:
|
|
99
|
+
raise OverflowError()
|
|
100
|
+
if left:
|
|
101
|
+
if check_complexity(right, limits.MAX_TOTAL_ITEMS // left) < 0:
|
|
102
|
+
raise OverflowError()
|
|
103
|
+
elif isinstance(left, int) and isinstance(right, (str, bytes)):
|
|
104
|
+
rsize = len(right)
|
|
105
|
+
if rsize:
|
|
106
|
+
if left < 0 or left > limits.MAX_STR_SIZE / rsize:
|
|
107
|
+
raise OverflowError()
|
|
108
|
+
elif isinstance(right, int) and isinstance(left, (tuple, frozenset, str, bytes)):
|
|
109
|
+
return safe_multiply(right, left, limits)
|
|
110
|
+
|
|
111
|
+
return left * right
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# pyre-fixme[2]: Parameter annotation cannot be `Any`.
|
|
115
|
+
def safe_power(left: Any, right: Any, limits: LimitsType = DefaultLimits) -> object:
|
|
116
|
+
if isinstance(left, int) and isinstance(right, int) and left and right > 0:
|
|
117
|
+
lbits = left.bit_length()
|
|
118
|
+
if lbits > limits.MAX_INT_SIZE / right:
|
|
119
|
+
raise OverflowError()
|
|
120
|
+
|
|
121
|
+
return left**right
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# pyre-fixme[2]: Parameter annotation cannot be `Any`.
|
|
125
|
+
def safe_mod(left: Any, right: Any, limits: LimitsType = DefaultLimits) -> object:
|
|
126
|
+
if isinstance(left, (str, bytes)):
|
|
127
|
+
raise OverflowError()
|
|
128
|
+
|
|
129
|
+
return left % right
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class AstOptimizer(ASTRewriter):
|
|
133
|
+
def __init__(self, optimize: bool = False, string_anns: bool = False) -> None:
|
|
134
|
+
super().__init__()
|
|
135
|
+
self.optimize = optimize
|
|
136
|
+
self.string_anns = string_anns
|
|
137
|
+
|
|
138
|
+
def skip_field(self, node: ast.AST, field: str) -> bool:
|
|
139
|
+
if self.string_anns:
|
|
140
|
+
if (
|
|
141
|
+
isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
142
|
+
and field == "returns"
|
|
143
|
+
):
|
|
144
|
+
return True
|
|
145
|
+
if isinstance(node, ast.arg) and field == "annotation":
|
|
146
|
+
return True
|
|
147
|
+
if isinstance(node, ast.AnnAssign) and field == "annotation":
|
|
148
|
+
return True
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
def visitUnaryOp(self, node: ast.UnaryOp) -> ast.expr:
|
|
152
|
+
op = self.visit(node.operand)
|
|
153
|
+
if isinstance(op, Constant):
|
|
154
|
+
conv = UNARY_OPS[type(node.op)]
|
|
155
|
+
try:
|
|
156
|
+
return copy_location(Constant(conv(op.value)), node)
|
|
157
|
+
except Exception:
|
|
158
|
+
pass
|
|
159
|
+
elif (
|
|
160
|
+
isinstance(node.op, ast.Not)
|
|
161
|
+
and isinstance(op, ast.Compare)
|
|
162
|
+
and len(op.ops) == 1
|
|
163
|
+
):
|
|
164
|
+
cmp_op = op.ops[0]
|
|
165
|
+
new_op = INVERSE_OPS.get(type(cmp_op))
|
|
166
|
+
if new_op is not None:
|
|
167
|
+
return self.update_node(op, ops=[new_op()])
|
|
168
|
+
|
|
169
|
+
return self.update_node(node, operand=op)
|
|
170
|
+
|
|
171
|
+
def visitBinOp(self, node: ast.BinOp) -> ast.expr:
|
|
172
|
+
left = self.visit(node.left)
|
|
173
|
+
right = self.visit(node.right)
|
|
174
|
+
|
|
175
|
+
if isinstance(left, Constant) and isinstance(right, Constant):
|
|
176
|
+
handler = BIN_OPS.get(type(node.op))
|
|
177
|
+
if handler is not None:
|
|
178
|
+
try:
|
|
179
|
+
return copy_location(
|
|
180
|
+
Constant(handler(left.value, right.value)), node
|
|
181
|
+
)
|
|
182
|
+
except Exception:
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
return self.update_node(node, left=left, right=right)
|
|
186
|
+
|
|
187
|
+
def makeConstTuple(self, elts: Iterable[ast.expr]) -> Constant | None:
|
|
188
|
+
if all(isinstance(elt, Constant) for elt in elts):
|
|
189
|
+
# pyre-ignore[16]: each elt is a constant at this point.
|
|
190
|
+
return Constant(tuple(elt.value for elt in elts))
|
|
191
|
+
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
def visitTuple(self, node: ast.Tuple) -> ast.expr:
|
|
195
|
+
elts = self.walk_list(node.elts)
|
|
196
|
+
|
|
197
|
+
if isinstance(node.ctx, ast.Load):
|
|
198
|
+
# pyre-ignore[6]: Can't type walk_list fully yet.
|
|
199
|
+
res = self.makeConstTuple(elts)
|
|
200
|
+
if res is not None:
|
|
201
|
+
return copy_location(res, node)
|
|
202
|
+
|
|
203
|
+
return self.update_node(node, elts=elts)
|
|
204
|
+
|
|
205
|
+
def visitSubscript(self, node: ast.Subscript) -> ast.expr:
|
|
206
|
+
value = self.visit(node.value)
|
|
207
|
+
slice = self.visit(node.slice)
|
|
208
|
+
|
|
209
|
+
if (
|
|
210
|
+
isinstance(node.ctx, ast.Load)
|
|
211
|
+
and isinstance(value, Constant)
|
|
212
|
+
and isinstance(slice, Constant)
|
|
213
|
+
):
|
|
214
|
+
try:
|
|
215
|
+
return copy_location(
|
|
216
|
+
Constant(value.value[slice.value]),
|
|
217
|
+
node,
|
|
218
|
+
)
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
return self.update_node(node, value=value, slice=slice)
|
|
223
|
+
|
|
224
|
+
def _visitIter(self, node: ast.expr) -> ast.expr:
|
|
225
|
+
if isinstance(node, ast.List):
|
|
226
|
+
elts = self.walk_list(node.elts)
|
|
227
|
+
# pyre-ignore[6]: Can't type walk_list fully yet.
|
|
228
|
+
res = self.makeConstTuple(elts)
|
|
229
|
+
if res is not None:
|
|
230
|
+
return copy_location(res, node)
|
|
231
|
+
if not any(isinstance(e, ast.Starred) for e in elts):
|
|
232
|
+
# pyre-fixme[6]: For 1st argument expected `List[expr]` but got
|
|
233
|
+
# `Sequence[expr]`.
|
|
234
|
+
return copy_location(ast.Tuple(elts=elts, ctx=node.ctx), node)
|
|
235
|
+
return self.update_node(node, elts=elts)
|
|
236
|
+
elif isinstance(node, ast.Set):
|
|
237
|
+
elts = self.walk_list(node.elts)
|
|
238
|
+
# pyre-ignore[6]: Can't type walk_list fully yet.
|
|
239
|
+
res = self.makeConstTuple(elts)
|
|
240
|
+
if res is not None:
|
|
241
|
+
return copy_location(Constant(frozenset(res.value)), node)
|
|
242
|
+
|
|
243
|
+
return self.update_node(node, elts=elts)
|
|
244
|
+
|
|
245
|
+
return self.generic_visit(node)
|
|
246
|
+
|
|
247
|
+
def visitcomprehension(self, node: ast.comprehension) -> ast.comprehension:
|
|
248
|
+
target = self.visit(node.target)
|
|
249
|
+
iter = self.visit(node.iter)
|
|
250
|
+
assert isinstance(iter, ast.expr)
|
|
251
|
+
ifs = self.walk_list(node.ifs)
|
|
252
|
+
iter = self._visitIter(iter)
|
|
253
|
+
|
|
254
|
+
return self.update_node(node, target=target, iter=iter, ifs=ifs)
|
|
255
|
+
|
|
256
|
+
def visitFor(self, node: ast.For) -> ast.For:
|
|
257
|
+
target = self.visit(node.target)
|
|
258
|
+
iter = self.visit(node.iter)
|
|
259
|
+
assert isinstance(iter, ast.expr)
|
|
260
|
+
body = self.walk_list(node.body)
|
|
261
|
+
orelse = self.walk_list(node.orelse)
|
|
262
|
+
|
|
263
|
+
iter = self._visitIter(iter)
|
|
264
|
+
return self.update_node(
|
|
265
|
+
node, target=target, iter=iter, body=body, orelse=orelse
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def visitCompare(self, node: ast.Compare) -> ast.expr:
|
|
269
|
+
left = self.visit(node.left)
|
|
270
|
+
comparators = self.walk_list(node.comparators)
|
|
271
|
+
|
|
272
|
+
if isinstance(node.ops[-1], (ast.In, ast.NotIn)):
|
|
273
|
+
# pyre-ignore[6]: Can't type walk_list fully yet.
|
|
274
|
+
new_iter = self._visitIter(comparators[-1])
|
|
275
|
+
if new_iter is not None and new_iter is not comparators[-1]:
|
|
276
|
+
comparators = list(comparators)
|
|
277
|
+
comparators[-1] = new_iter
|
|
278
|
+
|
|
279
|
+
return self.update_node(node, left=left, comparators=comparators)
|
|
280
|
+
|
|
281
|
+
def visitName(self, node: ast.Name) -> ast.Name | ast.Constant:
|
|
282
|
+
if node.id == "__debug__":
|
|
283
|
+
return copy_location(Constant(not self.optimize), node)
|
|
284
|
+
|
|
285
|
+
return self.generic_visit(node)
|
|
286
|
+
|
|
287
|
+
def visitNamedExpr(self, node: ast.NamedExpr) -> ast.NamedExpr:
|
|
288
|
+
return self.generic_visit(node)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
F_LJUST: int = 1 << 0
|
|
292
|
+
F_SIGN: int = 1 << 1
|
|
293
|
+
F_BLANK: int = 1 << 2
|
|
294
|
+
F_ALT: int = 1 << 3
|
|
295
|
+
F_ZERO: int = 1 << 4
|
|
296
|
+
|
|
297
|
+
FLAG_DICT: dict[str, int] = {
|
|
298
|
+
"-": F_LJUST,
|
|
299
|
+
"+": F_SIGN,
|
|
300
|
+
" ": F_BLANK,
|
|
301
|
+
"#": F_ALT,
|
|
302
|
+
"0": F_ZERO,
|
|
303
|
+
}
|
|
304
|
+
MAXDIGITS = 3
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class UnsupportedFormat(Exception):
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@dataclass(frozen=True)
|
|
312
|
+
class FormatInfo:
|
|
313
|
+
spec: str
|
|
314
|
+
flags: int = 0
|
|
315
|
+
width: int | None = None
|
|
316
|
+
prec: int | None = None
|
|
317
|
+
|
|
318
|
+
def as_formatted_value(self, arg: ast.expr) -> ast.FormattedValue | None:
|
|
319
|
+
if self.spec not in "sra":
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
res = []
|
|
323
|
+
if not (self.flags & F_LJUST) and self.width:
|
|
324
|
+
res.append(">")
|
|
325
|
+
if self.width is not None:
|
|
326
|
+
res.append(str(self.width))
|
|
327
|
+
if self.prec is not None:
|
|
328
|
+
res.append(f".{self.prec}")
|
|
329
|
+
|
|
330
|
+
return copy_location(
|
|
331
|
+
ast.FormattedValue(
|
|
332
|
+
arg,
|
|
333
|
+
ord(self.spec),
|
|
334
|
+
copy_location(ast.Constant("".join(res)), arg) if res else None,
|
|
335
|
+
),
|
|
336
|
+
arg,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class FormatParser:
|
|
341
|
+
def __init__(self, val: str) -> None:
|
|
342
|
+
self.val = val
|
|
343
|
+
self.size: int = len(val)
|
|
344
|
+
self.pos = 0
|
|
345
|
+
|
|
346
|
+
def next_ch(self) -> str:
|
|
347
|
+
"""Gets the next character in the format string, or raises an
|
|
348
|
+
exception if we've run out of characters"""
|
|
349
|
+
if self.pos >= self.size:
|
|
350
|
+
raise UnsupportedFormat()
|
|
351
|
+
self.pos += 1
|
|
352
|
+
return self.val[self.pos]
|
|
353
|
+
|
|
354
|
+
def parse_int(self) -> int:
|
|
355
|
+
"""Parses an integer from the format string"""
|
|
356
|
+
res = 0
|
|
357
|
+
digits = 0
|
|
358
|
+
ch = self.val[self.pos]
|
|
359
|
+
while "0" <= ch <= "9":
|
|
360
|
+
res = res * 10 + ord(ch) - ord("0")
|
|
361
|
+
ch = self.next_ch()
|
|
362
|
+
digits += 1
|
|
363
|
+
if digits >= MAXDIGITS:
|
|
364
|
+
raise UnsupportedFormat()
|
|
365
|
+
return res
|
|
366
|
+
|
|
367
|
+
def parse_flags(self, ch: str) -> int:
|
|
368
|
+
"""Parse any flags (-, +, " ", #, 0)"""
|
|
369
|
+
flags = 0
|
|
370
|
+
while True:
|
|
371
|
+
flag_val = FLAG_DICT.get(ch)
|
|
372
|
+
if flag_val is not None:
|
|
373
|
+
flags |= flag_val
|
|
374
|
+
ch = self.next_ch()
|
|
375
|
+
else:
|
|
376
|
+
break
|
|
377
|
+
return flags
|
|
378
|
+
|
|
379
|
+
def parse_str(self) -> str:
|
|
380
|
+
"""Parses a string component of the format string up to a %"""
|
|
381
|
+
has_percents = False
|
|
382
|
+
start = self.pos
|
|
383
|
+
while self.pos < self.size:
|
|
384
|
+
ch = self.val[self.pos]
|
|
385
|
+
if ch != "%":
|
|
386
|
+
self.pos += 1
|
|
387
|
+
elif self.pos + 1 < self.size and self.val[self.pos + 1] == "%":
|
|
388
|
+
has_percents = True
|
|
389
|
+
self.pos += 2
|
|
390
|
+
else:
|
|
391
|
+
break
|
|
392
|
+
|
|
393
|
+
component = self.val[start : self.pos]
|
|
394
|
+
if has_percents:
|
|
395
|
+
component = component.replace("%%", "%")
|
|
396
|
+
return component
|
|
397
|
+
|
|
398
|
+
def enum_components(self) -> Iterable[str | FormatInfo]:
|
|
399
|
+
"""Enumerates the components of the format string and returns a stream
|
|
400
|
+
of interleaved strings and FormatInfo objects"""
|
|
401
|
+
# Parse the string up to the format specifier
|
|
402
|
+
ch = None
|
|
403
|
+
while self.pos < self.size:
|
|
404
|
+
yield self.parse_str()
|
|
405
|
+
|
|
406
|
+
if self.pos == self.size:
|
|
407
|
+
return
|
|
408
|
+
|
|
409
|
+
assert self.val[self.pos] == "%"
|
|
410
|
+
|
|
411
|
+
flags = self.parse_flags(self.next_ch())
|
|
412
|
+
|
|
413
|
+
# Parse width
|
|
414
|
+
width = None
|
|
415
|
+
if "0" <= self.val[self.pos] <= "9":
|
|
416
|
+
width = self.parse_int()
|
|
417
|
+
|
|
418
|
+
prec = None
|
|
419
|
+
if self.val[self.pos] == ".":
|
|
420
|
+
self.next_ch()
|
|
421
|
+
prec = self.parse_int()
|
|
422
|
+
|
|
423
|
+
yield FormatInfo(self.val[self.pos], flags, width, prec)
|
|
424
|
+
self.pos += 1
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def enum_format_str_components(val: str) -> Iterable[str | FormatInfo]:
|
|
428
|
+
return FormatParser(val).enum_components()
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def set_no_locations(node: ast.expr) -> ast.expr:
|
|
432
|
+
node.lineno = -1
|
|
433
|
+
node.end_lineno = -1
|
|
434
|
+
node.col_offset = -1
|
|
435
|
+
node.end_col_offset = -1
|
|
436
|
+
return node
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
class AstOptimizer312(AstOptimizer):
|
|
440
|
+
def visitBinOp(self, node: ast.BinOp) -> ast.expr:
|
|
441
|
+
res = super().visitBinOp(node)
|
|
442
|
+
|
|
443
|
+
if (
|
|
444
|
+
not isinstance(res, ast.BinOp)
|
|
445
|
+
or not isinstance(res.op, ast.Mod)
|
|
446
|
+
or not isinstance(res.left, ast.Constant)
|
|
447
|
+
or not isinstance(res.left.value, str)
|
|
448
|
+
or not isinstance(res.right, ast.Tuple)
|
|
449
|
+
or any(isinstance(e, ast.Starred) for e in res.right.elts)
|
|
450
|
+
):
|
|
451
|
+
return res
|
|
452
|
+
|
|
453
|
+
return self.optimize_format(res)
|
|
454
|
+
|
|
455
|
+
def optimize_format(self, node: ast.BinOp) -> ast.expr:
|
|
456
|
+
left = node.left
|
|
457
|
+
right = node.right
|
|
458
|
+
assert isinstance(left, ast.Constant)
|
|
459
|
+
assert isinstance(right, ast.Tuple)
|
|
460
|
+
assert isinstance(left.value, str)
|
|
461
|
+
|
|
462
|
+
try:
|
|
463
|
+
seq = []
|
|
464
|
+
cnt = 0
|
|
465
|
+
for item in enum_format_str_components(left.value):
|
|
466
|
+
if isinstance(item, str):
|
|
467
|
+
if item:
|
|
468
|
+
seq.append(set_no_locations(ast.Constant(item)))
|
|
469
|
+
continue
|
|
470
|
+
|
|
471
|
+
if cnt >= len(right.elts):
|
|
472
|
+
# More format units than items.
|
|
473
|
+
return node
|
|
474
|
+
|
|
475
|
+
formatted = item.as_formatted_value(right.elts[cnt])
|
|
476
|
+
if formatted is None:
|
|
477
|
+
return node
|
|
478
|
+
seq.append(formatted)
|
|
479
|
+
cnt += 1
|
|
480
|
+
|
|
481
|
+
if cnt < len(right.elts):
|
|
482
|
+
# More items than format units.
|
|
483
|
+
return node
|
|
484
|
+
|
|
485
|
+
return copy_location(ast.JoinedStr(seq), node)
|
|
486
|
+
|
|
487
|
+
except UnsupportedFormat:
|
|
488
|
+
return node
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
class AstOptimizer314(AstOptimizer312):
|
|
492
|
+
def has_starred(self, e: ast.Tuple) -> bool:
|
|
493
|
+
return any(isinstance(e, ast.Starred) for e in e.elts)
|
|
494
|
+
|
|
495
|
+
def visitUnaryOp(self, node: ast.UnaryOp) -> ast.expr:
|
|
496
|
+
op = self.visit(node.operand)
|
|
497
|
+
return self.update_node(node, operand=op)
|
|
498
|
+
|
|
499
|
+
def visitBinOp(self, node: ast.BinOp) -> ast.expr:
|
|
500
|
+
lhs = self.visit(node.left)
|
|
501
|
+
rhs = self.visit(node.right)
|
|
502
|
+
if (
|
|
503
|
+
isinstance(lhs, ast.Constant)
|
|
504
|
+
and isinstance(rhs, ast.Tuple)
|
|
505
|
+
and isinstance(lhs.value, str)
|
|
506
|
+
and not self.has_starred(rhs)
|
|
507
|
+
):
|
|
508
|
+
return self.optimize_format(self.update_node(node, left=lhs, right=rhs))
|
|
509
|
+
|
|
510
|
+
return self.update_node(node, left=lhs, right=rhs)
|
|
511
|
+
|
|
512
|
+
def visitSubscript(self, node: ast.Subscript) -> ast.expr:
|
|
513
|
+
value = self.visit(node.value)
|
|
514
|
+
slice = self.visit(node.slice)
|
|
515
|
+
|
|
516
|
+
return self.update_node(node, value=value, slice=slice)
|
|
517
|
+
|
|
518
|
+
def visitMatchValue(self, node: ast.MatchValue) -> ast.MatchValue:
|
|
519
|
+
return self.update_node(node, value=self.fold_const_match_patterns(node.value))
|
|
520
|
+
|
|
521
|
+
def visitMatchMapping(self, node: ast.MatchMapping) -> ast.MatchMapping:
|
|
522
|
+
keys = [self.fold_const_match_patterns(key) for key in node.keys]
|
|
523
|
+
patterns = self.walk_list(node.patterns)
|
|
524
|
+
return self.update_node(node, keys=keys, patterns=patterns)
|
|
525
|
+
|
|
526
|
+
def fold_const_match_patterns(self, node: ast.expr) -> ast.expr:
|
|
527
|
+
if isinstance(node, ast.UnaryOp):
|
|
528
|
+
if isinstance(node.op, ast.USub) and isinstance(node.operand, ast.Constant):
|
|
529
|
+
return super().visitUnaryOp(node)
|
|
530
|
+
elif isinstance(node, ast.BinOp) and isinstance(node.op, (ast.Add, ast.Sub)):
|
|
531
|
+
if isinstance(node.right, ast.Constant):
|
|
532
|
+
node = self.update_node(
|
|
533
|
+
node, left=self.fold_const_match_patterns(node.left)
|
|
534
|
+
)
|
|
535
|
+
if isinstance(node.left, ast.Constant):
|
|
536
|
+
return super().visitBinOp(node)
|
|
537
|
+
|
|
538
|
+
return node
|
|
539
|
+
|
|
540
|
+
def visitTuple(self, node: ast.Tuple) -> ast.expr:
|
|
541
|
+
elts = self.walk_list(node.elts)
|
|
542
|
+
|
|
543
|
+
return self.update_node(node, elts=elts)
|
|
544
|
+
|
|
545
|
+
def _visitIter(self, node: ast.expr) -> ast.expr:
|
|
546
|
+
# This optimization has been removed in 3.14
|
|
547
|
+
return node
|