Skip to content

Commit 8d7f570

Browse files
0xegbrail
authored andcommitted
Support for rest parameters in destructuring
Tests for rest parameter support in destructuring * Update 262 properties * Refactor + cleanup ScriptRuntime::doObjectRest * Fix incorrect dumpIcode for Icode_OBJECT_REST
1 parent 96d264a commit 8d7f570

15 files changed

Lines changed: 1315 additions & 536 deletions

File tree

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: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,32 @@ static <T extends ScriptOrFn<T>> void dumpICode(
989989
out.println(tname + " \"" + str + '"');
990990
break;
991991
}
992+
case Icode_OBJECT_REST:
993+
{
994+
// read count of computed keys from bytecode
995+
int computedKeyCount = iCode[pc++] & 0xFF;
996+
997+
if (indexReg >= 0) {
998+
int[] staticKeyIndices = (int[]) idata.literalIds[indexReg];
999+
StringBuilder keys = new StringBuilder();
1000+
keys.append("[static: ");
1001+
for (int i = 0; i < staticKeyIndices.length; i++) {
1002+
if (i > 0) keys.append(", ");
1003+
keys.append("\"").append(strings[staticKeyIndices[i]]).append("\"");
1004+
}
1005+
keys.append("; computed: ").append(computedKeyCount).append("]");
1006+
out.println(tname + " excluding " + keys);
1007+
} else {
1008+
out.println(
1009+
tname
1010+
+ " literalId: "
1011+
+ (-indexReg - 1)
1012+
+ ", computed: "
1013+
+ computedKeyCount);
1014+
}
1015+
icodeLength = pc - old_pc;
1016+
break;
1017+
}
9921018
case Icode_REG_IND_C0:
9931019
indexReg = 0;
9941020
out.println(tname);
@@ -1727,6 +1753,7 @@ private abstract static class InstructionClass {
17271753
instructionObjs[base + Icode_SCOPE_LOAD] = new DoScopeLoad();
17281754
instructionObjs[base + Icode_SCOPE_SAVE] = new DoScopeSave();
17291755
instructionObjs[base + Icode_SPREAD] = new DoSpread();
1756+
instructionObjs[base + Icode_OBJECT_REST] = new DoObjectRest();
17301757
instructionObjs[base + Icode_CLOSURE_EXPR] = new DoClosureExpr();
17311758
instructionObjs[base + Icode_METHOD_EXPR] = new DoMethodExpr();
17321759
instructionObjs[base + Icode_CLOSURE_STMT] = new DoClosureStatement();
@@ -4478,6 +4505,46 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
44784505
}
44794506
}
44804507

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

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,19 @@ protected Node visitLet(boolean createWith, Node parent, Node previous, Node sco
467467
objectLiteral.addChildToBack(new Node(Token.VOID, Node.newNumber(0.0)));
468468
}
469469
}
470-
current = c.getFirstChild(); // should be a NAME, checked below
470+
// Process all NAME children of the inner LET node (not just the first)
471+
// This includes the main temp variable ($0) and any computed property temps
472+
// ($1, $2, etc.)
473+
for (Node child = c.getFirstChild(); child != null; child = child.getNext()) {
474+
if (child.getType() != Token.NAME) throw Kit.codeBug();
475+
list.add(ScriptRuntime.getIndexObject(child.getString()));
476+
Node init = child.getFirstChild();
477+
if (init == null) {
478+
init = new Node(Token.VOID, Node.newNumber(0.0));
479+
}
480+
objectLiteral.addChildToBack(init);
481+
}
482+
continue; // Already processed all children, move to next sibling of LETEXPR
471483
}
472484
if (current.getType() != Token.NAME) throw Kit.codeBug();
473485
list.add(ScriptRuntime.getIndexObject(current.getString()));
@@ -500,7 +512,20 @@ protected Node visitLet(boolean createWith, Node parent, Node previous, Node sco
500512
}
501513
// We're removing the LETEXPR, so move the symbols
502514
Scope.joinScopes((Scope) current, (Scope) scopeNode);
503-
current = c.getFirstChild(); // should be a NAME, checked below
515+
// Process all NAME children of the inner LET node (not just the first)
516+
// This includes the main temp variable ($0) and any computed property temps
517+
// ($1, $2, etc.)
518+
for (Node child = c.getFirstChild(); child != null; child = child.getNext()) {
519+
if (child.getType() != Token.NAME) throw Kit.codeBug();
520+
Node stringNode = Node.newString(child.getString());
521+
stringNode.setScope((Scope) scopeNode);
522+
Node init = child.getFirstChild();
523+
if (init == null) {
524+
init = new Node(Token.VOID, Node.newNumber(0.0));
525+
}
526+
newVars.addChildToBack(new Node(Token.SETVAR, stringNode, init));
527+
}
528+
continue; // Already processed all children, move to next sibling of LETEXPR
504529
}
505530
if (current.getType() != Token.NAME) throw Kit.codeBug();
506531
Node stringNode = Node.newString(current.getString());

0 commit comments

Comments
 (0)