depyo 1.0.2 → 1.1.0

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
  }
@@ -450,7 +477,8 @@ function handlePopExcept() {
450
477
  this.curBlock = this.defBlock || exceptBlock;
451
478
  }
452
479
 
453
- let bodyNode = exceptBlock.nodes;
480
+ // exceptBlock.nodes returns the block's m_nodes array directly (getter with no setter).
481
+ let bodyNodes = exceptBlock.nodes;
454
482
 
455
483
  if (this.curBlock.blockType == AST.ASTBlock.BlockType.Try) {
456
484
  const pendingEgType = this.egMatchTypeStack?.shift?.() || this.pendingEgMatchType;
@@ -475,7 +503,11 @@ function handlePopExcept() {
475
503
  }
476
504
  let catchBlock = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Except, exceptBlock.start, exceptBlock.end, matchType, false);
477
505
  catchBlock.line = exceptBlock.line;
478
- catchBlock.nodes = bodyNode.nodes;
506
+ // Copy the handler body from the popped Except. `nodes` is getter-only,
507
+ // so mutate m_nodes directly — plain assignment here silently no-ops.
508
+ if (Array.isArray(bodyNodes) && bodyNodes.length) {
509
+ catchBlock.m_nodes.push(...bodyNodes);
510
+ }
479
511
  catchBlock.isExceptStar = isExceptStar;
480
512
  if (catchBlock.condition && typeof catchBlock.condition === 'object' && 'src' in catchBlock.condition) {
481
513
  const storeSrc = catchBlock.condition.src;
@@ -563,6 +595,36 @@ function handleEndFinally() {
563
595
  if (!this.curBlock.inited) {
564
596
  console.error("Error when decompiling 'async for'.\n");
565
597
  }
598
+ } else if (asyncForBlock.blockType == AST.ASTBlock.BlockType.AsyncFor && asyncForBlock.inited) {
599
+ // Initialized AsyncFor: STORE_FAST already set the index.
600
+ // Extract the loop body from the Container's try block,
601
+ // close the AsyncFor, and mark remaining handler as unreachable.
602
+ let tryBlock = container.nodes.find(n => n.blockType == AST.ASTBlock.BlockType.Try);
603
+ if (tryBlock && !tryBlock.nodes.empty()) {
604
+ let store = tryBlock.nodes.find(n => n instanceof AST.ASTStore);
605
+ let startIdx = store ? tryBlock.nodes.indexOf(store) + 1 : 0;
606
+ for (let i = startIdx; i < tryBlock.nodes.length; i++) {
607
+ let node = tryBlock.nodes[i];
608
+ if (node && node.constructor.name !== 'ASTReturn' &&
609
+ !(node.constructor.name === 'ASTCall' && node.func?.name === 'await')) {
610
+ asyncForBlock.nodes.push(node);
611
+ }
612
+ }
613
+ if (asyncForBlock.nodes.length === 0) {
614
+ let passNode = new AST.ASTKeyword(AST.ASTKeyword.Word.Pass);
615
+ passNode.line = tryBlock.line;
616
+ asyncForBlock.nodes.push(passNode);
617
+ }
618
+ }
619
+ let asyncForEnd = asyncForBlock.end;
620
+ this.blocks.pop();
621
+ this.blocks.top().append(asyncForBlock);
622
+ this.curBlock = this.blocks.top();
623
+ this.unreachableUntil = asyncForEnd;
624
+ if (global.g_cliArgs?.debug) {
625
+ console.log(`[handleEndFinally-Except] Closed initialized AsyncFor, unreachable until ${asyncForEnd}`);
626
+ }
627
+ return;
566
628
  } else {
567
629
  this.blocks.push(container);
568
630
  }
@@ -782,7 +844,10 @@ function handleRaiseVarargsA() {
782
844
  for (let idx = 0; idx < this.code.Current.Argument; idx++) {
783
845
  paramList.unshift(this.dataStack.pop());
784
846
  }
785
- let node = new AST.ASTRaise(paramList);
847
+ const fromClause =
848
+ this.code.Current.Argument === 2 &&
849
+ this.object.Reader.versionCompare(3, 0) >= 0;
850
+ let node = new AST.ASTRaise(paramList, fromClause);
786
851
  node.line = this.code.Current.LineNo;
787
852
  this.curBlock.append(node);
788
853
 
@@ -919,13 +984,16 @@ function handleCheckEgMatch() {
919
984
  }
920
985
 
921
986
  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
987
+ // CPython 3.11: pops orig + excs list, replaces with None-or-new-EG.
988
+ // The following POP_JUMP_IF_NOT_NONE/SWAP/POP_EXCEPT/RERAISE sequence is
989
+ // compiler-generated plumbing for `except*`, not user code. Mirror the
990
+ // 3.12 CALL_INTRINSIC_2(arg=1) handler: consume stack, suppress the
991
+ // next conditional, mark exception-group context.
992
+ this.dataStack.pop();
993
+ this.dataStack.pop();
994
+ this.ignoreNextConditional = true;
995
+ this.cleanupStackDepth = this.dataStack.length + 1;
996
+ this.dataStack.push(new AST.ASTNone());
929
997
  this.inExceptionGroup = true;
930
998
  }
931
999
 
@@ -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
  }