Skip to content

Commit 9c9409f

Browse files
committed
Support for rest parameters in destructuring
1 parent 0261fb4 commit 9c9409f

File tree

13 files changed

+1150
-485
lines changed

13 files changed

+1150
-485
lines changed

rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,49 @@ private void visitExpression(Node node, int contextFlags) {
604604
stackChange(1);
605605
break;
606606

607+
case Token.OBJECT_REST:
608+
{
609+
// Handle object rest operation: {...rest} in destructuring
610+
visitExpression(child, 0); // source object
611+
Object[] excludedKeys = (Object[]) node.getProp(Node.OBJECT_IDS_PROP);
612+
613+
// Count static vs computed keys and build static key indices array
614+
int computedKeyCount = 0;
615+
java.util.List<Integer> staticKeyIndices = new java.util.ArrayList<>();
616+
for (Object key : excludedKeys) {
617+
if (key instanceof Node) {
618+
computedKeyCount++;
619+
} else {
620+
String keyStr = key.toString();
621+
int keyIndex = strings.getOrDefault(keyStr, -1);
622+
if (keyIndex == -1) {
623+
keyIndex = strings.size();
624+
strings.put(keyStr, keyIndex);
625+
}
626+
staticKeyIndices.add(keyIndex);
627+
}
628+
}
629+
630+
// Store static key indices in literalIds
631+
int staticKeysLiteralId = literalIds.size();
632+
int[] staticKeyArray = staticKeyIndices.stream().mapToInt(i -> i).toArray();
633+
literalIds.add(staticKeyArray);
634+
635+
// Evaluate & push computed keys onto stack
636+
for (Object key : excludedKeys) {
637+
if (key instanceof Node) {
638+
visitExpression((Node) key, 0); // Evaluate computed key expression
639+
}
640+
}
641+
642+
// Bytecode: [Icode_OBJECT_REST] [literalId:index] [computedKeyCount:uint8]
643+
addIndexOp(Icode_OBJECT_REST, staticKeysLiteralId);
644+
addUint8(computedKeyCount);
645+
646+
stackChange(-computedKeyCount); // Computed keys removed from stack
647+
}
648+
break;
649+
607650
case Token.REF_CALL:
608651
case Token.CALL:
609652
case Token.NEW:

rhino/src/main/java/org/mozilla/javascript/Icode.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,11 @@ abstract class Icode {
165165
// spread
166166
Icode_SPREAD = Icode_DELPROP_SUPER - 1,
167167

168+
// object rest - create object excluding extracted keys
169+
Icode_OBJECT_REST = Icode_SPREAD - 1,
170+
168171
// Last icode
169-
MIN_ICODE = Icode_SPREAD;
172+
MIN_ICODE = Icode_OBJECT_REST;
170173

171174
static String bytecodeName(int bytecode) {
172175
if (!validBytecode(bytecode)) {
@@ -358,6 +361,8 @@ static String bytecodeName(int bytecode) {
358361
return "DELPROP_SUPER";
359362
case Icode_SPREAD:
360363
return "SPREAD";
364+
case Icode_OBJECT_REST:
365+
return "OBJECT_REST";
361366
}
362367

363368
// icode without name

rhino/src/main/java/org/mozilla/javascript/Interpreter.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,27 @@ static <T extends ScriptOrFn<T>> void dumpICode(
989989
out.println(tname + " \"" + str + '"');
990990
break;
991991
}
992+
case Icode_OBJECT_REST:
993+
{
994+
// read key counts (static & computed)
995+
int staticKeyCount = getIndex(iCode, pc);
996+
pc += 2;
997+
int computedKeyCount = getIndex(iCode, pc);
998+
pc += 2;
999+
1000+
StringBuilder keys = new StringBuilder();
1001+
keys.append("[static: ");
1002+
for (int i = 0; i < staticKeyCount; i++) {
1003+
if (i > 0) keys.append(", ");
1004+
int keyIndex = getIndex(iCode, pc);
1005+
pc += 2;
1006+
keys.append("\"").append(strings[keyIndex]).append("\"");
1007+
}
1008+
keys.append("; computed: ").append(computedKeyCount).append("]");
1009+
out.println(tname + " excluding " + keys);
1010+
icodeLength = pc - old_pc;
1011+
break;
1012+
}
9921013
case Icode_REG_IND_C0:
9931014
indexReg = 0;
9941015
out.println(tname);
@@ -1727,6 +1748,7 @@ private abstract static class InstructionClass {
17271748
instructionObjs[base + Icode_SCOPE_LOAD] = new DoScopeLoad();
17281749
instructionObjs[base + Icode_SCOPE_SAVE] = new DoScopeSave();
17291750
instructionObjs[base + Icode_SPREAD] = new DoSpread();
1751+
instructionObjs[base + Icode_OBJECT_REST] = new DoObjectRest();
17301752
instructionObjs[base + Icode_CLOSURE_EXPR] = new DoClosureExpr();
17311753
instructionObjs[base + Icode_METHOD_EXPR] = new DoMethodExpr();
17321754
instructionObjs[base + Icode_CLOSURE_STMT] = new DoClosureStatement();
@@ -4478,6 +4500,46 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
44784500
}
44794501
}
44804502

4503+
private static class DoObjectRest extends InstructionClass {
4504+
@Override
4505+
NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
4506+
// indexReg contains the literalId for static key indices array
4507+
int computedKeyCount = frame.idata.itsICode[frame.pc++] & 0xFF;
4508+
4509+
// Get static key indices from literalIds using indexReg
4510+
int[] staticKeyIndices = (int[]) frame.idata.literalIds[state.indexReg];
4511+
int staticKeyCount = staticKeyIndices.length;
4512+
4513+
Object[] excludeKeys = new Object[staticKeyCount + computedKeyCount];
4514+
4515+
// Fill static keys from the strings table using stored indices
4516+
for (int i = 0; i < staticKeyCount; i++) {
4517+
excludeKeys[i] = frame.idata.itsStringTable[staticKeyIndices[i]];
4518+
}
4519+
4520+
// Pop computed keys from stack: [source, computed_key_1, ..., computed_key_N]
4521+
for (int i = computedKeyCount - 1; i >= 0; i--) {
4522+
Object computedKey = frame.stack[state.stackTop];
4523+
if (computedKey == DOUBLE_MARK) {
4524+
computedKey = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
4525+
}
4526+
excludeKeys[staticKeyCount + i] = ScriptRuntime.toString(computedKey);
4527+
state.stackTop--;
4528+
}
4529+
4530+
Object source = frame.stack[state.stackTop];
4531+
if (source == DOUBLE_MARK) {
4532+
source = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
4533+
}
4534+
4535+
// Create rest object
4536+
Scriptable result = ScriptRuntime.doObjectRest(cx, frame.scope, source, excludeKeys);
4537+
4538+
frame.stack[state.stackTop] = result;
4539+
return null;
4540+
}
4541+
}
4542+
44814543
private static class DoObjectLit extends InstructionClass {
44824544
@Override
44834545
NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {

rhino/src/main/java/org/mozilla/javascript/Node.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ public class Node implements Iterable<Node> {
6969
OPTIONAL_CHAINING = 30,
7070
SUPER_PROPERTY_ACCESS = 31,
7171
NUMBER_OF_SPREAD = 32,
72-
LAST_PROP = NUMBER_OF_SPREAD,
72+
OBJECT_REST_PROP = 33, // marks a CALL node as object rest operation
73+
LAST_PROP = OBJECT_REST_PROP,
7374
FIRST_PROP = FUNCTION_PROP;
7475

7576
// values of ISNUMBER_PROP to specify
@@ -457,6 +458,8 @@ static String propName(int propType) {
457458
return "super_property_access";
458459
case NUMBER_OF_SPREAD:
459460
return "number_of_spread";
461+
case OBJECT_REST_PROP:
462+
return "object_rest_prop";
460463

461464
default:
462465
Kit.codeBug();

0 commit comments

Comments
 (0)