depyo 1.0.2 → 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.
@@ -132,17 +132,18 @@ function extractWithPatternForFinally() {
132
132
 
133
133
  if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.STORE_FAST_A) {
134
134
  // Has "as variable"
135
- asVariable = new AST.ASTName(thirdInstr.Name?.toString() || "###FIXME###");
135
+ if (thirdInstr.Name == null) {
136
+ throw new Error(`SETUP_FINALLY (Py2.7 WITH) at offset ${this.code.Current.Offset} (file offset ${this.object.codeOffset + this.code.Current.Offset}): STORE_FAST has null Name`);
137
+ }
138
+ asVariable = new AST.ASTName(thirdInstr.Name.toString());
136
139
  } else if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.POP_TOP) {
137
140
  // No "as" clause
138
141
  asVariable = null;
139
142
  } else if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.UNPACK_SEQUENCE_A) {
140
- // Tuple unpacking: with expr as (x, y):
141
- // Pattern: LOAD_FAST, DELETE_FAST, UNPACK_SEQUENCE n, STORE_FAST..., STORE_FAST...
142
- // Use placeholder for tuple unpacking (reconstructing tuple is complex)
143
- asVariable = new AST.ASTName("###FIXME###");
144
- // Skip LOAD_FAST + DELETE_FAST + UNPACK_SEQUENCE + n STORE_FAST instructions
145
- skipCount = 3 + (thirdInstr.Argument || 2);
143
+ // Tuple unpacking: `with expr as (x, y):` — reconstructing the
144
+ // tuple of names from UNPACK_SEQUENCE + STORE_FASTs requires
145
+ // forward instruction collection that this path doesn't yet do.
146
+ throw new Error(`SETUP_FINALLY (Py2.7 WITH) at offset ${this.code.Current.Offset} (file offset ${this.object.codeOffset + this.code.Current.Offset}): tuple-unpack 'as (...)' clauses not yet supported (UNPACK_SEQUENCE_A at offset ${thirdInstr.Offset})`);
146
147
  } else {
147
148
  if (global.g_cliArgs?.debug) {
148
149
  console.log(`WITH pattern (Python 2.7) check failed: third=${thirdInstr?.InstructionName}`);
@@ -228,7 +229,10 @@ function extractWithPatternForFinally() {
228
229
  // Check if this is variable assignment (WITH ... as var:)
229
230
  if (instr.OpCodeID === this.OpCodes.STORE_FAST_A && i < 5) {
230
231
  // Found variable assignment early in sequence
231
- asVariable = new AST.ASTName(instr.Name?.toString() || "###FIXME###");
232
+ if (instr.Name == null) {
233
+ throw new Error(`SETUP_FINALLY (Py3 WITH) at offset ${this.code.Current.Offset} (file offset ${this.object.codeOffset + this.code.Current.Offset}): STORE_FAST at offset ${instr.Offset} has null Name`);
234
+ }
235
+ asVariable = new AST.ASTName(instr.Name.toString());
232
236
  skipCount = i + 1;
233
237
  foundBodyStart = true;
234
238
  if (global.g_cliArgs?.debug) {
@@ -323,10 +327,14 @@ function extractContextManager() {
323
327
  instr.OpCodeID === this.OpCodes.LOAD_GLOBAL_A ||
324
328
  instr.OpCodeID === this.OpCodes.LOAD_NAME_A ||
325
329
  instr.OpCodeID === this.OpCodes.LOAD_DEREF_A) {
326
- ctxMgrExpr = new AST.ASTName(instr.Name?.toString() || "###FIXME###");
330
+ if (instr.Name == null) {
331
+ throw new Error(`extractContextManager: ${instr.InstructionName} at offset ${instr.Offset} has null Name`);
332
+ }
333
+ ctxMgrExpr = new AST.ASTName(instr.Name.toString());
327
334
  } else {
328
- // For CALL_FUNCTION, LOAD_ATTR, etc., use placeholder
329
- ctxMgrExpr = new AST.ASTName("###FIXME###");
335
+ // CALL_FUNCTION/LOAD_ATTR/BINARY_SUBSCR: full expression
336
+ // reconstruction needs stack replay (not implemented here).
337
+ throw new Error(`extractContextManager: unsupported context manager opcode ${instr.InstructionName} at offset ${instr.Offset} — needs stack replay`);
330
338
  }
331
339
  break;
332
340
  }
@@ -353,10 +361,25 @@ function handlePopBlock() {
353
361
  return;
354
362
  }
355
363
 
356
- if (this.curBlock.blockType == AST.ASTBlock.BlockType.With) {
364
+ if (this.curBlock.blockType == AST.ASTBlock.BlockType.With
365
+ || this.curBlock.blockType == AST.ASTBlock.BlockType.AsyncWith) {
357
366
  // This should only be popped by a WITH_CLEANUP
358
367
  if (global.g_cliArgs?.debug) {
359
- console.log(`POP_BLOCK on WITH block ignored at offset ${this.code.Current.Offset}`);
368
+ console.log(`POP_BLOCK on ${this.curBlock.type_str.toUpperCase()} block ignored at offset ${this.code.Current.Offset}`);
369
+ }
370
+ return;
371
+ }
372
+
373
+ // Py2.x nested genexpr/comprehension: inner loop emits SETUP_LOOP/POP_BLOCK
374
+ // around FOR_ITER/JUMP_ABSOLUTE. FOR_ITER consumes the SETUP_LOOP's While and
375
+ // JUMP_ABSOLUTE pops the resulting For, leaving this POP_BLOCK phantom. If it
376
+ // runs here it pops the enclosing For+comprehension prematurely and orphans
377
+ // the ASTComprehension sitting on the data stack.
378
+ if (this.curBlock.blockType == AST.ASTBlock.BlockType.For
379
+ && this.curBlock.comprehension
380
+ && this.dataStack.top() instanceof AST.ASTComprehension) {
381
+ if (global.g_cliArgs?.debug) {
382
+ console.log(`POP_BLOCK ignored on For+comprehension at offset ${this.code.Current.Offset} (ASTComprehension on stack)`);
360
383
  }
361
384
  return;
362
385
  }
@@ -413,7 +436,11 @@ function handlePopBlock() {
413
436
  (tmp.blockType == AST.ASTBlock.BlockType.Try && !this.curBlock.hasExcept)
414
437
  ) {
415
438
 
416
- let final = new AST.ASTBlock(AST.ASTBlock.BlockType.Finally, tmp.start, tmp.end, true);
439
+ // The Finally body actually starts at container.finally (the SETUP_FINALLY
440
+ // jump target), not at tmp.start which inherits the popped Else/Try offsets.
441
+ // End is left as 0 so closeEndedBlocks doesn't pop it before END_FINALLY runs.
442
+ let finalStart = this.curBlock.finally || tmp.start;
443
+ let final = new AST.ASTBlock(AST.ASTBlock.BlockType.Finally, finalStart, 0, true);
417
444
  this.blocks.push(final);
418
445
  this.curBlock = this.blocks.top();
419
446
  }
@@ -563,6 +590,36 @@ function handleEndFinally() {
563
590
  if (!this.curBlock.inited) {
564
591
  console.error("Error when decompiling 'async for'.\n");
565
592
  }
593
+ } else if (asyncForBlock.blockType == AST.ASTBlock.BlockType.AsyncFor && asyncForBlock.inited) {
594
+ // Initialized AsyncFor: STORE_FAST already set the index.
595
+ // Extract the loop body from the Container's try block,
596
+ // close the AsyncFor, and mark remaining handler as unreachable.
597
+ let tryBlock = container.nodes.find(n => n.blockType == AST.ASTBlock.BlockType.Try);
598
+ if (tryBlock && !tryBlock.nodes.empty()) {
599
+ let store = tryBlock.nodes.find(n => n instanceof AST.ASTStore);
600
+ let startIdx = store ? tryBlock.nodes.indexOf(store) + 1 : 0;
601
+ for (let i = startIdx; i < tryBlock.nodes.length; i++) {
602
+ let node = tryBlock.nodes[i];
603
+ if (node && node.constructor.name !== 'ASTReturn' &&
604
+ !(node.constructor.name === 'ASTCall' && node.func?.name === 'await')) {
605
+ asyncForBlock.nodes.push(node);
606
+ }
607
+ }
608
+ if (asyncForBlock.nodes.length === 0) {
609
+ let passNode = new AST.ASTKeyword(AST.ASTKeyword.Word.Pass);
610
+ passNode.line = tryBlock.line;
611
+ asyncForBlock.nodes.push(passNode);
612
+ }
613
+ }
614
+ let asyncForEnd = asyncForBlock.end;
615
+ this.blocks.pop();
616
+ this.blocks.top().append(asyncForBlock);
617
+ this.curBlock = this.blocks.top();
618
+ this.unreachableUntil = asyncForEnd;
619
+ if (global.g_cliArgs?.debug) {
620
+ console.log(`[handleEndFinally-Except] Closed initialized AsyncFor, unreachable until ${asyncForEnd}`);
621
+ }
622
+ return;
566
623
  } else {
567
624
  this.blocks.push(container);
568
625
  }
@@ -782,7 +839,10 @@ function handleRaiseVarargsA() {
782
839
  for (let idx = 0; idx < this.code.Current.Argument; idx++) {
783
840
  paramList.unshift(this.dataStack.pop());
784
841
  }
785
- let node = new AST.ASTRaise(paramList);
842
+ const fromClause =
843
+ this.code.Current.Argument === 2 &&
844
+ this.object.Reader.versionCompare(3, 0) >= 0;
845
+ let node = new AST.ASTRaise(paramList, fromClause);
786
846
  node.line = this.code.Current.LineNo;
787
847
  this.curBlock.append(node);
788
848
 
@@ -919,13 +979,16 @@ function handleCheckEgMatch() {
919
979
  }
920
980
 
921
981
  function handlePrepReraiseStar() {
922
- // PREP_RERAISE_STAR separates matched/unmatched parts. Represent as helper call.
923
- let value = this.dataStack.pop();
924
- let helper = new AST.ASTCall(new AST.ASTName("__prep_reraise_star__"), [value], []);
925
- helper.line = this.code.Current.LineNo;
926
- this.dataStack.push(helper);
927
-
928
- // Maintain group context for subsequent handlers
982
+ // CPython 3.11: pops orig + excs list, replaces with None-or-new-EG.
983
+ // The following POP_JUMP_IF_NOT_NONE/SWAP/POP_EXCEPT/RERAISE sequence is
984
+ // compiler-generated plumbing for `except*`, not user code. Mirror the
985
+ // 3.12 CALL_INTRINSIC_2(arg=1) handler: consume stack, suppress the
986
+ // next conditional, mark exception-group context.
987
+ this.dataStack.pop();
988
+ this.dataStack.pop();
989
+ this.ignoreNextConditional = true;
990
+ this.cleanupStackDepth = this.dataStack.length + 1;
991
+ this.dataStack.push(new AST.ASTNone());
929
992
  this.inExceptionGroup = true;
930
993
  }
931
994
 
@@ -1,26 +1,69 @@
1
1
  const AST = require('../ast/ast_node');
2
2
 
3
- function handleBuildTemplate() {
4
- // Python 3.14 BUILD_TEMPLATE: template objects for f-strings; keep stack as-is (operands already handled by formatting ops).
5
- const count = this.code.Current.Argument || 0;
6
- const items = [];
7
- for (let i = 0; i < count; i++) {
8
- items.unshift(this.dataStack.pop());
3
+ // PEP 750 t-strings (Python 3.14+):
4
+ // strings_tuple ; (value ; str_repr ; [format_spec])+ ; BUILD_INTERPOLATION oparg
5
+ // ... ; BUILD_TUPLE N ; BUILD_TEMPLATE -> Template
6
+ // BUILD_INTERPOLATION oparg layout:
7
+ // oparg & 3 = pop count (2 = no format spec, 3 = has format spec)
8
+ // (oparg >> 2) & 3 = conversion (0=none, 1=!s, 2=!r, 3=!a)
9
+ function handleBuildInterpolationA() {
10
+ const oparg = this.code.Current.Argument || 0;
11
+ const popCount = oparg & 3;
12
+ const convCode = (oparg >> 2) & 3;
13
+
14
+ let formatSpec = null;
15
+ if (popCount === 3) {
16
+ formatSpec = this.dataStack.pop();
9
17
  }
10
- // Re-push items as a tuple-like collection to preserve stack shape.
11
- const tpl = new AST.ASTTuple(items);
12
- tpl.requireParens = false;
13
- this.dataStack.push(tpl);
18
+ this.dataStack.pop(); // discard str representation (used at runtime)
19
+ const value = this.dataStack.pop();
20
+
21
+ const conversion = convCode === 0 ? AST.ASTFormattedValue.ConversionFlag.None
22
+ : convCode === 1 ? AST.ASTFormattedValue.ConversionFlag.Str
23
+ : convCode === 2 ? AST.ASTFormattedValue.ConversionFlag.Repr
24
+ : AST.ASTFormattedValue.ConversionFlag.ASCII;
25
+
26
+ const node = new AST.ASTFormattedValue(value, conversion, formatSpec);
27
+ node.line = this.code.Current.LineNo;
28
+ this.dataStack.push(node);
14
29
  }
15
30
 
16
- function handleBuildInterpolationA() {
17
- // Python 3.14 BUILD_INTERPOLATION: build f-string pieces (similar to BUILD_STRING).
18
- const count = this.code.Current.Argument || 0;
19
- const values = [];
20
- for (let i = 0; i < count; i++) {
21
- values.push(this.dataStack.pop());
31
+ function handleBuildTemplate() {
32
+ // Pops (strings_tuple, interpolations_tuple) and produces a Template node.
33
+ const interpolations = this.dataStack.pop();
34
+ const strings = this.dataStack.pop();
35
+
36
+ let interpVals = [];
37
+ if (interpolations instanceof AST.ASTTuple) {
38
+ interpVals = interpolations.values || [];
39
+ } else if (interpolations?.object?.Value) {
40
+ interpVals = interpolations.object.Value;
41
+ }
42
+
43
+ // strings is loaded via LOAD_CONST as a Py_Tuple of Py_String objects
44
+ let stringVals = [];
45
+ if (strings instanceof AST.ASTTuple) {
46
+ stringVals = strings.values || [];
47
+ } else if (strings?.object?.Value) {
48
+ // Wrap each Py_String in an ASTObject so ASTJoinedStr.codeFragment can render it.
49
+ stringVals = strings.object.Value.map(pyStr => new AST.ASTObject(pyStr));
22
50
  }
23
- const joined = new AST.ASTJoinedStr(values);
51
+
52
+ // Interleave: str[0], interp[0], str[1], interp[1], ..., str[N]
53
+ const pieces = [];
54
+ for (let i = 0; i < stringVals.length; i++) {
55
+ if (stringVals[i]) {
56
+ pieces.push(stringVals[i]);
57
+ }
58
+ if (i < interpVals.length) {
59
+ pieces.push(interpVals[i]);
60
+ }
61
+ }
62
+
63
+ // ASTJoinedStr's codeFragment reverses (BUILD_STRING pops in reverse). Compensate.
64
+ pieces.reverse();
65
+ const joined = new AST.ASTJoinedStr(pieces);
66
+ joined.isTemplateString = true;
24
67
  joined.line = this.code.Current.LineNo;
25
68
  this.dataStack.push(joined);
26
69
  }