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.
- package/depyo.js +43 -2
- package/lib/OpCodes.js +22 -5
- package/lib/PycDecompiler.js +402 -39
- package/lib/PycDisassembler.js +1 -1
- package/lib/PycReader.js +22 -3
- package/lib/PythonObject.js +40 -6
- package/lib/ast/ast_node.js +292 -71
- 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/context_managers.js +202 -13
- package/lib/handlers/control_flow_jumps.js +516 -23
- package/lib/handlers/exceptions_blocks.js +85 -22
- package/lib/handlers/formatting.js +60 -17
- package/lib/handlers/function_calls.js +454 -57
- package/lib/handlers/function_class_build.js +159 -64
- 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 +216 -43
- 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 +1 -1
|
@@ -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({
|
|
@@ -603,6 +664,27 @@ function handlePopTop() {
|
|
|
603
664
|
this.OpCodes.JUMP_IF_FALSE_A
|
|
604
665
|
].includes(this.code.Prev?.OpCodeID);
|
|
605
666
|
|
|
667
|
+
// A POP_TOP following ROT_TWO/ROT_THREE/ROT_FOUR is part of binding-extraction
|
|
668
|
+
// stack cleanup inside a class/sequence pattern, not the body-start POP_TOP.
|
|
669
|
+
let prevIsRot = [
|
|
670
|
+
this.OpCodes.ROT_TWO,
|
|
671
|
+
this.OpCodes.ROT_THREE,
|
|
672
|
+
this.OpCodes.ROT_FOUR
|
|
673
|
+
].includes(this.code.Prev?.OpCodeID);
|
|
674
|
+
|
|
675
|
+
// If the next non-CACHE opcode is STORE_FAST/STORE_NAME, this POP_TOP is mid-pattern
|
|
676
|
+
// binding cleanup (pops the match result tuple before the remaining STORE captures).
|
|
677
|
+
let nextIsStoreBinding = (() => {
|
|
678
|
+
let scan = this.code.Next;
|
|
679
|
+
let steps = 0;
|
|
680
|
+
while (scan && steps < 4) {
|
|
681
|
+
if (scan.OpCodeID == this.OpCodes.CACHE) { scan = scan.Next; steps++; continue; }
|
|
682
|
+
return scan.OpCodeID == this.OpCodes.STORE_FAST_A ||
|
|
683
|
+
scan.OpCodeID == this.OpCodes.STORE_NAME_A;
|
|
684
|
+
}
|
|
685
|
+
return false;
|
|
686
|
+
})();
|
|
687
|
+
|
|
606
688
|
const patternHasOps = (this.patternOps?.length || 0) > 0;
|
|
607
689
|
let isLiteralPattern = patternHasOps && !this.patternOps.some(op => ["MATCH_SEQUENCE","MATCH_CLASS","MATCH_MAPPING","MATCH_KEYS"].includes(op.type));
|
|
608
690
|
let shouldStartCase = readyForWildcard || !!this.inMatchPattern;
|
|
@@ -612,6 +694,41 @@ function handlePopTop() {
|
|
|
612
694
|
if (patternHasOps && isLiteralPattern && !prevIsJump) {
|
|
613
695
|
shouldStartCase = false;
|
|
614
696
|
}
|
|
697
|
+
if (patternHasOps && prevIsRot) {
|
|
698
|
+
shouldStartCase = false;
|
|
699
|
+
}
|
|
700
|
+
if (patternHasOps && nextIsStoreBinding) {
|
|
701
|
+
shouldStartCase = false;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// When readyForWildcard fires on the match-exit POP_TOP and the only
|
|
705
|
+
// code that follows is the function's implicit `return None` tail,
|
|
706
|
+
// the source didn't actually include a `case _:` clause — CPython
|
|
707
|
+
// emits the tail unconditionally. Suppress the phantom wildcard.
|
|
708
|
+
if (readyForWildcard && !patternHasOps) {
|
|
709
|
+
let scan = this.code.Next;
|
|
710
|
+
let steps = 0;
|
|
711
|
+
let sawLoadNone = false;
|
|
712
|
+
while (scan && steps < 6) {
|
|
713
|
+
const op = scan.OpCodeID;
|
|
714
|
+
if (op == this.OpCodes.CACHE ||
|
|
715
|
+
op == this.OpCodes.RESUME_A ||
|
|
716
|
+
op == this.OpCodes.NOP) {
|
|
717
|
+
scan = scan.Next; steps++; continue;
|
|
718
|
+
}
|
|
719
|
+
if (op == this.OpCodes.LOAD_CONST_A && !sawLoadNone) {
|
|
720
|
+
sawLoadNone = true;
|
|
721
|
+
scan = scan.Next; steps++; continue;
|
|
722
|
+
}
|
|
723
|
+
if (op == this.OpCodes.RETURN_VALUE || op == this.OpCodes.RETURN_CONST_A) {
|
|
724
|
+
if (sawLoadNone || op == this.OpCodes.RETURN_CONST_A) {
|
|
725
|
+
shouldStartCase = false;
|
|
726
|
+
}
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
615
732
|
|
|
616
733
|
if (shouldStartCase) {
|
|
617
734
|
if (beginMatchCaseFromPattern.call(this, {reason: 'pop_top'})) {
|
|
@@ -628,7 +745,7 @@ function handlePopTop() {
|
|
|
628
745
|
return;
|
|
629
746
|
}
|
|
630
747
|
|
|
631
|
-
if (
|
|
748
|
+
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
749
|
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Except) {
|
|
633
750
|
// Skipping POP_TOP, POP_TOP, POP_TOP
|
|
634
751
|
if ([this.OpCodes.JUMP_ABSOLUTE_A, this.OpCodes.JUMP_FORWARD_A].includes(this.code.Prev.OpCodeID)) {
|
|
@@ -685,7 +802,13 @@ function handlePopTop() {
|
|
|
685
802
|
}
|
|
686
803
|
if (!this.curBlock.inited) {
|
|
687
804
|
if (this.curBlock.blockType == AST.ASTBlock.BlockType.With) {
|
|
805
|
+
// POP_TOP initializing a WITH block consumes the context-manager
|
|
806
|
+
// expression off the stack (Py3.0+ `with EXPR:` with no `as`).
|
|
807
|
+
// The value IS the with-expr, not a body statement — return before
|
|
808
|
+
// the fall-through append would add a phantom duplicate.
|
|
688
809
|
this.curBlock.expr = value;
|
|
810
|
+
this.curBlock.init();
|
|
811
|
+
return;
|
|
689
812
|
} else if (this.curBlock.blockType == AST.ASTBlock.BlockType.If && !this.curBlock.condition) {
|
|
690
813
|
this.curBlock.condition = value;
|
|
691
814
|
}
|
|
@@ -702,17 +825,59 @@ function handlePopTop() {
|
|
|
702
825
|
this.curBlock.append(value);
|
|
703
826
|
}
|
|
704
827
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
828
|
+
/* Python 2.3 listcomp body uses `_[1](x)` (the cached list.append call)
|
|
829
|
+
* instead of LIST_APPEND. Detect this call, whether emitted directly in
|
|
830
|
+
* a For+comp body or nested inside if-filters (`[x for x in y if c]`),
|
|
831
|
+
* and materialize an ASTComprehension instead of leaving the call as a
|
|
832
|
+
* bogus body statement. */
|
|
833
|
+
if (value instanceof AST.ASTCall) {
|
|
834
|
+
let forBlock = null;
|
|
835
|
+
let ifBlocks = [];
|
|
836
|
+
for (let i = this.blocks.length - 1; i >= 0; i--) {
|
|
837
|
+
const blk = this.blocks[i];
|
|
838
|
+
if (blk.blockType == AST.ASTBlock.BlockType.For && blk.comprehension) {
|
|
839
|
+
forBlock = blk;
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
if (blk.blockType == AST.ASTBlock.BlockType.If) {
|
|
843
|
+
ifBlocks.push(blk);
|
|
844
|
+
} else {
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const funcIsCompAppend = forBlock && value.func &&
|
|
850
|
+
this._listCompAppendRefs &&
|
|
851
|
+
Object.values(this._listCompAppendRefs).includes(value.func);
|
|
852
|
+
const isDirectCompBody = forBlock && this.curBlock === forBlock;
|
|
853
|
+
|
|
854
|
+
if (funcIsCompAppend || isDirectCompBody) {
|
|
712
855
|
let pparams = value.pparams;
|
|
713
856
|
if (!pparams.empty()) {
|
|
857
|
+
if (funcIsCompAppend && this.curBlock !== forBlock) {
|
|
858
|
+
for (const ifBlk of ifBlocks) {
|
|
859
|
+
let cond = ifBlk.condition;
|
|
860
|
+
if (!cond) continue;
|
|
861
|
+
if (ifBlk.negative) {
|
|
862
|
+
const notCond = new AST.ASTUnary(cond, AST.ASTUnary.UnaryOp.Not);
|
|
863
|
+
notCond.line = this.code.Current.LineNo;
|
|
864
|
+
cond = notCond;
|
|
865
|
+
}
|
|
866
|
+
if (forBlock.condition) {
|
|
867
|
+
const andCond = new AST.ASTBinary(forBlock.condition, cond, AST.ASTBinary.BinOp.LogicalAnd);
|
|
868
|
+
andCond.line = this.code.Current.LineNo;
|
|
869
|
+
forBlock.condition = andCond;
|
|
870
|
+
} else {
|
|
871
|
+
forBlock.condition = cond;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
while (this.curBlock !== forBlock && this.blocks.length > 1) {
|
|
875
|
+
this.blocks.pop();
|
|
876
|
+
this.curBlock = this.blocks.top();
|
|
877
|
+
}
|
|
878
|
+
}
|
|
714
879
|
let res = pparams[0];
|
|
715
|
-
let node = new AST.ASTComprehension
|
|
880
|
+
let node = new AST.ASTComprehension(res);
|
|
716
881
|
node.line = this.code.Current.LineNo;
|
|
717
882
|
this.dataStack.push(node);
|
|
718
883
|
}
|
|
@@ -809,7 +974,15 @@ function handleReturnValue() {
|
|
|
809
974
|
// Only start new case if no current case AND (not in pattern OR has unflushed pattern ops)
|
|
810
975
|
const hasUnflushedPattern = (this.patternOps?.length || 0) > 0;
|
|
811
976
|
const needsNewCase = !this.currentCase && (!this.inMatchPattern || hasUnflushedPattern);
|
|
812
|
-
|
|
977
|
+
// Suppress phantom wildcard when this is the function-tail implicit
|
|
978
|
+
// return None: no pattern ops recorded, LOAD_CONST None precedes us,
|
|
979
|
+
// and this is the last RETURN_VALUE in the code object. CPython
|
|
980
|
+
// emits the tail whether or not source had `case _:`.
|
|
981
|
+
const topIsNone = this.dataStack.top() instanceof AST.ASTNone ||
|
|
982
|
+
(this.dataStack.top() instanceof AST.ASTObject &&
|
|
983
|
+
this.dataStack.top().obj?.ClassName === 'Py_None');
|
|
984
|
+
const isImplicitTail = !hasUnflushedPattern && topIsNone && !this.code.Next;
|
|
985
|
+
if (needsNewCase && !isImplicitTail) {
|
|
813
986
|
beginMatchCaseFromPattern.call(this, {reason: 'return'});
|
|
814
987
|
}
|
|
815
988
|
}
|
|
@@ -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
|
};
|
package/lib/handlers/unpack.js
CHANGED
|
@@ -102,28 +102,30 @@ function handleBuildSetUnpackA() {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
function handleBuildMapUnpackA() {
|
|
105
|
-
|
|
105
|
+
// Py 3.5-3.8: count of mappings to merge for {**a, **b} dict literal.
|
|
106
|
+
processBuildMapUnpack.call(this, /*withCall=*/false);
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
function handleBuildMapUnpackWithCallA() {
|
|
109
|
-
|
|
110
|
+
// Py 3.5 only: oparg = count + (fn_loc << 8). Lower byte is the map count,
|
|
111
|
+
// upper byte locates the callable for error reporting.
|
|
112
|
+
// Py 3.6-3.8: oparg = plain count (this opcode is kept for unpack-with-call
|
|
113
|
+
// semantics but the fn_loc byte is dropped).
|
|
114
|
+
processBuildMapUnpack.call(this, /*withCall=*/true);
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
function processBuildMapUnpack() {
|
|
113
|
-
let
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
} else {
|
|
121
|
-
if (global.g_cliArgs?.debug) {
|
|
122
|
-
console.error("Expected a map for BUILD_MAP_UNPACK");
|
|
123
|
-
}
|
|
124
|
-
}
|
|
117
|
+
function processBuildMapUnpack(withCall) {
|
|
118
|
+
let count = this.code.Current.Argument;
|
|
119
|
+
if (withCall && this.object.Reader.versionCompare(3, 6) < 0) {
|
|
120
|
+
count = count & 0xFF;
|
|
121
|
+
}
|
|
122
|
+
let items = [];
|
|
123
|
+
for (let idx = 0; idx < count; idx++) {
|
|
124
|
+
items.unshift(this.dataStack.pop());
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
let node = new AST.ASTMapUnpack(items);
|
|
127
|
+
node.line = this.code.Current.LineNo;
|
|
128
|
+
this.dataStack.push(node);
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
module.exports = {
|