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.
- package/depyo.js +43 -2
- package/lib/OpCodes.js +22 -5
- package/lib/PycDecompiler.js +1050 -40
- package/lib/PycDisassembler.js +1 -1
- package/lib/PycReader.js +22 -3
- package/lib/PythonObject.js +42 -6
- package/lib/ast/ast_node.js +381 -88
- package/lib/bytecode/python_3_0.js +1 -1
- package/lib/bytecode/python_3_12.js +1 -1
- package/lib/bytecode/python_3_13.js +13 -13
- package/lib/bytecode/python_3_14.js +13 -13
- package/lib/bytecode/python_3_15.js +183 -0
- package/lib/code_reader.js +107 -146
- package/lib/handlers/collections_update.js +50 -1
- package/lib/handlers/comparisons.js +3 -10
- package/lib/handlers/context_managers.js +202 -13
- package/lib/handlers/control_flow_jumps.js +516 -23
- package/lib/handlers/exceptions_blocks.js +92 -24
- package/lib/handlers/formatting.js +60 -17
- package/lib/handlers/function_calls.js +474 -58
- package/lib/handlers/function_class_build.js +170 -65
- package/lib/handlers/generators_async.js +67 -0
- package/lib/handlers/load_store_names.js +190 -57
- package/lib/handlers/loop_iterator.js +162 -6
- package/lib/handlers/misc_other.js +253 -44
- package/lib/handlers/stack_ops.js +81 -19
- package/lib/handlers/subscript_slice.js +103 -1
- package/lib/handlers/unpack.js +18 -16
- package/package.json +2 -2
|
@@ -106,7 +106,8 @@ function reconstructPattern(patternOps) {
|
|
|
106
106
|
let attrOpsStart = ops.indexOf(matchClassOp) + 1;
|
|
107
107
|
let attrOps = ops.slice(attrOpsStart);
|
|
108
108
|
const unpackIndex = attrOps.findIndex(op => op.type === 'UNPACK_SEQUENCE');
|
|
109
|
-
|
|
109
|
+
const hasUnpack = unpackIndex >= 0;
|
|
110
|
+
if (hasUnpack) {
|
|
110
111
|
attrOps = attrOps.slice(unpackIndex + 1);
|
|
111
112
|
if (unpackOp) {
|
|
112
113
|
markConsumed(unpackOp);
|
|
@@ -121,47 +122,107 @@ function reconstructPattern(patternOps) {
|
|
|
121
122
|
attrOps = attrOps.slice(1);
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
125
|
+
// Two bytecode shapes for class-pattern attribute extraction:
|
|
126
|
+
//
|
|
127
|
+
// (A) 3.11+ UNPACK_SEQUENCE shape — each attribute emits either a
|
|
128
|
+
// COMPARE (literal) or STORE_FAST (binding) inline at its
|
|
129
|
+
// position. SWAP 2 indicates reverseOrder across all attrs.
|
|
130
|
+
//
|
|
131
|
+
// (B) 3.10 DUP_TOP/ROT_TWO shape — BIND_STASH marks a binding
|
|
132
|
+
// position; its STORE_FAST appears later, after any subsequent
|
|
133
|
+
// COMPAREs. STORE_FASTs are FIFO-matched to BIND_STASHes.
|
|
134
|
+
if (hasUnpack) {
|
|
135
|
+
const attributeOps = [];
|
|
136
|
+
for (const op of attrOps) {
|
|
137
|
+
if (op.type === 'COMPARE' || op.type === 'STORE_FAST') {
|
|
138
|
+
attributeOps.push(op);
|
|
139
|
+
markConsumed(op);
|
|
140
|
+
}
|
|
141
|
+
if (attrCount && attributeOps.length >= attrCount) {
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
133
144
|
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (reverseOrder) {
|
|
137
|
-
attributeOps.reverse();
|
|
138
|
-
}
|
|
139
145
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (attrCount && attrIndex >= attrCount) {
|
|
143
|
-
break;
|
|
146
|
+
if (reverseOrder) {
|
|
147
|
+
attributeOps.reverse();
|
|
144
148
|
}
|
|
145
149
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
let attrIndex = 0;
|
|
151
|
+
for (const op of attributeOps) {
|
|
152
|
+
if (attrCount && attrIndex >= attrCount) {
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
151
155
|
const name = attrNames.length ? (attrNames[attrIndex] || `_attr${attrIndex}`) : null;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
if (op.type === 'COMPARE') {
|
|
157
|
+
attributes.push({
|
|
158
|
+
name,
|
|
159
|
+
pattern: new AST.ASTPattern(AST.ASTPattern.PatternType.Literal, op.right)
|
|
160
|
+
});
|
|
161
|
+
} else {
|
|
162
|
+
attributes.push({
|
|
163
|
+
name,
|
|
164
|
+
pattern: new AST.ASTPattern(AST.ASTPattern.PatternType.Variable, op.name)
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
attrIndex++;
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
const inlineOps = []; // COMPARE or BIND_STASH in position order
|
|
171
|
+
const storeOps = []; // Trailing STORE_FAST queue, FIFO to stashes
|
|
172
|
+
for (const op of attrOps) {
|
|
173
|
+
if (op.type === 'COMPARE' || op.type === 'BIND_STASH') {
|
|
174
|
+
if (!attrCount || inlineOps.length < attrCount) {
|
|
175
|
+
inlineOps.push(op);
|
|
176
|
+
markConsumed(op);
|
|
177
|
+
}
|
|
178
|
+
} else if (op.type === 'STORE_FAST') {
|
|
179
|
+
storeOps.push(op);
|
|
180
|
+
markConsumed(op);
|
|
181
|
+
}
|
|
160
182
|
}
|
|
161
183
|
|
|
162
|
-
|
|
184
|
+
// Legacy fixtures (no BIND_STASH recorded) fall back to inline
|
|
185
|
+
// STORE_FAST handling so pre-existing Case-4-style snapshots don't
|
|
186
|
+
// regress.
|
|
187
|
+
if (inlineOps.length === 0 && storeOps.length > 0) {
|
|
188
|
+
const attributeOps = storeOps;
|
|
189
|
+
let attrIndex = 0;
|
|
190
|
+
for (const op of attributeOps) {
|
|
191
|
+
if (attrCount && attrIndex >= attrCount) break;
|
|
192
|
+
const name = attrNames.length ? (attrNames[attrIndex] || op.name || `_attr${attrIndex}`) : null;
|
|
193
|
+
attributes.push({
|
|
194
|
+
name,
|
|
195
|
+
pattern: new AST.ASTPattern(AST.ASTPattern.PatternType.Variable, op.name)
|
|
196
|
+
});
|
|
197
|
+
attrIndex++;
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
let stashIdx = 0;
|
|
201
|
+
let attrIndex = 0;
|
|
202
|
+
for (const op of inlineOps) {
|
|
203
|
+
if (attrCount && attrIndex >= attrCount) break;
|
|
204
|
+
const name = attrNames.length ? (attrNames[attrIndex] || `_attr${attrIndex}`) : null;
|
|
205
|
+
if (op.type === 'COMPARE') {
|
|
206
|
+
attributes.push({
|
|
207
|
+
name,
|
|
208
|
+
pattern: new AST.ASTPattern(AST.ASTPattern.PatternType.Literal, op.right)
|
|
209
|
+
});
|
|
210
|
+
} else {
|
|
211
|
+
const storeOp = storeOps[stashIdx++];
|
|
212
|
+
attributes.push({
|
|
213
|
+
name,
|
|
214
|
+
pattern: storeOp
|
|
215
|
+
? new AST.ASTPattern(AST.ASTPattern.PatternType.Variable, storeOp.name)
|
|
216
|
+
: new AST.ASTPattern(AST.ASTPattern.PatternType.Wildcard, '_')
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
attrIndex++;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
163
222
|
}
|
|
164
223
|
|
|
224
|
+
let attrIndex = attributes.length;
|
|
225
|
+
|
|
165
226
|
// If there are declared attributes without recorded ops, fill them with wildcards
|
|
166
227
|
while (attrIndex < attrNames.length) {
|
|
167
228
|
attributes.push({
|
|
@@ -521,7 +582,10 @@ function hasUpcomingStringBuild(context) {
|
|
|
521
582
|
context.OpCodes.STORE_DEREF_A
|
|
522
583
|
]);
|
|
523
584
|
|
|
524
|
-
|
|
585
|
+
// 3.11+ inline caches inflate the physical instruction stream; a single
|
|
586
|
+
// f-string with a few interpolations easily spans 30+ code slots before
|
|
587
|
+
// reaching BUILD_STRING. Cap generously and rely on earlyStops to bound.
|
|
588
|
+
for (let step = 1; step <= 48; step++) {
|
|
525
589
|
const instr = context.code.PeekInstructionAtOffset(context.code.Current.Offset + step * 2);
|
|
526
590
|
if (!instr) {
|
|
527
591
|
break;
|
|
@@ -603,6 +667,27 @@ function handlePopTop() {
|
|
|
603
667
|
this.OpCodes.JUMP_IF_FALSE_A
|
|
604
668
|
].includes(this.code.Prev?.OpCodeID);
|
|
605
669
|
|
|
670
|
+
// A POP_TOP following ROT_TWO/ROT_THREE/ROT_FOUR is part of binding-extraction
|
|
671
|
+
// stack cleanup inside a class/sequence pattern, not the body-start POP_TOP.
|
|
672
|
+
let prevIsRot = [
|
|
673
|
+
this.OpCodes.ROT_TWO,
|
|
674
|
+
this.OpCodes.ROT_THREE,
|
|
675
|
+
this.OpCodes.ROT_FOUR
|
|
676
|
+
].includes(this.code.Prev?.OpCodeID);
|
|
677
|
+
|
|
678
|
+
// If the next non-CACHE opcode is STORE_FAST/STORE_NAME, this POP_TOP is mid-pattern
|
|
679
|
+
// binding cleanup (pops the match result tuple before the remaining STORE captures).
|
|
680
|
+
let nextIsStoreBinding = (() => {
|
|
681
|
+
let scan = this.code.Next;
|
|
682
|
+
let steps = 0;
|
|
683
|
+
while (scan && steps < 4) {
|
|
684
|
+
if (scan.OpCodeID == this.OpCodes.CACHE) { scan = scan.Next; steps++; continue; }
|
|
685
|
+
return scan.OpCodeID == this.OpCodes.STORE_FAST_A ||
|
|
686
|
+
scan.OpCodeID == this.OpCodes.STORE_NAME_A;
|
|
687
|
+
}
|
|
688
|
+
return false;
|
|
689
|
+
})();
|
|
690
|
+
|
|
606
691
|
const patternHasOps = (this.patternOps?.length || 0) > 0;
|
|
607
692
|
let isLiteralPattern = patternHasOps && !this.patternOps.some(op => ["MATCH_SEQUENCE","MATCH_CLASS","MATCH_MAPPING","MATCH_KEYS"].includes(op.type));
|
|
608
693
|
let shouldStartCase = readyForWildcard || !!this.inMatchPattern;
|
|
@@ -612,6 +697,41 @@ function handlePopTop() {
|
|
|
612
697
|
if (patternHasOps && isLiteralPattern && !prevIsJump) {
|
|
613
698
|
shouldStartCase = false;
|
|
614
699
|
}
|
|
700
|
+
if (patternHasOps && prevIsRot) {
|
|
701
|
+
shouldStartCase = false;
|
|
702
|
+
}
|
|
703
|
+
if (patternHasOps && nextIsStoreBinding) {
|
|
704
|
+
shouldStartCase = false;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// When readyForWildcard fires on the match-exit POP_TOP and the only
|
|
708
|
+
// code that follows is the function's implicit `return None` tail,
|
|
709
|
+
// the source didn't actually include a `case _:` clause — CPython
|
|
710
|
+
// emits the tail unconditionally. Suppress the phantom wildcard.
|
|
711
|
+
if (readyForWildcard && !patternHasOps) {
|
|
712
|
+
let scan = this.code.Next;
|
|
713
|
+
let steps = 0;
|
|
714
|
+
let sawLoadNone = false;
|
|
715
|
+
while (scan && steps < 6) {
|
|
716
|
+
const op = scan.OpCodeID;
|
|
717
|
+
if (op == this.OpCodes.CACHE ||
|
|
718
|
+
op == this.OpCodes.RESUME_A ||
|
|
719
|
+
op == this.OpCodes.NOP) {
|
|
720
|
+
scan = scan.Next; steps++; continue;
|
|
721
|
+
}
|
|
722
|
+
if (op == this.OpCodes.LOAD_CONST_A && !sawLoadNone) {
|
|
723
|
+
sawLoadNone = true;
|
|
724
|
+
scan = scan.Next; steps++; continue;
|
|
725
|
+
}
|
|
726
|
+
if (op == this.OpCodes.RETURN_VALUE || op == this.OpCodes.RETURN_CONST_A) {
|
|
727
|
+
if (sawLoadNone || op == this.OpCodes.RETURN_CONST_A) {
|
|
728
|
+
shouldStartCase = false;
|
|
729
|
+
}
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
break;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
615
735
|
|
|
616
736
|
if (shouldStartCase) {
|
|
617
737
|
if (beginMatchCaseFromPattern.call(this, {reason: 'pop_top'})) {
|
|
@@ -628,7 +748,29 @@ function handlePopTop() {
|
|
|
628
748
|
return;
|
|
629
749
|
}
|
|
630
750
|
|
|
631
|
-
|
|
751
|
+
// Mid-UNPACK_SEQUENCE POP_TOP: CPython emits `a, _, b = tup` as
|
|
752
|
+
// UNPACK 3 / STORE a / POP_TOP / STORE b, where POP_TOP drops the
|
|
753
|
+
// middle slot. Our unpack-state-machine accumulates STORE targets
|
|
754
|
+
// into a tuple; treat POP_TOP as an anonymous `_` slot so the
|
|
755
|
+
// remaining STORE_FAST still sees the tuple on the stack.
|
|
756
|
+
if (this.unpack > 0) {
|
|
757
|
+
let tupleNode = this.dataStack.top();
|
|
758
|
+
if (tupleNode instanceof AST.ASTTuple) {
|
|
759
|
+
let placeholder = new AST.ASTName("_");
|
|
760
|
+
if (this.starPos-- == 0) placeholder.name = "*_";
|
|
761
|
+
tupleNode.add(placeholder);
|
|
762
|
+
if (--this.unpack <= 0) {
|
|
763
|
+
this.dataStack.pop();
|
|
764
|
+
let seqNode = this.dataStack.pop();
|
|
765
|
+
let node = new AST.ASTStore(seqNode, tupleNode);
|
|
766
|
+
node.line = this.code.Current.LineNo;
|
|
767
|
+
this.curBlock.append(node);
|
|
768
|
+
}
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if ([this.OpCodes.JUMP_ABSOLUTE_A, this.OpCodes.JUMP_FORWARD_A, this.OpCodes.POP_JUMP_IF_FALSE_A, this.OpCodes.JUMP_IF_FALSE_A].includes(this.code.Prev?.OpCodeID)) {
|
|
632
774
|
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Except) {
|
|
633
775
|
// Skipping POP_TOP, POP_TOP, POP_TOP
|
|
634
776
|
if ([this.OpCodes.JUMP_ABSOLUTE_A, this.OpCodes.JUMP_FORWARD_A].includes(this.code.Prev.OpCodeID)) {
|
|
@@ -685,7 +827,13 @@ function handlePopTop() {
|
|
|
685
827
|
}
|
|
686
828
|
if (!this.curBlock.inited) {
|
|
687
829
|
if (this.curBlock.blockType == AST.ASTBlock.BlockType.With) {
|
|
830
|
+
// POP_TOP initializing a WITH block consumes the context-manager
|
|
831
|
+
// expression off the stack (Py3.0+ `with EXPR:` with no `as`).
|
|
832
|
+
// The value IS the with-expr, not a body statement — return before
|
|
833
|
+
// the fall-through append would add a phantom duplicate.
|
|
688
834
|
this.curBlock.expr = value;
|
|
835
|
+
this.curBlock.init();
|
|
836
|
+
return;
|
|
689
837
|
} else if (this.curBlock.blockType == AST.ASTBlock.BlockType.If && !this.curBlock.condition) {
|
|
690
838
|
this.curBlock.condition = value;
|
|
691
839
|
}
|
|
@@ -702,17 +850,59 @@ function handlePopTop() {
|
|
|
702
850
|
this.curBlock.append(value);
|
|
703
851
|
}
|
|
704
852
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
853
|
+
/* Python 2.3 listcomp body uses `_[1](x)` (the cached list.append call)
|
|
854
|
+
* instead of LIST_APPEND. Detect this call, whether emitted directly in
|
|
855
|
+
* a For+comp body or nested inside if-filters (`[x for x in y if c]`),
|
|
856
|
+
* and materialize an ASTComprehension instead of leaving the call as a
|
|
857
|
+
* bogus body statement. */
|
|
858
|
+
if (value instanceof AST.ASTCall) {
|
|
859
|
+
let forBlock = null;
|
|
860
|
+
let ifBlocks = [];
|
|
861
|
+
for (let i = this.blocks.length - 1; i >= 0; i--) {
|
|
862
|
+
const blk = this.blocks[i];
|
|
863
|
+
if (blk.blockType == AST.ASTBlock.BlockType.For && blk.comprehension) {
|
|
864
|
+
forBlock = blk;
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
if (blk.blockType == AST.ASTBlock.BlockType.If) {
|
|
868
|
+
ifBlocks.push(blk);
|
|
869
|
+
} else {
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const funcIsCompAppend = forBlock && value.func &&
|
|
875
|
+
this._listCompAppendRefs &&
|
|
876
|
+
Object.values(this._listCompAppendRefs).includes(value.func);
|
|
877
|
+
const isDirectCompBody = forBlock && this.curBlock === forBlock;
|
|
878
|
+
|
|
879
|
+
if (funcIsCompAppend || isDirectCompBody) {
|
|
712
880
|
let pparams = value.pparams;
|
|
713
881
|
if (!pparams.empty()) {
|
|
882
|
+
if (funcIsCompAppend && this.curBlock !== forBlock) {
|
|
883
|
+
for (const ifBlk of ifBlocks) {
|
|
884
|
+
let cond = ifBlk.condition;
|
|
885
|
+
if (!cond) continue;
|
|
886
|
+
if (ifBlk.negative) {
|
|
887
|
+
const notCond = new AST.ASTUnary(cond, AST.ASTUnary.UnaryOp.Not);
|
|
888
|
+
notCond.line = this.code.Current.LineNo;
|
|
889
|
+
cond = notCond;
|
|
890
|
+
}
|
|
891
|
+
if (forBlock.condition) {
|
|
892
|
+
const andCond = new AST.ASTBinary(forBlock.condition, cond, AST.ASTBinary.BinOp.LogicalAnd);
|
|
893
|
+
andCond.line = this.code.Current.LineNo;
|
|
894
|
+
forBlock.condition = andCond;
|
|
895
|
+
} else {
|
|
896
|
+
forBlock.condition = cond;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
while (this.curBlock !== forBlock && this.blocks.length > 1) {
|
|
900
|
+
this.blocks.pop();
|
|
901
|
+
this.curBlock = this.blocks.top();
|
|
902
|
+
}
|
|
903
|
+
}
|
|
714
904
|
let res = pparams[0];
|
|
715
|
-
let node = new AST.ASTComprehension
|
|
905
|
+
let node = new AST.ASTComprehension(res);
|
|
716
906
|
node.line = this.code.Current.LineNo;
|
|
717
907
|
this.dataStack.push(node);
|
|
718
908
|
}
|
|
@@ -809,7 +999,15 @@ function handleReturnValue() {
|
|
|
809
999
|
// Only start new case if no current case AND (not in pattern OR has unflushed pattern ops)
|
|
810
1000
|
const hasUnflushedPattern = (this.patternOps?.length || 0) > 0;
|
|
811
1001
|
const needsNewCase = !this.currentCase && (!this.inMatchPattern || hasUnflushedPattern);
|
|
812
|
-
|
|
1002
|
+
// Suppress phantom wildcard when this is the function-tail implicit
|
|
1003
|
+
// return None: no pattern ops recorded, LOAD_CONST None precedes us,
|
|
1004
|
+
// and this is the last RETURN_VALUE in the code object. CPython
|
|
1005
|
+
// emits the tail whether or not source had `case _:`.
|
|
1006
|
+
const topIsNone = this.dataStack.top() instanceof AST.ASTNone ||
|
|
1007
|
+
(this.dataStack.top() instanceof AST.ASTObject &&
|
|
1008
|
+
this.dataStack.top().obj?.ClassName === 'Py_None');
|
|
1009
|
+
const isImplicitTail = !hasUnflushedPattern && topIsNone && !this.code.Next;
|
|
1010
|
+
if (needsNewCase && !isImplicitTail) {
|
|
813
1011
|
beginMatchCaseFromPattern.call(this, {reason: 'return'});
|
|
814
1012
|
}
|
|
815
1013
|
}
|
|
@@ -938,7 +1136,15 @@ function handleReturnConstA() {
|
|
|
938
1136
|
nextOp.OpCodeID === this.OpCodes.RERAISE;
|
|
939
1137
|
const prevIsPOPExcept = this.code.Prev?.OpCodeID === this.OpCodes.POP_EXCEPT;
|
|
940
1138
|
const nextIsSWAP = nextOp?.OpCodeID === this.OpCodes.SWAP_A;
|
|
1139
|
+
const nextIsRERAISE = nextOp?.OpCodeID === this.OpCodes.RERAISE ||
|
|
1140
|
+
nextOp?.OpCodeID === this.OpCodes.RERAISE_A;
|
|
941
1141
|
const isExceptCleanup = isNone && prevIsPOPExcept && nextIsSWAP;
|
|
1142
|
+
// CPython 3.11+ places the function's implicit `return None` at the tail of an
|
|
1143
|
+
// except handler (POP_EXCEPT → RETURN_CONST None → RERAISE or end-of-function).
|
|
1144
|
+
// The source has no explicit return there; drop it so the handler body doesn't
|
|
1145
|
+
// inherit a phantom return.
|
|
1146
|
+
const isExceptTailImplicitReturn = isNone && prevIsPOPExcept &&
|
|
1147
|
+
(nextIsRERAISE || !nextOp);
|
|
942
1148
|
|
|
943
1149
|
if (isNone && isModuleLevel && isAtEnd) {
|
|
944
1150
|
return; // Skip implicit return None at module end
|
|
@@ -946,6 +1152,9 @@ function handleReturnConstA() {
|
|
|
946
1152
|
if (isExceptCleanup) {
|
|
947
1153
|
return; // Skip return None in exception cleanup (except* success path)
|
|
948
1154
|
}
|
|
1155
|
+
if (isExceptTailImplicitReturn) {
|
|
1156
|
+
return; // Skip synthetic return None at end of except handler
|
|
1157
|
+
}
|
|
949
1158
|
|
|
950
1159
|
let value = new AST.ASTObject(this.code.Current.ConstantObject);
|
|
951
1160
|
let node = new AST.ASTReturn(value);
|
|
@@ -86,17 +86,58 @@ function handleDupTop() {
|
|
|
86
86
|
nextOpCode == this.OpCodes.STORE_FAST_A ||
|
|
87
87
|
nextOpCode == this.OpCodes.STORE_GLOBAL_A);
|
|
88
88
|
|
|
89
|
+
// Pre-3.8 list/set/dict comprehensions compile to
|
|
90
|
+
// BUILD_LIST 0 / DUP_TOP / STORE_NAME _[N] / ... / LIST_APPEND / ...
|
|
91
|
+
// which shares the DUP→STORE→non-STORE shape with walrus. `_[N]` is
|
|
92
|
+
// CPython's synthetic append slot, never a real identifier, so the
|
|
93
|
+
// STORE is swallowed (see processStore) and the DUP must be too —
|
|
94
|
+
// otherwise the walrus branch leaks isWalrusOperator to the next STORE
|
|
95
|
+
// (the FOR_ITER's iteration variable), stealing its valueNode.
|
|
96
|
+
let nextName = this.code.Next?.Name || "";
|
|
97
|
+
if (isStoreOp && nextName.length >= 2 && nextName.startsWith('_[')) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
89
101
|
if (isStoreOp && this.code.Next?.Next) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
// Chain assign and nested walrus share the shape
|
|
103
|
+
// (DUP STORE)+ ending either with STORE (chain, statement)
|
|
104
|
+
// or STORE / POP_TOP (walrus, expression).
|
|
105
|
+
// Walk forward over the alternating DUP/STORE run and inspect
|
|
106
|
+
// what follows the last STORE to distinguish them.
|
|
107
|
+
const storeOps = new Set([
|
|
108
|
+
this.OpCodes.STORE_NAME_A,
|
|
109
|
+
this.OpCodes.STORE_FAST_A,
|
|
110
|
+
this.OpCodes.STORE_GLOBAL_A,
|
|
111
|
+
this.OpCodes.STORE_ATTR_A,
|
|
112
|
+
this.OpCodes.STORE_DEREF_A
|
|
113
|
+
]);
|
|
114
|
+
const instructions = this.code.Instructions;
|
|
115
|
+
let idx = this.code.CurrentInstructionIndex + 1; // at STORE
|
|
116
|
+
let lastStoreIdx = -1;
|
|
117
|
+
let storeCount = 0;
|
|
118
|
+
while (idx < instructions.length) {
|
|
119
|
+
const op = instructions[idx].OpCodeID;
|
|
120
|
+
if (storeOps.has(op)) {
|
|
121
|
+
lastStoreIdx = idx;
|
|
122
|
+
storeCount++;
|
|
123
|
+
idx++;
|
|
124
|
+
} else if (op == this.OpCodes.DUP_TOP) {
|
|
125
|
+
idx++;
|
|
126
|
+
} else {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const afterRunOp = lastStoreIdx >= 0
|
|
131
|
+
? instructions[lastStoreIdx + 1]?.OpCodeID
|
|
132
|
+
: undefined;
|
|
133
|
+
// Chain assign needs 2+ STOREs; single-STORE runs are walrus
|
|
134
|
+
// regardless of what follows.
|
|
135
|
+
const isChainAssign = storeCount >= 2 &&
|
|
136
|
+
afterRunOp !== undefined &&
|
|
137
|
+
afterRunOp !== this.OpCodes.POP_TOP;
|
|
138
|
+
|
|
139
|
+
if (!isChainAssign) {
|
|
140
|
+
// Nested walrus operator — mark this DUP as walrus-producing.
|
|
100
141
|
this.isWalrusOperator = true;
|
|
101
142
|
return;
|
|
102
143
|
}
|
|
@@ -210,6 +251,19 @@ function handleSwapA() {
|
|
|
210
251
|
}
|
|
211
252
|
|
|
212
253
|
function handleRotTwo() {
|
|
254
|
+
// In Py3.10 match/case, ROT_TWO plays two roles:
|
|
255
|
+
// 1. Sequence-pattern element-order reversal (follows UNPACK_SEQUENCE)
|
|
256
|
+
// → record SWAP so reconstructPattern's reverseOrder kicks in.
|
|
257
|
+
// 2. Class-pattern binding-extraction dance (follows BINARY_SUBSCR)
|
|
258
|
+
// → record BIND_STASH so reconstructPattern can place the later
|
|
259
|
+
// STORE_FAST at the correct attribute position.
|
|
260
|
+
if (this.inMatchPattern) {
|
|
261
|
+
if (this.code.Prev?.OpCodeID == this.OpCodes.UNPACK_SEQUENCE_A) {
|
|
262
|
+
this.patternOps.push({type: 'SWAP', depth: 2});
|
|
263
|
+
} else if (this.code.Prev?.OpCodeID == this.OpCodes.BINARY_SUBSCR) {
|
|
264
|
+
this.patternOps.push({type: 'BIND_STASH'});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
213
267
|
let one = this.dataStack.pop();
|
|
214
268
|
if (this.dataStack.top() instanceof AST.ASTChainStore) {
|
|
215
269
|
this.dataStack.pop();
|
|
@@ -221,18 +275,26 @@ function handleRotTwo() {
|
|
|
221
275
|
}
|
|
222
276
|
|
|
223
277
|
function handleRotThree() {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
this.dataStack.
|
|
232
|
-
this.dataStack.push(two);
|
|
278
|
+
if (this.inMatchPattern &&
|
|
279
|
+
this.code.Prev?.OpCodeID == this.OpCodes.BINARY_SUBSCR) {
|
|
280
|
+
this.patternOps.push({type: 'BIND_STASH'});
|
|
281
|
+
}
|
|
282
|
+
let one = this.dataStack.pop();
|
|
283
|
+
let two = this.dataStack.pop();
|
|
284
|
+
if (this.dataStack.top() instanceof AST.ASTChainStore) {
|
|
285
|
+
this.dataStack.pop();
|
|
233
286
|
}
|
|
287
|
+
let three = this.dataStack.pop();
|
|
288
|
+
this.dataStack.push(one);
|
|
289
|
+
this.dataStack.push(three);
|
|
290
|
+
this.dataStack.push(two);
|
|
291
|
+
}
|
|
234
292
|
|
|
235
293
|
function handleRotFour() {
|
|
294
|
+
if (this.inMatchPattern &&
|
|
295
|
+
this.code.Prev?.OpCodeID == this.OpCodes.BINARY_SUBSCR) {
|
|
296
|
+
this.patternOps.push({type: 'BIND_STASH'});
|
|
297
|
+
}
|
|
236
298
|
let one = this.dataStack.pop();
|
|
237
299
|
let two = this.dataStack.pop();
|
|
238
300
|
let three = this.dataStack.pop();
|
|
@@ -25,6 +25,26 @@ function handleMapAddA() {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// Async dict comprehension inner code: GET_ANEXT iteration uses
|
|
29
|
+
// SETUP_EXCEPT/FINALLY rather than FOR_ITER, so no For+comp block
|
|
30
|
+
// exists. Save the key/value so the CALL-site reconstruction can use
|
|
31
|
+
// them as the comprehension's yield expression.
|
|
32
|
+
let forBlock = null;
|
|
33
|
+
for (let i = this.blocks.length - 1; i >= 0; i--) {
|
|
34
|
+
const blk = this.blocks[i];
|
|
35
|
+
if (blk.blockType == AST.ASTBlock.BlockType.For && blk.comprehension) {
|
|
36
|
+
forBlock = blk;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (blk.blockType != AST.ASTBlock.BlockType.If) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!forBlock) {
|
|
44
|
+
this.object._asyncCompYieldExpr = value;
|
|
45
|
+
this.object._asyncCompYieldKey = key;
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
let mapNode = this.dataStack.pop();
|
|
29
49
|
|
|
30
50
|
if (!(mapNode instanceof AST.ASTMap)) {
|
|
@@ -38,7 +58,23 @@ function handleMapAddA() {
|
|
|
38
58
|
}
|
|
39
59
|
|
|
40
60
|
function handleStoreMap() {
|
|
41
|
-
|
|
61
|
+
// Py2.x STORE_MAP: TOS=key, SECOND=value, THIRD=dict under construction.
|
|
62
|
+
// Pops key and value, leaves dict on stack with entry added.
|
|
63
|
+
// Without this, BUILD_MAP yields an empty ASTMap and the raw key/value
|
|
64
|
+
// LOAD_CONSTs pile onto the stack; the trailing STORE_NAME then picks
|
|
65
|
+
// up the last-pushed key (a literal int/string) instead of the dict.
|
|
66
|
+
let key = this.dataStack.pop();
|
|
67
|
+
let value = this.dataStack.pop();
|
|
68
|
+
let mapNode = this.dataStack.top();
|
|
69
|
+
if (mapNode instanceof AST.ASTMap) {
|
|
70
|
+
mapNode.add(key, value);
|
|
71
|
+
mapNode.line = this.code.Current.LineNo;
|
|
72
|
+
} else {
|
|
73
|
+
// No dict under construction — unexpected; restore stack so downstream
|
|
74
|
+
// handlers still see the values and can diagnose the shape.
|
|
75
|
+
this.dataStack.push(value);
|
|
76
|
+
this.dataStack.push(key);
|
|
77
|
+
}
|
|
42
78
|
}
|
|
43
79
|
|
|
44
80
|
function handleBuildSliceA() {
|
|
@@ -297,6 +333,70 @@ function storeMatchesAnnotation(storeNode, annotationKeyNode) {
|
|
|
297
333
|
return false;
|
|
298
334
|
}
|
|
299
335
|
|
|
336
|
+
function handleBinarySlice() {
|
|
337
|
+
// Python 3.12+ BINARY_SLICE: (container, start, stop -- container[start:stop])
|
|
338
|
+
let stop = this.dataStack.pop();
|
|
339
|
+
let start = this.dataStack.pop();
|
|
340
|
+
let container = this.dataStack.pop();
|
|
341
|
+
|
|
342
|
+
if (start instanceof AST.ASTObject && (start.object == null || start.object.ClassName == 'Py_None')) {
|
|
343
|
+
start = null;
|
|
344
|
+
}
|
|
345
|
+
if (stop instanceof AST.ASTObject && (stop.object == null || stop.object.ClassName == 'Py_None')) {
|
|
346
|
+
stop = null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let sliceOp;
|
|
350
|
+
if (!start && !stop) {
|
|
351
|
+
sliceOp = AST.ASTSlice.SliceOp.Slice0;
|
|
352
|
+
} else if (!start) {
|
|
353
|
+
sliceOp = AST.ASTSlice.SliceOp.Slice2;
|
|
354
|
+
} else if (!stop) {
|
|
355
|
+
sliceOp = AST.ASTSlice.SliceOp.Slice1;
|
|
356
|
+
} else {
|
|
357
|
+
sliceOp = AST.ASTSlice.SliceOp.Slice3;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
let sliceNode = new AST.ASTSlice(sliceOp, start, stop);
|
|
361
|
+
sliceNode.line = this.code.Current.LineNo;
|
|
362
|
+
let node = new AST.ASTSubscr(container, sliceNode);
|
|
363
|
+
node.line = this.code.Current.LineNo;
|
|
364
|
+
this.dataStack.push(node);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function handleStoreSlice() {
|
|
368
|
+
// Python 3.12+ STORE_SLICE: (value, container, start, stop -- )
|
|
369
|
+
// Stores container[start:stop] = value
|
|
370
|
+
let stop = this.dataStack.pop();
|
|
371
|
+
let start = this.dataStack.pop();
|
|
372
|
+
let container = this.dataStack.pop();
|
|
373
|
+
let value = this.dataStack.pop();
|
|
374
|
+
|
|
375
|
+
if (start instanceof AST.ASTObject && (start.object == null || start.object.ClassName == 'Py_None')) {
|
|
376
|
+
start = null;
|
|
377
|
+
}
|
|
378
|
+
if (stop instanceof AST.ASTObject && (stop.object == null || stop.object.ClassName == 'Py_None')) {
|
|
379
|
+
stop = null;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
let sliceOp;
|
|
383
|
+
if (!start && !stop) {
|
|
384
|
+
sliceOp = AST.ASTSlice.SliceOp.Slice0;
|
|
385
|
+
} else if (!start) {
|
|
386
|
+
sliceOp = AST.ASTSlice.SliceOp.Slice2;
|
|
387
|
+
} else if (!stop) {
|
|
388
|
+
sliceOp = AST.ASTSlice.SliceOp.Slice1;
|
|
389
|
+
} else {
|
|
390
|
+
sliceOp = AST.ASTSlice.SliceOp.Slice3;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
let sliceNode = new AST.ASTSlice(sliceOp, start, stop);
|
|
394
|
+
sliceNode.line = this.code.Current.LineNo;
|
|
395
|
+
let node = new AST.ASTStore(value, new AST.ASTSubscr(container, sliceNode));
|
|
396
|
+
node.line = this.code.Current.LineNo;
|
|
397
|
+
this.curBlock.append(node);
|
|
398
|
+
}
|
|
399
|
+
|
|
300
400
|
function handleStoreSubscr() {
|
|
301
401
|
if (this.unpack) {
|
|
302
402
|
let subscrNode = this.dataStack.pop();
|
|
@@ -390,5 +490,7 @@ module.exports = {
|
|
|
390
490
|
handleStoreSlice1,
|
|
391
491
|
handleStoreSlice2,
|
|
392
492
|
handleStoreSlice3,
|
|
493
|
+
handleBinarySlice,
|
|
494
|
+
handleStoreSlice,
|
|
393
495
|
handleStoreSubscr,
|
|
394
496
|
};
|