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

initial implementation of function rest parameter support #1451

Merged
merged 4 commits into from
Apr 28, 2024
Merged
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
1 change: 1 addition & 0 deletions src/org/mozilla/javascript/CodeGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ private void generateICodeFromTree(Node tree) {
itsData.argNames = scriptOrFn.getParamAndVarNames();
itsData.argIsConst = scriptOrFn.getParamAndVarConst();
itsData.argCount = scriptOrFn.getParamCount();
itsData.argsHasRest = scriptOrFn.hasRestParameter();

itsData.encodedSourceStart = scriptOrFn.getEncodedSourceStart();
itsData.encodedSourceEnd = scriptOrFn.getEncodedSourceEnd();
Expand Down
4 changes: 4 additions & 0 deletions src/org/mozilla/javascript/Decompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,10 @@ else if (nextToken == Token.NAME) {
i = printSourceString(source, i + 1, false, result);
continue;

case Token.DOTDOTDOT:
result.append("...");
break;

default:
// If we don't know how to decompile it, raise an exception.
throw new RuntimeException("Token: " + Token.name(source.charAt(i)));
Expand Down
10 changes: 7 additions & 3 deletions src/org/mozilla/javascript/IRFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -2450,11 +2450,15 @@ Node decompileFunctionHeader(FunctionNode fn) {
decompiler.addToken(Token.LP);
}
List<AstNode> params = fn.getParams();
for (int i = 0; i < params.size(); i++) {
decompile(params.get(i));
if (i < params.size() - 1) {
int last = params.size() - 1;
for (int i = 0; i <= last; i++) {
if (i > 0) {
decompiler.addToken(Token.COMMA);
}
if (fn.hasRestParameter() && i == last) {
decompiler.addToken(Token.DOTDOTDOT);
}
decompile(params.get(i));
}
if (!noParen) {
decompiler.addToken(Token.RP);
Expand Down
3 changes: 3 additions & 0 deletions src/org/mozilla/javascript/InterpretedFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ protected int getLanguageVersion() {

@Override
protected int getParamCount() {
if (idata.argsHasRest) {
return idata.argCount - 1;
}
return idata.argCount;
}

Expand Down
34 changes: 32 additions & 2 deletions src/org/mozilla/javascript/Interpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,21 @@ void initializeArgs(
if (idata.itsFunctionType == FunctionNode.ARROW_FUNCTION) {
scope =
ScriptRuntime.createArrowFunctionActivation(
fnOrScript, scope, args, idata.isStrict);
fnOrScript,
cx,
scope,
args,
idata.isStrict,
idata.argsHasRest);
} else {
scope =
ScriptRuntime.createFunctionActivation(
fnOrScript, scope, args, idata.isStrict);
fnOrScript,
cx,
scope,
args,
idata.isStrict,
idata.argsHasRest);
}
}
} else {
Expand Down Expand Up @@ -188,6 +198,26 @@ void initializeArgs(
for (int i = definedArgs; i != idata.itsMaxVars; ++i) {
stack[i] = Undefined.instance;
}

if (idata.argsHasRest) {
Object[] vals;
int offset = idata.argCount - 1;
if (argCount >= idata.argCount) {
vals = new Object[argCount - offset];

argShift = argShift + offset;
for (int valsIdx = 0; valsIdx != vals.length; ++argShift, ++valsIdx) {
Object val = args[argShift];
if (val == UniqueTag.DOUBLE_MARK) {
val = ScriptRuntime.wrapNumber(argsDbl[argShift]);
}
vals[valsIdx] = val;
}
} else {
vals = ScriptRuntime.emptyArgs;
}
stack[offset] = cx.newArray(scope, vals);
}
}

CallFrame cloneFrozen() {
Expand Down
1 change: 1 addition & 0 deletions src/org/mozilla/javascript/InterpreterData.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ private void init() {
String[] argNames;
boolean[] argIsConst;
int argCount;
boolean argsHasRest;

int itsMaxCalleeArgs;

Expand Down
32 changes: 27 additions & 5 deletions src/org/mozilla/javascript/NativeCall.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ static void init(Scriptable scope, boolean sealed) {

NativeCall(
NativeFunction function,
Context cx,
Scriptable scope,
Object[] args,
boolean isArrow,
boolean isStrict) {
boolean isStrict,
boolean argsHasRest) {
this.function = function;

setParentScope(scope);
Expand All @@ -44,10 +46,30 @@ static void init(Scriptable scope, boolean sealed) {
int paramAndVarCount = function.getParamAndVarCount();
int paramCount = function.getParamCount();
if (paramAndVarCount != 0) {
for (int i = 0; i < paramCount; ++i) {
String name = function.getParamOrVarName(i);
Object val = i < args.length ? args[i] : Undefined.instance;
defineProperty(name, val, PERMANENT);
if (argsHasRest) {
Object[] vals;
if (args.length >= paramCount) {
vals = new Object[args.length - paramCount];
System.arraycopy(args, paramCount, vals, 0, args.length - paramCount);
} else {
vals = ScriptRuntime.emptyArgs;
}

for (int i = 0; i < paramCount; ++i) {
String name = function.getParamOrVarName(i);
Object val = i < args.length ? args[i] : Undefined.instance;
defineProperty(name, val, PERMANENT);
}
defineProperty(
function.getParamOrVarName(paramCount),
cx.newArray(scope, vals),
PERMANENT);
} else {
for (int i = 0; i < paramCount; ++i) {
String name = function.getParamOrVarName(i);
Object val = i < args.length ? args[i] : Undefined.instance;
defineProperty(name, val, PERMANENT);
}
}
}

Expand Down
27 changes: 27 additions & 0 deletions src/org/mozilla/javascript/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -787,10 +787,20 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException {
do {
int tt = peekToken();
if (tt == Token.RP) {
if (fnNode.hasRestParameter()) {
// Error: parameter after rest parameter
reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg);
}

fnNode.putIntProp(Node.TRAILING_COMMA, 1);
break;
}
if (tt == Token.LB || tt == Token.LC) {
if (fnNode.hasRestParameter()) {
// Error: parameter after rest parameter
reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg);
}

AstNode expr = destructuringPrimaryExpr();
markDestructuring(expr);
fnNode.addParam(expr);
Expand All @@ -804,7 +814,24 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException {
defineSymbol(Token.LP, pname, false);
destructuring.put(pname, expr);
} else {
boolean wasRest = false;
if (tt == Token.DOTDOTDOT) {
if (fnNode.hasRestParameter()) {
// Error: parameter after rest parameter
reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg);
}

fnNode.setHasRestParameter(true);
wasRest = true;
consumeToken();
}

if (mustMatchToken(Token.NAME, "msg.no.parm", true)) {
if (!wasRest && fnNode.hasRestParameter()) {
// Error: parameter after rest parameter
reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg);
}

Name paramNameNode = createNameNode();
Comment jsdocNodeForName = getAndResetJsDoc();
if (jsdocNodeForName != null) {
Expand Down
70 changes: 65 additions & 5 deletions src/org/mozilla/javascript/ScriptRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,33 @@ public static Object[] padArguments(Object[] args, int count) {
return result;
}

/**
* Helper function for builtin objects that use the varargs form. ECMA function formal arguments
* are undefined if not supplied; this function pads the argument array out to the expected
* length, if necessary. Also the rest parameter array construction is done here.
*/
public static Object[] padAndRestArguments(
Context cx, Scriptable scope, Object[] args, int argCount) {
Object[] result = new Object[argCount];
int paramCount = argCount - 1;
if (args.length < paramCount) {
System.arraycopy(args, 0, result, 0, args.length);
Arrays.fill(result, args.length, paramCount, Undefined.instance);
} else {
System.arraycopy(args, 0, result, 0, paramCount);
}

Object[] restValues;
if (args.length > paramCount) {
restValues = new Object[args.length - paramCount];
System.arraycopy(args, paramCount, restValues, 0, restValues.length);
} else {
restValues = ScriptRuntime.emptyArgs;
}
result[paramCount] = cx.newArray(scope, restValues);
return result;
}

public static String escapeString(String s) {
return escapeString(s, '"');
}
Expand Down Expand Up @@ -4001,23 +4028,56 @@ public static void initScript(
}

/**
* @deprecated Use {@link #createFunctionActivation(NativeFunction, Scriptable, Object[],
* boolean)} instead
* @deprecated Use {@link #createFunctionActivation(NativeFunction, Context, Scriptable,
* Object[], boolean, boolean)} instead
*/
@Deprecated
public static Scriptable createFunctionActivation(
NativeFunction funObj, Scriptable scope, Object[] args) {
return createFunctionActivation(funObj, scope, args, false);
return createFunctionActivation(
funObj, Context.getCurrentContext(), scope, args, false, false);
}

/**
* @deprecated Use {@link #createFunctionActivation(NativeFunction, Context, Scriptable,
* Object[], boolean, boolean)} instead
*/
@Deprecated
public static Scriptable createFunctionActivation(
NativeFunction funObj, Scriptable scope, Object[] args, boolean isStrict) {
return new NativeCall(funObj, scope, args, false, isStrict);
return new NativeCall(
funObj, Context.getCurrentContext(), scope, args, false, isStrict, false);
}

public static Scriptable createFunctionActivation(
NativeFunction funObj,
Context cx,
Scriptable scope,
Object[] args,
boolean isStrict,
boolean argsHasRest) {
return new NativeCall(funObj, cx, scope, args, false, isStrict, argsHasRest);
}

/**
* @deprecated Use {@link #createArrowFunctionActivation(NativeFunction, Context, Scriptable,
* Object[], boolean, boolean)} instead
*/
@Deprecated
public static Scriptable createArrowFunctionActivation(
NativeFunction funObj, Scriptable scope, Object[] args, boolean isStrict) {
return new NativeCall(funObj, scope, args, true, isStrict);
return new NativeCall(
funObj, Context.getCurrentContext(), scope, args, true, isStrict, false);
}

public static Scriptable createArrowFunctionActivation(
NativeFunction funObj,
Context cx,
Scriptable scope,
Object[] args,
boolean isStrict,
boolean argsHasRest) {
return new NativeCall(funObj, cx, scope, args, true, isStrict, argsHasRest);
}

public static void enterActivationFunction(Context cx, Scriptable scope) {
Expand Down
3 changes: 2 additions & 1 deletion src/org/mozilla/javascript/Token.java
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ public static enum CommentType {
TEMPLATE_CHARS = 171, // template literal - literal section
TEMPLATE_LITERAL_SUBST = 172, // template literal - substitution
TAGGED_TEMPLATE_LITERAL = 173, // template literal - tagged/handler
LAST_TOKEN = 173;
DOTDOTDOT = 174, // spread/rest ...
LAST_TOKEN = 174;

/**
* Returns a name for the token. If Rhino is compiled with certain hardcoded debugging flags in
Expand Down
4 changes: 4 additions & 0 deletions src/org/mozilla/javascript/TokenStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,10 @@ && peekChar() == '!'
return Token.COLON;
case '.':
if (matchChar('.')) {
if (parser.compilerEnv.getLanguageVersion() >= Context.VERSION_1_8
&& matchChar('.')) {
return Token.DOTDOTDOT;
}
return Token.DOTDOT;
} else if (matchChar('(')) {
return Token.DOTQUERY;
Expand Down
10 changes: 10 additions & 0 deletions src/org/mozilla/javascript/ast/FunctionNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public static enum Form {
private Form functionForm = Form.FUNCTION;
private int lp = -1;
private int rp = -1;
private boolean hasRestParameter;

// codegen variables
private int functionType;
Expand Down Expand Up @@ -284,6 +285,15 @@ public void setIsES6Generator() {
isGenerator = true;
}

@Override
public boolean hasRestParameter() {
return hasRestParameter;
}

public void setHasRestParameter(boolean hasRestParameter) {
this.hasRestParameter = hasRestParameter;
}

public void addResumptionPoint(Node target) {
if (generatorResumePoints == null) generatorResumePoints = new ArrayList<>();
generatorResumePoints.add(target);
Expand Down
4 changes: 4 additions & 0 deletions src/org/mozilla/javascript/ast/ScriptNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ public boolean[] getParamAndVarConst() {
return isConsts;
}

public boolean hasRestParameter() {
return false;
}

void addSymbol(Symbol symbol) {
if (variableNames != null) codeBug();
if (symbol.getDeclType() == Token.LP) {
Expand Down
Loading
Loading