Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nullish assign operator ??= #1705

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 73 additions & 9 deletions rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -602,10 +602,17 @@ private void visitExpression(Node node, int contextFlags) {
case Token.CALL:
case Token.NEW:
{
boolean isOptionalChainingCall =
node.getIntProp(Node.OPTIONAL_CHAINING, 0) == 1;
CompleteOptionalCallJump completeOptionalCallJump = null;
if (type == Token.NEW) {
visitExpression(child, 0);
} else {
generateCallFunAndThis(child);
completeOptionalCallJump =
generateCallFunAndThis(child, isOptionalChainingCall);
if (completeOptionalCallJump != null) {
resolveForwardGoto(completeOptionalCallJump.putArgsAndDoCallLabel);
}
}
int argCount = 0;
while ((child = child.getNext()) != null) {
Expand Down Expand Up @@ -643,6 +650,10 @@ private void visitExpression(Node node, int contextFlags) {
if (argCount > itsData.itsMaxCalleeArgs) {
itsData.itsMaxCalleeArgs = argCount;
}

if (completeOptionalCallJump != null) {
resolveForwardGoto(completeOptionalCallJump.afterLabel);
}
}
break;

Expand Down Expand Up @@ -1148,16 +1159,23 @@ private void finishGetElemGeneration(Node child) {
stackChange(-1);
}

private void generateCallFunAndThis(Node left) {
private CompleteOptionalCallJump generateCallFunAndThis(
Node left, boolean isOptionalChainingCall) {
// Generate code to place on stack function and thisObj
int type = left.getType();
switch (type) {
case Token.NAME:
{
String name = left.getString();
// stack: ... -> ... function thisObj
addStringOp(Icode_NAME_AND_THIS, name);
stackChange(2);
if (isOptionalChainingCall) {
addStringOp(Icode_NAME_AND_THIS_OPTIONAL, name);
stackChange(2);
return completeOptionalCallJump();
} else {
addStringOp(Icode_NAME_AND_THIS, name);
stackChange(2);
}
break;
}
case Token.GETPROP:
Expand All @@ -1169,23 +1187,59 @@ private void generateCallFunAndThis(Node left) {
if (type == Token.GETPROP) {
String property = id.getString();
// stack: ... target -> ... function thisObj
addStringOp(Icode_PROP_AND_THIS, property);
stackChange(1);
if (isOptionalChainingCall) {
addStringOp(Icode_PROP_AND_THIS_OPTIONAL, property);
stackChange(1);
return completeOptionalCallJump();
} else {
addStringOp(Icode_PROP_AND_THIS, property);
stackChange(1);
}
} else {
visitExpression(id, 0);
// stack: ... target id -> ... function thisObj
addIcode(Icode_ELEM_AND_THIS);
if (isOptionalChainingCall) {
addIcode(Icode_ELEM_AND_THIS_OPTIONAL);
return completeOptionalCallJump();
} else {
addIcode(Icode_ELEM_AND_THIS);
}
}
break;
}
default:
// Including Token.GETVAR
visitExpression(left, 0);
// stack: ... value -> ... function thisObj
addIcode(Icode_VALUE_AND_THIS);
stackChange(1);
if (isOptionalChainingCall) {
addIcode(Icode_VALUE_AND_THIS_OPTIONAL);
stackChange(1);
return completeOptionalCallJump();
} else {
addIcode(Icode_VALUE_AND_THIS);
stackChange(1);
}
break;
}
return null;
}

private CompleteOptionalCallJump completeOptionalCallJump() {
// If it's null or undefined, pop undefined and skip the arguments and call
addIcode(Icode_DUP);
stackChange(1);
int putArgsAndDoCallLabel = iCodeTop;
addGotoOp(Icode.Icode_IF_NOT_NULL_UNDEF);
stackChange(-1);

// Put undefined
addIcode(Icode_POP);
addIcode(Icode_POP);
addStringOp(Token.NAME, "undefined");
int afterLabel = iCodeTop;
addGotoOp(Token.GOTO);

return new CompleteOptionalCallJump(putArgsAndDoCallLabel, afterLabel);
}

private void visitIncDec(Node node, Node child) {
Expand Down Expand Up @@ -1695,4 +1749,14 @@ private void releaseLocal(int localSlot) {
--localTop;
if (localSlot != localTop) Kit.codeBug();
}

private static final class CompleteOptionalCallJump {
private final int putArgsAndDoCallLabel;
private final int afterLabel;

public CompleteOptionalCallJump(int putArgsAndDoCallLabel, int afterLabel) {
this.putArgsAndDoCallLabel = putArgsAndDoCallLabel;
this.afterLabel = afterLabel;
}
}
}
6 changes: 6 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/IRFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,9 @@ private Node transformFunctionCall(FunctionCall node) {
AstNode arg = args.get(i);
call.addChildToBack(transform(arg));
}
if (node.isOptionalCall()) {
call.putIntProp(Node.OPTIONAL_CHAINING, 1);
}
return call;
}

Expand Down Expand Up @@ -2149,6 +2152,9 @@ private Node createAssignment(int assignType, Node left, Node right) {
case Token.ASSIGN_EXP:
assignOp = Token.EXP;
break;
case Token.ASSIGN_NULLISH:
assignOp = Token.NULLISH_COALESCING;
break;
default:
throw Kit.codeBug();
}
Expand Down
21 changes: 19 additions & 2 deletions rhino/src/main/java/org/mozilla/javascript/Icode.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,21 @@ abstract class Icode {
Icode_PROP_AND_THIS = Icode_NAME_AND_THIS - 1,
Icode_ELEM_AND_THIS = Icode_PROP_AND_THIS - 1,
Icode_VALUE_AND_THIS = Icode_ELEM_AND_THIS - 1,
Icode_NAME_AND_THIS_OPTIONAL = Icode_VALUE_AND_THIS - 1,
Icode_PROP_AND_THIS_OPTIONAL = Icode_NAME_AND_THIS_OPTIONAL - 1,
Icode_ELEM_AND_THIS_OPTIONAL = Icode_PROP_AND_THIS_OPTIONAL - 1,
Icode_VALUE_AND_THIS_OPTIONAL = Icode_ELEM_AND_THIS_OPTIONAL - 1,

// Create closure object for nested functions
Icode_CLOSURE_EXPR = Icode_VALUE_AND_THIS - 1,
Icode_CLOSURE_EXPR = Icode_VALUE_AND_THIS_OPTIONAL - 1,
Icode_CLOSURE_STMT = Icode_CLOSURE_EXPR - 1,

// Special calls
Icode_CALLSPECIAL = Icode_CLOSURE_STMT - 1,
Icode_CALLSPECIAL_OPTIONAL = Icode_CALLSPECIAL - 1,

// To return undefined value
Icode_RETUNDEF = Icode_CALLSPECIAL - 1,
Icode_RETUNDEF = Icode_CALLSPECIAL_OPTIONAL - 1,

// Exception handling implementation
Icode_GOSUB = Icode_RETUNDEF - 1,
Expand Down Expand Up @@ -163,6 +168,8 @@ static String bytecodeName(int bytecode) {
}

switch (bytecode) {
case Icode_DELNAME:
return "DELNAME";
case Icode_DUP:
return "DUP";
case Icode_DUP2:
Expand Down Expand Up @@ -197,12 +204,22 @@ static String bytecodeName(int bytecode) {
return "ELEM_AND_THIS";
case Icode_VALUE_AND_THIS:
return "VALUE_AND_THIS";
case Icode_NAME_AND_THIS_OPTIONAL:
return "NAME_AND_THIS_OPTIONAL";
case Icode_PROP_AND_THIS_OPTIONAL:
return "PROP_AND_THIS_OPTIONAL";
case Icode_ELEM_AND_THIS_OPTIONAL:
return "ELEM_AND_THIS_OPTIONAL";
case Icode_VALUE_AND_THIS_OPTIONAL:
return "VALUE_AND_THIS_OPTIONAL";
case Icode_CLOSURE_EXPR:
return "CLOSURE_EXPR";
case Icode_CLOSURE_STMT:
return "CLOSURE_STMT";
case Icode_CALLSPECIAL:
return "CALLSPECIAL";
case Icode_CALLSPECIAL_OPTIONAL:
return "CALLSPECIAL_OPTIONAL";
case Icode_RETUNDEF:
return "RETUNDEF";
case Icode_GOSUB:
Expand Down
69 changes: 66 additions & 3 deletions rhino/src/main/java/org/mozilla/javascript/Interpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ static void dumpICode(InterpreterData idata) {
}

case Icode_CALLSPECIAL:
case Icode_CALLSPECIAL_OPTIONAL:
{
int callType = iCode[pc] & 0xFF;
boolean isNew = (iCode[pc + 1] != 0);
Expand Down Expand Up @@ -830,6 +831,7 @@ private static int bytecodeSpan(int bytecode) {
return 1 + 2;

case Icode_CALLSPECIAL:
case Icode_CALLSPECIAL_OPTIONAL:
// call type
// is new
// line number
Expand Down Expand Up @@ -1742,6 +1744,15 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl
++stackTop;
stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx);
continue Loop;
case Icode_NAME_AND_THIS_OPTIONAL:
// stringReg: name
++stackTop;
stack[stackTop] =
ScriptRuntime.getNameFunctionAndThisOptional(
stringReg, cx, frame.scope);
++stackTop;
stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx);
continue Loop;
case Icode_PROP_AND_THIS:
{
Object obj = stack[stackTop];
Expand All @@ -1755,6 +1766,19 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl
stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx);
continue Loop;
}
case Icode_PROP_AND_THIS_OPTIONAL:
{
Object obj = stack[stackTop];
if (obj == DBL_MRK)
obj = ScriptRuntime.wrapNumber(sDbl[stackTop]);
// stringReg: property
stack[stackTop] =
ScriptRuntime.getPropFunctionAndThisOptional(
obj, stringReg, cx, frame.scope);
++stackTop;
stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx);
continue Loop;
}
case Icode_ELEM_AND_THIS:
{
Object obj = stack[stackTop - 1];
Expand All @@ -1769,6 +1793,20 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl
stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx);
continue Loop;
}
case Icode_ELEM_AND_THIS_OPTIONAL:
{
Object obj = stack[stackTop - 1];
if (obj == DBL_MRK)
obj = ScriptRuntime.wrapNumber(sDbl[stackTop - 1]);
Object id = stack[stackTop];
if (id == DBL_MRK)
id = ScriptRuntime.wrapNumber(sDbl[stackTop]);
stack[stackTop - 1] =
ScriptRuntime.getElemFunctionAndThisOptional(
obj, id, cx, frame.scope);
stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx);
continue Loop;
}
case Icode_VALUE_AND_THIS:
{
Object value = stack[stackTop];
Expand All @@ -1780,6 +1818,18 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl
stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx);
continue Loop;
}
case Icode_VALUE_AND_THIS_OPTIONAL:
{
Object value = stack[stackTop];
if (value == DBL_MRK)
value = ScriptRuntime.wrapNumber(sDbl[stackTop]);
stack[stackTop] =
ScriptRuntime.getValueFunctionAndThisOptional(
value, cx);
++stackTop;
stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx);
continue Loop;
}
case Icode_CALLSPECIAL:
{
if (instructionCounting) {
Expand All @@ -1788,7 +1838,18 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl
stackTop =
doCallSpecial(
cx, frame, stack, sDbl, stackTop, iCode,
indexReg);
indexReg, false);
continue Loop;
}
case Icode_CALLSPECIAL_OPTIONAL:
{
if (instructionCounting) {
cx.instructionCount += INVOCATION_COST;
}
stackTop =
doCallSpecial(
cx, frame, stack, sDbl, stackTop, iCode,
indexReg, true);
continue Loop;
}
case Token.CALL:
Expand Down Expand Up @@ -2968,7 +3029,8 @@ private static int doCallSpecial(
double[] sDbl,
int stackTop,
byte[] iCode,
int indexReg) {
int indexReg,
boolean isOptionalChainingCall) {
int callType = iCode[frame.pc] & 0xFF;
boolean isNew = (iCode[frame.pc + 1] != 0);
int sourceLine = getIndex(iCode, frame.pc + 2);
Expand Down Expand Up @@ -3002,7 +3064,8 @@ private static int doCallSpecial(
frame.thisObj,
callType,
frame.idata.itsSourceFile,
sourceLine);
sourceLine,
isOptionalChainingCall);
}
frame.pc += 4;
return stackTop;
Expand Down
2 changes: 2 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,8 @@ public boolean hasSideEffects() {
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_EXP:
case Token.ASSIGN_NULLISH:
case Token.ENTERWITH:
case Token.LEAVEWITH:
case Token.RETURN:
Expand Down
Loading
Loading