depyo 1.2.0 → 1.2.2

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.
@@ -2436,6 +2436,46 @@ class PycDecompiler {
2436
2436
  }
2437
2437
  };
2438
2438
 
2439
+ // CPython 3.6+ `except Foo as X:` lowers to a try/finally cleanup
2440
+ // wrapper around the handler body. The reconstruction sometimes
2441
+ // emits the handler ASTCondBlock twice when the inner cleanup's
2442
+ // END_FINALLY is misread as a second handler match. Collapse
2443
+ // consecutive identical Except blocks (same rendered condition +
2444
+ // body) since real Python source can't legally have them.
2445
+ const dedupeConsecutiveExcepts = (nodes) => {
2446
+ const renderedKey = (n) => {
2447
+ try {
2448
+ const cf = n.codeFragment?.();
2449
+ return typeof cf === 'string' ? cf : (cf?.toString?.() || null);
2450
+ } catch (e) {
2451
+ return null;
2452
+ }
2453
+ };
2454
+ const isExcept = (n) => (n instanceof AST.ASTCondBlock)
2455
+ && n.blockType === AST.ASTBlock.BlockType.Except;
2456
+ // Walk runs of contiguous Except siblings and drop duplicates
2457
+ // (handles both adjacent and non-adjacent duplicates within
2458
+ // the same handler chain — e.g. `except A / except: / except A`).
2459
+ let i = 0;
2460
+ while (i < nodes.length) {
2461
+ if (!isExcept(nodes[i])) { i++; continue; }
2462
+ let j = i;
2463
+ while (j < nodes.length && isExcept(nodes[j])) j++;
2464
+ const seen = new Map();
2465
+ for (let k = i; k < j; k++) {
2466
+ const key = renderedKey(nodes[k]);
2467
+ if (key && seen.has(key)) {
2468
+ nodes.splice(k, 1);
2469
+ j--;
2470
+ k--;
2471
+ } else if (key) {
2472
+ seen.set(key, true);
2473
+ }
2474
+ }
2475
+ i = j;
2476
+ }
2477
+ };
2478
+
2439
2479
  const visited = new WeakSet();
2440
2480
  const visit = (n) => {
2441
2481
  if (!n || visited.has(n)) return;
@@ -2462,6 +2502,10 @@ class PycDecompiler {
2462
2502
  if (parentType === AST.ASTBlock.BlockType.Except) {
2463
2503
  unwrapTryFinallyPassContainer(nodes);
2464
2504
  }
2505
+ // Dedupe at every level (Container, Try, NodeList) since the
2506
+ // duplicate Except siblings can land in any of these depending
2507
+ // on how SETUP_FINALLY/END_FINALLY interleave.
2508
+ dedupeConsecutiveExcepts(nodes);
2465
2509
  }
2466
2510
  };
2467
2511
  visit(root);
@@ -95,6 +95,14 @@ function processListAppend() {
95
95
  let node = new AST.ASTComprehension (value);
96
96
  node.line = this.code.Current.LineNo;
97
97
  this.dataStack.push(node);
98
+ } else if (list instanceof AST.ASTList) {
99
+ // CPython's "big list" path: when a list literal exceeds the
100
+ // STACK_USE_GUIDELINE and not every element is a compile-time
101
+ // constant, the compiler emits BUILD_LIST 0 + N×LIST_APPEND
102
+ // instead of a single BUILD_LIST N. Mutate the list literal in
103
+ // place; the list reference must remain on top of the data stack
104
+ // for the next LIST_APPEND (or the final STORE_*) to find it.
105
+ list.values.push(value);
98
106
  } else {
99
107
  let node = new AST.ASTSubscr (list, value);
100
108
  node.line = this.code.Current.LineNo;
@@ -1177,12 +1177,37 @@ function handleJumpAbsoluteA() {
1177
1177
  this.blocks.push(next);
1178
1178
  prev = null;
1179
1179
  } else if (prev.blockType == AST.ASTBlock.BlockType.Except) {
1180
- let top = this.blocks.top();
1181
- let next = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Except, top.start, top.end, null, false);
1182
- next.init();
1180
+ // After closing one handler we speculatively open a fresh
1181
+ // Except slot in case another handler follows. But if the
1182
+ // chain has no more user-written handlers — only CPython's
1183
+ // synthetic END_FINALLY re-raise terminator before our
1184
+ // jump target — pushing an empty Except here produces a
1185
+ // phantom `except: pass` in the output.
1186
+ let chainTerminated = false;
1187
+ const target = this.code.Current.JumpTarget;
1188
+ const cursorStart = this.code.Next?.Offset;
1189
+ if (target != null && cursorStart != null && cursorStart < target) {
1190
+ let cursor = cursorStart;
1191
+ for (let k = 0; k < 32 && cursor < target; k++) {
1192
+ const instr = this.code.PeekInstructionAtOffset(cursor);
1193
+ if (!instr) break;
1194
+ if (instr.OpCodeID === this.OpCodes.END_FINALLY) {
1195
+ chainTerminated = true;
1196
+ break;
1197
+ }
1198
+ cursor = instr.Offset + (instr.Size || 1);
1199
+ }
1200
+ }
1201
+ if (chainTerminated) {
1202
+ prev = null;
1203
+ } else {
1204
+ let top = this.blocks.top();
1205
+ let next = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Except, top.start, top.end, null, false);
1206
+ next.init();
1183
1207
 
1184
- this.blocks.push(next);
1185
- prev = null;
1208
+ this.blocks.push(next);
1209
+ prev = null;
1210
+ }
1186
1211
  } else if (prev.blockType == AST.ASTBlock.BlockType.Else) {
1187
1212
  /* Special case */
1188
1213
  if (this.blocks.top().blockType != AST.ASTBlock.BlockType.Main) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "depyo",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Python bytecode decompiler (Python 1.0–3.15) implemented in Node.js",
5
5
  "bin": {
6
6
  "depyo": "./depyo.js"