-
Notifications
You must be signed in to change notification settings - Fork 1
/
varname.py
404 lines (395 loc) · 14.5 KB
/
varname.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
from collections import deque
from dis import (_inline_cache_entries as _ICE, get_instructions,
hasjabs, hasjrel, opmap)
from sys import _getframe as _out, version_info
hasjabs = {*hasjabs}
hasjrel = {*hasjrel}
hasj = hasjabs | hasjrel
globals().update({
x: opmap.get(x, -1)
for x in
"LOAD_NAME LOAD_FAST LOAD_DEREF LOAD_CLASSDEREF LOAD_GLOBAL "
"LOAD_ATTR CALL PRECALL EXTENDED_ARG KW_NAMES "
"LOAD_FROM_DICT_OR_GLOBALS LOAD_FAST_CHECK LOAD_FAST_AND_CLEAR "
"LOAD_FROM_DICT_OR_DEREF LOAD_SUPER_ATTR CACHE JUMP_FORWARD "
"LOAD_CONST LOAD_METHOD BUILD_TUPLE STORE_FAST STORE_DEREF "
"STORE_FAST_MAYBE_NULL STORE_NAME STORE_GLOBAL COPY "
"CALL_INTRINSIC_1 LIST_TO_TUPLE PUSH_NULL BUILD_LIST "
"LIST_APPEND LIST_EXTEND BUILD_CONST_KEY_MAP DICT_MERGE "
"BUILD_MAP CALL_FUNCTION_EX"
.split()
})
if NOT_311 := not (3,11) <= version_info < (3, 12):
PRECALL_HEADER = 0
else:
PRECALL_HEADER = 1 + _ICE[PRECALL]
ARG_OFFSET = (PRECALL_HEADER + 1 + _ICE[CALL]) * 2
FASTLOCAL_LOADS = {
LOAD_FAST, LOAD_DEREF, LOAD_CLASSDEREF, LOAD_FAST_CHECK,
LOAD_FAST_AND_CLEAR, LOAD_FROM_DICT_OR_DEREF
}
FASTLOCAL_STORES = {
STORE_FAST, STORE_DEREF, STORE_FAST_MAYBE_NULL
}
def skip_header(instrs, i):
assert not i & 1, "Odd instruction index"
assert i >= 0, "Negative index"
seen_KW_NAMES = False
while instrs[i] == EXTENDED_ARG or instrs[i] == CACHE:
i -= 2
if instrs[i] == KW_NAMES:
assert not seen_KW_NAMES, "Duplicate KW_NAMES"
seen_KW_NAMES = True
i -= 2
return i
def get_localname(code, oparg):
# local var
if oparg < code.co_nlocals:
return code.co_varnames[oparg]
oparg -= code.co_nlocals
# cell var
n_deref = len(code.co_cellvars)
if oparg < n_deref:
return code.co_cellvars[oparg]
oparg -= n_deref
# free var
return code.co_freevars[oparg]
def is_posargs_end(instr):
return (
instr.opcode == LIST_TO_TUPLE
or instr.opcode == CALL_INTRINSIC_1 and instr.arg == 6
or instr.opcode == LOAD_CONST and instr.argval == ()
)
def _varname(
code, i, instrs=None, valid_jumps=None,
find_idx=False, allow_const=False,
reverse_append=False
):
if instrs is None:
instrs = [*get_instructions(code)]
if find_idx:
assert not i & 1, "Odd instruction index"
for j, instr in enumerate(instrs):
if i == instr.offset:
i = j
break
else:
raise IndexError(f"cannot find index {i} in code sequence")
if valid_jumps is None:
valid_jumps = set()
invalid = set()
for j in range(i):
instr = instrs[j]
if instr.opcode == JUMP_FORWARD and instr.argval not in invalid:
valid_jumps.add(instr.argval)
elif instr.opcode in hasj:
if instr.argval in valid_jumps:
valid_jumps.remove(instr.argval)
invalid.add(instr.argval)
qname_list = deque()
if reverse_append:
add = qname_list.append
addmany = qname_list.extend
else:
add = qname_list.appendleft
addmany = qname_list.extendleft
orig_i = i
while i >= 0:
instr = instrs[i]
opcode = instr.opcode
oparg = instr.arg
i -= 1
if instr.is_jump_target and instr.offset not in valid_jumps:
# part of a complex expression; break
i = orig_i
qname_list.clear()
break
if opcode == LOAD_NAME or opcode == LOAD_FROM_DICT_OR_GLOBALS:
add(code.co_names[oparg])
break
elif opcode == LOAD_GLOBAL:
add(code.co_names[oparg >> 1])
break
elif opcode in FASTLOCAL_LOADS:
add(get_localname(code, oparg))
break
elif opcode == LOAD_ATTR or opcode == LOAD_METHOD:
add(code.co_names[oparg >> NOT_311])
add('.')
elif opcode == LOAD_SUPER_ATTR:
# attr
add(code.co_names[oparg >> 2])
add(").")
if oparg & 2:
# second super() arg
name1, i, try_ = _varname_default(
code, i, instrs,
valid_jumps, allow_const=True,
reverse_append=True
)
# first super() arg
name0, i, _ = _varname_default(
code, i, instrs,
valid_jumps, allow_const=True,
reverse_append=True,
try_=try_
)
# add in reverse order
addmany(name1)
add(", ")
addmany(name0)
add("super(")
break
elif opcode == CALL:
narg = oparg
instr = instrs[i]
opcode = instr.opcode
if opcode == PRECALL:
i -= 1
instr = instrs[i]
opcode = instr.opcode
if opcode == KW_NAMES:
i -= 1
kw = code.co_consts[instr.arg]
nkw = len(kw)
else:
nkw = 0
kw = ()
still_simple = True
add(')')
add_comma = False
for n in range(narg):
valname, i, still_simple = _varname_default(
code, i, instrs,
valid_jumps, allow_const=True,
reverse_append=True,
try_=still_simple
)
if add_comma:
add(", ")
else:
add_comma = True
addmany(valname)
if n < nkw:
add('=')
add(kw[~n])
add('(')
funcname, i, still_simple = _varname_default(
code, i, instrs,
valid_jumps, allow_const=True,
reverse_append=True,
try_=still_simple
)
addmany(funcname)
if still_simple and instrs[i].opcode == PUSH_NULL:
i -= 1
break
elif opcode == LOAD_CONST and allow_const:
if (isinstance(instr.argval, int)
and qname_list and qname_list[0] == '.'):
add(' ')
add(instr.argrepr)
break
elif opcode == BUILD_TUPLE:
still_simple = True
add_comma = False
for _ in range(oparg):
elname, i, still_simple = _varname_default(
code, i, instrs,
valid_jumps, allow_const=True,
reverse_append=True,
try_=still_simple
)
if add_comma:
add(", ")
else:
add_comma = True
addmany(elname)
break
elif opcode in FASTLOCAL_STORES:
assert instrs[i].opcode == COPY and instrs[i + 1].arg == 1, \
"Walrus expected; not found"
add(get_localname(code, oparg))
break
elif opcode == STORE_NAME or opcode == STORE_GLOBAL:
assert instrs[i].opcode == COPY and instrs[i + 1].arg == 1, \
"Walrus expected; not found"
add(code.co_names[oparg])
break
elif opcode == CALL_FUNCTION_EX:
add(')')
still_simple = True
add_comma = False
do_posargs = True
nprocessed = 0
if oparg & 1:
while still_simple:
instr = instrs[i]
if is_KW := instr.opcode == DICT_MERGE:
instr = instrs[i := i - 1]
opcode = instr.opcode
if opcode == BUILD_MAP and instr.arg == 1:
i -= 1
argname, i, still_simple = _varname_default(
code, i, instrs,
valid_jumps, allow_const=True,
reverse_append=True,
try_=still_simple
)
if still_simple:
instr = instrs[i]
assert instr.opcode == LOAD_CONST, \
"Keyword argument expected; not found"
i -= 1
kwname = instr.argval
else:
kwname = "..."
if add_comma:
add(", ")
else:
add_comma = True
addmany(argname)
add('=')
add(kwname)
elif opcode == BUILD_MAP and instr.arg == 0:
i -= 1
continue
elif opcode == BUILD_CONST_KEY_MAP:
nkw = instr.arg
instr = instrs[i := i - 1]
assert instr.opcode == LOAD_CONST, \
"Keyword arguments expected; not found"
kw = instr.argval
i -= 1
for n in range(nkw):
argname, i, still_simple = _varname_default(
code, i, instrs,
valid_jumps, allow_const=True,
reverse_append=True,
try_=still_simple
)
if add_comma:
add(", ")
else:
add_comma = True
addmany(argname)
add('=')
add(kw[~n])
elif is_KW:
argname, i, still_simple = _varname_default(
code, i, instrs,
valid_jumps, allow_const=True,
reverse_append=True,
try_=still_simple
)
if add_comma:
add(", ")
else:
add_comma = True
addmany(argname)
add("**")
elif is_posargs_end(instr):
do_posargs = opcode != LOAD_CONST
i -= 1
break
else:
still_simple = False
if still_simple:
nprocessed += 1
elif is_posargs_end(instrs[i]):
do_posargs = opcode != LOAD_CONST
i -= 1
posarg_complex = False
if do_posargs:
while still_simple:
instr = instrs[i]
opcode = instr.opcode
if opcode == LIST_APPEND:
i -= 1
argname, i, still_simple = _varname_default(
code, i, instrs,
valid_jumps, allow_const=True,
reverse_append=True,
try_=still_simple
)
if not still_simple:
posarg_complex = True
if add_comma:
add(", ")
else:
add_comma = True
addmany(argname)
elif opcode == BUILD_LIST:
i -= 1
for _ in range(instr.arg):
argname, i, still_simple = _varname_default(
code, i, instrs,
valid_jumps, allow_const=True,
reverse_append=True,
try_=still_simple
)
if not still_simple:
posarg_complex = True
if add_comma:
add(", ")
else:
add_comma = True
addmany(argname)
break
else:
is_LIST_EXTEND = opcode == LIST_EXTEND
i -= is_LIST_EXTEND
argname, i, still_simple = _varname_default(
code, i, instrs,
valid_jumps, allow_const=True,
reverse_append=True,
try_=still_simple
)
if add_comma:
add(", ")
else:
add_comma = True
addmany(argname)
add('*')
if not is_LIST_EXTEND:
break
if still_simple:
nprocessed += 1
if not still_simple and not posarg_complex:
if add_comma:
add(", ")
else:
add_comma = True
if nprocessed > 0:
add(', ')
add("...")
add('(')
funcname, i, still_simple = _varname_default(
code, i, instrs,
valid_jumps, allow_const=True,
reverse_append=True,
try_=still_simple
)
addmany(funcname)
if still_simple and instrs[i].opcode == PUSH_NULL:
i -= 1
break
elif opcode != CACHE:
break
return qname_list, i
def _varname_default(code, i, *args, default=("...",), try_=True, **kwargs):
if try_:
name, i = _varname(code, i, *args, **kwargs)
success = bool(name)
else:
success = False
if not success:
name = default
return name, i, success
def varname(val):
frame = _out(1)
i = skip_header(frame.f_code.co_code, frame.f_lasti - ARG_OFFSET)
qname_list, _ = _varname(frame.f_code, i, find_idx=True)
if not qname_list:
raise ValueError(f"No name found for value: {val!r}")
return "".join(qname_list)