depyo 1.0.1 → 1.0.3

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.
@@ -60,8 +60,24 @@ function handleMakeFunctionA() {
60
60
  }
61
61
 
62
62
  function handleMakeCellA() {
63
- const varNames = this.object?.VarNames?.Value || [];
64
- const name = varNames[this.code.Current.Argument]?.toString?.() || `##cell_${this.code.Current.Argument}##`;
63
+ // In 3.11+ the argument is an index into localsplus ([locals | cells | frees]).
64
+ // Prefer the already-computed FreeName when available; otherwise fall back to
65
+ // VarNames + CellVars + FreeVars lookup.
66
+ const arg = this.code.Current.Argument;
67
+ let name = this.code.Current.FreeName;
68
+ if (!name) {
69
+ const varNames = this.object?.VarNames?.Value || [];
70
+ const cellVars = this.object?.CellVars?.Value || [];
71
+ const freeVars = this.object?.FreeVars?.Value || [];
72
+ if (arg < varNames.length) {
73
+ name = varNames[arg]?.toString?.();
74
+ } else if (arg - varNames.length < cellVars.length) {
75
+ name = cellVars[arg - varNames.length]?.toString?.();
76
+ } else if (arg - varNames.length - cellVars.length < freeVars.length) {
77
+ name = freeVars[arg - varNames.length - cellVars.length]?.toString?.();
78
+ }
79
+ }
80
+ if (!name) name = `##cell_${arg}##`;
65
81
  this.dataStack.push(new AST.ASTName(name));
66
82
  }
67
83
 
@@ -76,11 +92,15 @@ function handleSetFunctionAttributeA() {
76
92
  const pairs = tupleNode?.values || [];
77
93
  for (let i = 0; i + 1 < pairs.length; i += 2) {
78
94
  const keyNode = pairs[i];
79
- const valNode = pairs[i + 1];
95
+ let valNode = pairs[i + 1];
80
96
  let keyStr = keyNode?.object?.Value || keyNode?.name || keyNode?.codeFragment?.()?.toString?.() || keyNode?.toString?.() || '';
81
97
  if (typeof keyStr === 'string') {
82
98
  keyStr = keyStr.replace(/^['"]|['"]$/g, "");
83
99
  }
100
+ // Strip spurious parens around tuple subscripts (e.g. tuple[(T, U)] → tuple[T, U]).
101
+ if (valNode instanceof AST.ASTSubscr && valNode.key instanceof AST.ASTTuple) {
102
+ valNode.key.m_requireParens = false;
103
+ }
84
104
  if (keyStr) {
85
105
  annotations[keyStr] = valNode;
86
106
  }
@@ -138,87 +158,162 @@ function processMakeFunction() {
138
158
 
139
159
  let decompiler = new PycDecompiler(func_code.object);
140
160
  func_code.object.SourceCode = decompiler.decompile();
161
+ if (decompiler.errors.length) this.errors.push(...decompiler.errors);
141
162
 
142
- let annotationMap = null;
143
- // Python 3.11+ MAKE_FUNCTION flag bit 0x4 indicates annotations tuple on stack before code object
144
- if ((this.object.Reader.versionCompare(3, 11) >= 0) &&
145
- (this.code.Current.Argument & 0x04) &&
146
- this.dataStack.top() instanceof AST.ASTTuple) {
147
- const tupleAnn = this.dataStack.pop();
148
- const pairs = tupleAnn.values || [];
149
- annotationMap = {};
150
- for (let i = 0; i + 1 < pairs.length; i += 2) {
151
- const keyNode = pairs[i];
152
- const valNode = pairs[i + 1];
153
- let keyStr = keyNode?.object?.Value || keyNode?.name || keyNode?.codeFragment?.()?.toString?.() || keyNode?.toString?.() || '';
154
- if (typeof keyStr === 'string') {
155
- keyStr = keyStr.replace(/^['"]|['"]$/g, "");
163
+ let defArgs = [], kwDefArgs = [], annotationMap = null;
164
+
165
+ const stringifyKey = (keyNode) => {
166
+ let keyStr = keyNode?.object?.Value ?? keyNode?.name ?? keyNode?.codeFragment?.()?.toString?.() ?? keyNode?.toString?.() ?? '';
167
+ if (typeof keyStr === 'string') {
168
+ keyStr = keyStr.replace(/^['"]|['"]$/g, "");
169
+ }
170
+ return keyStr;
171
+ };
172
+
173
+ const buildAnnotationMap = (node) => {
174
+ const map = {};
175
+ // ASTObject wrapping a Py_Tuple constant — unpack
176
+ if (node instanceof AST.ASTObject &&
177
+ (node.object?.ClassName === 'Py_Tuple' || node.object?.ClassName === 'Py_SmallTuple')) {
178
+ const items = (node.object.Value || []).map(v => new AST.ASTObject(v));
179
+ for (let i = 0; i + 1 < items.length; i += 2) {
180
+ const keyStr = stringifyKey(items[i]);
181
+ if (keyStr) map[keyStr] = items[i + 1];
156
182
  }
157
- if (keyStr) {
158
- annotationMap[keyStr] = valNode;
183
+ return map;
184
+ }
185
+ if (node instanceof AST.ASTTuple) {
186
+ // Python 3.10+ format: (key1, val1, key2, val2, ...)
187
+ const pairs = node.values || [];
188
+ for (let i = 0; i + 1 < pairs.length; i += 2) {
189
+ const keyStr = stringifyKey(pairs[i]);
190
+ if (keyStr) map[keyStr] = pairs[i + 1];
191
+ }
192
+ } else if (node instanceof AST.ASTConstMap) {
193
+ // Python 3.6–3.9 (BUILD_CONST_KEY_MAP): keys = ASTObject(tuple of names),
194
+ // values = array popped from stack in reverse order.
195
+ const keysObj = node.keys;
196
+ let flatKeys = [];
197
+ if (keysObj instanceof AST.ASTObject) {
198
+ flatKeys = keysObj.object?.Value || [];
199
+ } else if (Array.isArray(keysObj)) {
200
+ flatKeys = keysObj;
201
+ }
202
+ const valuesArr = Array.isArray(node.values) ? [...node.values].reverse() : [];
203
+ for (let i = 0; i < Math.min(flatKeys.length, valuesArr.length); i++) {
204
+ let keyStr = flatKeys[i];
205
+ if (keyStr && typeof keyStr === 'object') keyStr = keyStr.Value ?? keyStr.toString?.();
206
+ if (typeof keyStr === 'string') keyStr = keyStr.replace(/^['"]|['"]$/g, "");
207
+ if (keyStr) map[String(keyStr)] = valuesArr[i];
208
+ }
209
+ } else if (node instanceof AST.ASTMap) {
210
+ // BUILD_MAP form: keys/values added in pairs.
211
+ const keys = node.keys || [];
212
+ const values = node.values || [];
213
+ for (let i = 0; i < Math.min(keys.length, values.length); i++) {
214
+ const keyStr = stringifyKey(keys[i]);
215
+ if (keyStr) map[keyStr] = values[i];
159
216
  }
160
217
  }
161
- }
218
+ return map;
219
+ };
162
220
 
163
- let defArgs = [], kwDefArgs = [], annotations = [];
164
- const is311Plus = this.object.Reader.versionCompare(3, 11) >= 0;
165
- if (is311Plus) {
221
+ if (this.object.Reader.versionCompare(3, 6) >= 0) {
222
+ // Python 3.6+ MAKE_FUNCTION uses flag-based encoding.
223
+ // Stack from top → bottom: [closure(0x08)?, annotations(0x04)?, kwdefaults(0x02)?, defaults(0x01)?]
166
224
  const flags = this.code.Current.Argument;
167
- // defaults tuple
225
+ if (flags & 0x08) {
226
+ this.dataStack.pop(); // closure tuple, captured via cellvars
227
+ }
228
+ if (flags & 0x04) {
229
+ const annNode = this.dataStack.pop();
230
+ annotationMap = buildAnnotationMap(annNode);
231
+ }
232
+ if (flags & 0x02) {
233
+ const kwDict = this.dataStack.pop();
234
+ if (kwDict instanceof AST.ASTConstMap || kwDict instanceof AST.ASTMap) {
235
+ const keys = kwDict.keys || kwDict.m_keys || [];
236
+ const values = kwDict.values || kwDict.m_values || [];
237
+ for (let i = 0; i < Math.min(keys.length, values.length); i++) {
238
+ kwDefArgs.push({name: keys[i], value: values[i]});
239
+ }
240
+ }
241
+ }
168
242
  if (flags & 0x01) {
169
243
  const defaults = this.dataStack.pop();
170
244
  if (defaults instanceof AST.ASTTuple) {
171
245
  defArgs = defaults.values;
246
+ } else if (defaults instanceof AST.ASTObject &&
247
+ (defaults.object?.ClassName === 'Py_Tuple' || defaults.object?.ClassName === 'Py_SmallTuple')) {
248
+ defArgs = (defaults.object.Value || []).map(v => new AST.ASTObject(v));
172
249
  }
173
250
  }
174
- // kwdefaults mapping (rare in these fixtures)
175
- if (flags & 0x02) {
176
- this.dataStack.pop(); // discard kwdefaults for now
177
- }
178
- // annotations handled earlier (0x04)
179
- if (flags & 0x08) {
180
- this.dataStack.pop(); // closure tuple
181
- }
182
- } else {
251
+ } else if (this.object.Reader.versionCompare(3, 0) >= 0) {
252
+ // Python 3.0–3.5 MAKE_FUNCTION/MAKE_CLOSURE uses count-based encoding.
253
+ // arg = (numAnnotations << 16) | (kwDefCount << 8) | defCount
254
+ // CPython push order (compile.c → ceval.c stack from bottom up):
255
+ // [kwdefault_name1, kwdefault_value1, ..., positional_default1, ...,
256
+ // annotation_value1, ..., annotation_names_tuple,
257
+ // closure_tuple (MAKE_CLOSURE only), code, qualname]
258
+ // After qualname & code are already popped at top of processMakeFunction,
259
+ // remaining stack top → bottom:
260
+ // [closure_tuple?, annotation_names_tuple?, annotation_values...,
261
+ // positional_defaults..., kwdefault pairs (name,value)...]
262
+ // Pop order: closure → annotations → positional defaults → kwdefaults.
183
263
  let defCount = this.code.Current.Argument & 0xFF;
184
264
  let kwDefCount = (this.code.Current.Argument >> 8) & 0xFF;
185
- let numAnnotations = (this.code.Current.Argument >> 16) & 0xFF;
186
-
187
- if (this.object.Reader.versionCompare(3, 0) < 0) {
188
- for (let idx = 0; idx < defCount; ++idx) {
189
- defArgs.unshift(this.dataStack.pop());
190
- }
191
-
192
- if (kwDefCount > 0) {
193
- for (let idx = 0; idx < kwDefCount - defCount; ++idx) {
194
- kwDefArgs.unshift(this.dataStack.pop());
195
- }
196
- }
197
- } else {
198
- if (numAnnotations > 0) {
199
- let tuple = this.dataStack.pop();
200
- while (--numAnnotations > 0) {
201
- annotations.push({key: tuple[numAnnotations], value: this.dataStack.pop()})
202
- }
203
- }
265
+ let numAnnotations = (this.code.Current.Argument >> 16) & 0x7FFF;
204
266
 
205
- if (defCount > 0) {
206
- while (defCount-- > 0) {
207
- defArgs.unshift(this.dataStack.pop());
208
- }
209
- }
267
+ if (this.code.Current.OpCodeID === this.OpCodes.MAKE_CLOSURE_A) {
268
+ this.dataStack.pop(); // closure tuple — captured via cellvars elsewhere
269
+ }
210
270
 
211
- if (kwDefCount > 0) {
212
- while (kwDefCount-- > 0) {
213
- let value = this.dataStack.pop();
214
- let name = this.dataStack.pop();
215
- kwDefArgs.unshift({name, value});
216
- }
271
+ if (numAnnotations > 0) {
272
+ const namesTuple = this.dataStack.pop();
273
+ // Names tuple is loaded via LOAD_CONST → ASTObject wrapping Py_Tuple,
274
+ // OR (rare) BUILD_TUPLE → ASTTuple.
275
+ let rawNames = [];
276
+ if (namesTuple instanceof AST.ASTObject &&
277
+ (namesTuple.object?.ClassName === 'Py_Tuple' || namesTuple.object?.ClassName === 'Py_SmallTuple')) {
278
+ rawNames = namesTuple.object.Value || [];
279
+ } else if (namesTuple instanceof AST.ASTTuple) {
280
+ rawNames = namesTuple.values || [];
281
+ } else if (namesTuple?.object?.Value) {
282
+ rawNames = namesTuple.object.Value;
283
+ }
284
+ const names = rawNames.map(n => {
285
+ let s = n?.Value ?? n?.object?.Value ?? n?.name ?? n?.toString?.() ?? '';
286
+ if (typeof s === 'string') s = s.replace(/^['"]|['"]$/g, "");
287
+ return s;
288
+ });
289
+ const valueCount = numAnnotations - 1;
290
+ const values = [];
291
+ for (let i = 0; i < valueCount; i++) {
292
+ values.unshift(this.dataStack.pop());
217
293
  }
294
+ annotationMap = {};
295
+ for (let i = 0; i < Math.min(names.length, values.length); i++) {
296
+ if (names[i]) annotationMap[names[i]] = values[i];
297
+ }
298
+ }
299
+ // Positional defaults are popped BEFORE kwdefaults (they were pushed later).
300
+ for (let idx = 0; idx < defCount; idx++) {
301
+ defArgs.unshift(this.dataStack.pop());
302
+ }
303
+ for (let idx = 0; idx < kwDefCount; idx++) {
304
+ const value = this.dataStack.pop();
305
+ const name = this.dataStack.pop();
306
+ kwDefArgs.unshift({name, value});
307
+ }
308
+ } else {
309
+ // Python 2.x MAKE_FUNCTION: argument is just defCount.
310
+ const defCount = this.code.Current.Argument & 0xFF;
311
+ for (let idx = 0; idx < defCount; idx++) {
312
+ defArgs.unshift(this.dataStack.pop());
218
313
  }
219
314
  }
220
315
 
221
- let node = new AST.ASTFunction (func_code, defArgs, kwDefArgs, annotations);
316
+ let node = new AST.ASTFunction(func_code, defArgs, kwDefArgs);
222
317
  if (annotationMap) {
223
318
  node.annotations = annotationMap;
224
319
  }
@@ -23,6 +23,73 @@ function handleYieldValue() {
23
23
  return;
24
24
  }
25
25
 
26
+ // Generator expression: YIELD_VALUE inside a For block tagged as comprehension
27
+ // produces the genexpr's yielded expression. Mirror LIST_APPEND/SET_ADD pattern:
28
+ // build an ASTComprehension on the stack so the enclosing JUMP_ABSOLUTE can
29
+ // attach generators via addGenerator. Skip the trailing POP_TOP that balances
30
+ // YIELD_VALUE's received-value push in the runtime model.
31
+ // The yield may fire inside nested If blocks (genexpr with filter condition),
32
+ // so walk up through If blocks to find the enclosing For+comprehension.
33
+ let forBlock = null;
34
+ let ifBlocks = [];
35
+ if (this.curBlock.blockType == AST.ASTBlock.BlockType.For && this.curBlock.comprehension) {
36
+ forBlock = this.curBlock;
37
+ } else {
38
+ for (let i = this.blocks.length - 1; i >= 0; i--) {
39
+ const blk = this.blocks[i];
40
+ if (blk.blockType == AST.ASTBlock.BlockType.For && blk.comprehension) {
41
+ forBlock = blk;
42
+ break;
43
+ }
44
+ if (blk.blockType == AST.ASTBlock.BlockType.If) {
45
+ ifBlocks.push(blk);
46
+ } else {
47
+ break;
48
+ }
49
+ }
50
+ }
51
+ if (forBlock) {
52
+ for (const ifBlk of ifBlocks) {
53
+ let cond = ifBlk.condition;
54
+ if (!cond) continue;
55
+ if (ifBlk.negative) {
56
+ cond = new AST.ASTUnary(cond, AST.ASTUnary.UnaryOp.Not);
57
+ cond.line = this.code.Current.LineNo;
58
+ }
59
+ if (forBlock.condition) {
60
+ const andCond = new AST.ASTBinary(forBlock.condition, cond, AST.ASTBinary.BinOp.LogicalAnd);
61
+ andCond.line = this.code.Current.LineNo;
62
+ forBlock.condition = andCond;
63
+ } else {
64
+ forBlock.condition = cond;
65
+ }
66
+ }
67
+ while (this.curBlock !== forBlock && this.blocks.length > 1) {
68
+ this.blocks.pop();
69
+ this.curBlock = this.blocks.top();
70
+ }
71
+ let node = new AST.ASTComprehension(value);
72
+ node.line = this.code.Current.LineNo;
73
+ node.kind = AST.ASTComprehension.GENERATOR;
74
+ this.dataStack.push(node);
75
+ if (this.code.Next?.OpCodeID == this.OpCodes.POP_TOP) {
76
+ this.code.GoNext();
77
+ }
78
+ return;
79
+ }
80
+
81
+ // Async genexpr inner code: GET_ANEXT iteration uses SETUP_EXCEPT/FINALLY
82
+ // rather than FOR_ITER, so no For+comp block exists when YIELD_VALUE fires
83
+ // inside the comprehension body. Save the yielded value so the CALL-site
84
+ // reconstruction in function_calls.js can use it as the comprehension's
85
+ // yield expression. We still append ASTReturn below for regular async
86
+ // generators; the save is harmless because the CALL-site only reads
87
+ // _asyncCompYieldExpr when reconstructing a recognized comp code object.
88
+ const objName = this.object?.Name?.toString?.() || '';
89
+ if (objName.includes('genexpr')) {
90
+ this.object._asyncCompYieldExpr = value;
91
+ }
92
+
26
93
  // Python 3.11+: YIELD_VALUE appears in async for loops as implementation detail
27
94
  // Skip if we're inside AsyncFor block and value looks like generator machinery
28
95
  if (this.object.Reader.versionCompare(3, 11) >= 0) {