diff --git a/src/org/mozilla/javascript/CodeGenerator.java b/src/org/mozilla/javascript/CodeGenerator.java index 9093ab0af2..095406b970 100644 --- a/src/org/mozilla/javascript/CodeGenerator.java +++ b/src/org/mozilla/javascript/CodeGenerator.java @@ -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(); diff --git a/src/org/mozilla/javascript/Decompiler.java b/src/org/mozilla/javascript/Decompiler.java index cbd5ab63df..709e49cb78 100644 --- a/src/org/mozilla/javascript/Decompiler.java +++ b/src/org/mozilla/javascript/Decompiler.java @@ -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))); diff --git a/src/org/mozilla/javascript/IRFactory.java b/src/org/mozilla/javascript/IRFactory.java index b0ff9559eb..ce77106b2f 100644 --- a/src/org/mozilla/javascript/IRFactory.java +++ b/src/org/mozilla/javascript/IRFactory.java @@ -2450,11 +2450,15 @@ Node decompileFunctionHeader(FunctionNode fn) { decompiler.addToken(Token.LP); } List 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); diff --git a/src/org/mozilla/javascript/InterpretedFunction.java b/src/org/mozilla/javascript/InterpretedFunction.java index aeb660ba01..f72608f337 100644 --- a/src/org/mozilla/javascript/InterpretedFunction.java +++ b/src/org/mozilla/javascript/InterpretedFunction.java @@ -133,6 +133,9 @@ protected int getLanguageVersion() { @Override protected int getParamCount() { + if (idata.argsHasRest) { + return idata.argCount - 1; + } return idata.argCount; } diff --git a/src/org/mozilla/javascript/Interpreter.java b/src/org/mozilla/javascript/Interpreter.java index af904e2686..314e434b4b 100644 --- a/src/org/mozilla/javascript/Interpreter.java +++ b/src/org/mozilla/javascript/Interpreter.java @@ -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 { @@ -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() { diff --git a/src/org/mozilla/javascript/InterpreterData.java b/src/org/mozilla/javascript/InterpreterData.java index 8e95210d0f..9551dabf9d 100644 --- a/src/org/mozilla/javascript/InterpreterData.java +++ b/src/org/mozilla/javascript/InterpreterData.java @@ -68,6 +68,7 @@ private void init() { String[] argNames; boolean[] argIsConst; int argCount; + boolean argsHasRest; int itsMaxCalleeArgs; diff --git a/src/org/mozilla/javascript/NativeCall.java b/src/org/mozilla/javascript/NativeCall.java index 33e9a4cc8b..d8474761c3 100644 --- a/src/org/mozilla/javascript/NativeCall.java +++ b/src/org/mozilla/javascript/NativeCall.java @@ -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); @@ -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); + } } } diff --git a/src/org/mozilla/javascript/Parser.java b/src/org/mozilla/javascript/Parser.java index 9cc30fe574..8a31a212a4 100644 --- a/src/org/mozilla/javascript/Parser.java +++ b/src/org/mozilla/javascript/Parser.java @@ -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); @@ -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) { diff --git a/src/org/mozilla/javascript/ScriptRuntime.java b/src/org/mozilla/javascript/ScriptRuntime.java index 2a02469156..b449d37c82 100644 --- a/src/org/mozilla/javascript/ScriptRuntime.java +++ b/src/org/mozilla/javascript/ScriptRuntime.java @@ -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, '"'); } @@ -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) { diff --git a/src/org/mozilla/javascript/Token.java b/src/org/mozilla/javascript/Token.java index ec424c4950..de0961d938 100644 --- a/src/org/mozilla/javascript/Token.java +++ b/src/org/mozilla/javascript/Token.java @@ -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 diff --git a/src/org/mozilla/javascript/TokenStream.java b/src/org/mozilla/javascript/TokenStream.java index e84d1c9a07..503ad01fc7 100644 --- a/src/org/mozilla/javascript/TokenStream.java +++ b/src/org/mozilla/javascript/TokenStream.java @@ -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; diff --git a/src/org/mozilla/javascript/ast/FunctionNode.java b/src/org/mozilla/javascript/ast/FunctionNode.java index a19f7ce4f6..7c5d07d6e8 100644 --- a/src/org/mozilla/javascript/ast/FunctionNode.java +++ b/src/org/mozilla/javascript/ast/FunctionNode.java @@ -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; @@ -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); diff --git a/src/org/mozilla/javascript/ast/ScriptNode.java b/src/org/mozilla/javascript/ast/ScriptNode.java index f27023fcb5..bfe31daf96 100644 --- a/src/org/mozilla/javascript/ast/ScriptNode.java +++ b/src/org/mozilla/javascript/ast/ScriptNode.java @@ -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) { diff --git a/src/org/mozilla/javascript/optimizer/BodyCodegen.java b/src/org/mozilla/javascript/optimizer/BodyCodegen.java index 4ad3a167da..c8d048a40b 100644 --- a/src/org/mozilla/javascript/optimizer/BodyCodegen.java +++ b/src/org/mozilla/javascript/optimizer/BodyCodegen.java @@ -115,15 +115,19 @@ private void generateGenerator() { // generators are forced to have an activation record cfw.addALoad(funObjLocal); + cfw.addALoad(contextLocal); cfw.addALoad(variableObjectLocal); cfw.addALoad(argsLocal); cfw.addPush(scriptOrFn.isInStrictMode()); + cfw.addPush(scriptOrFn.hasRestParameter()); addScriptRuntimeInvoke( "createFunctionActivation", "(Lorg/mozilla/javascript/NativeFunction;" + + "Lorg/mozilla/javascript/Context;" + "Lorg/mozilla/javascript/Scriptable;" + "[Ljava/lang/Object;" + "Z" + + "Z" + ")Lorg/mozilla/javascript/Scriptable;"); cfw.addAStore(variableObjectLocal); @@ -322,18 +326,34 @@ private void generatePrologue() { int parmCount = scriptOrFn.getParamCount(); if (parmCount > 0 && !inDirectCallFunction) { // Set up args array - // check length of arguments, pad if need be - cfw.addALoad(argsLocal); - cfw.add(ByteCode.ARRAYLENGTH); - cfw.addPush(parmCount); - int label = cfw.acquireLabel(); - cfw.add(ByteCode.IF_ICMPGE, label); - cfw.addALoad(argsLocal); - cfw.addPush(parmCount); - addScriptRuntimeInvoke( - "padArguments", "([Ljava/lang/Object;I" + ")[Ljava/lang/Object;"); - cfw.addAStore(argsLocal); - cfw.markLabel(label); + if (scriptOrFn.hasRestParameter()) { + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(argsLocal); + cfw.addPush(parmCount); + addScriptRuntimeInvoke( + "padAndRestArguments", + "(" + + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;" + + "I" + + ")[Ljava/lang/Object;"); + cfw.addAStore(argsLocal); + } else { + // check length of arguments, pad if need be + cfw.addALoad(argsLocal); + cfw.add(ByteCode.ARRAYLENGTH); + cfw.addPush(parmCount); + int label = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ICMPGE, label); + cfw.addALoad(argsLocal); + cfw.addPush(parmCount); + addScriptRuntimeInvoke( + "padArguments", "([Ljava/lang/Object;I)[Ljava/lang/Object;"); + cfw.addAStore(argsLocal); + cfw.markLabel(label); + } } int paramCount = fnCurrent.fnode.getParamCount(); @@ -399,17 +419,21 @@ private void generatePrologue() { if (fnCurrent != null) { debugVariableName = "activation"; cfw.addALoad(funObjLocal); + cfw.addALoad(contextLocal); cfw.addALoad(variableObjectLocal); cfw.addALoad(argsLocal); + cfw.addPush(scriptOrFn.isInStrictMode()); + cfw.addPush(scriptOrFn.hasRestParameter()); String methodName = isArrow ? "createArrowFunctionActivation" : "createFunctionActivation"; - cfw.addPush(scriptOrFn.isInStrictMode()); addScriptRuntimeInvoke( methodName, "(Lorg/mozilla/javascript/NativeFunction;" + + "Lorg/mozilla/javascript/Context;" + "Lorg/mozilla/javascript/Scriptable;" + "[Ljava/lang/Object;" + "Z" + + "Z" + ")Lorg/mozilla/javascript/Scriptable;"); cfw.addAStore(variableObjectLocal); cfw.addALoad(contextLocal); diff --git a/src/org/mozilla/javascript/optimizer/Codegen.java b/src/org/mozilla/javascript/optimizer/Codegen.java index 997530db6d..f6257766b0 100644 --- a/src/org/mozilla/javascript/optimizer/Codegen.java +++ b/src/org/mozilla/javascript/optimizer/Codegen.java @@ -743,7 +743,8 @@ private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encoded final int Do_getEncodedSource = 4; final int Do_getParamOrVarConst = 5; final int Do_isGeneratorFunction = 6; - final int SWITCH_COUNT = 7; + final int Do_hasRestParameter = 7; + final int SWITCH_COUNT = 8; for (int methodIndex = 0; methodIndex != SWITCH_COUNT; ++methodIndex) { if (methodIndex == Do_getEncodedSource && encodedSource == null) { @@ -786,6 +787,10 @@ private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encoded methodLocals = 1; // Only this cfw.startMethod("isGeneratorFunction", "()Z", ACC_PROTECTED); break; + case Do_hasRestParameter: + methodLocals = 1; // Only this + cfw.startMethod("hasRestParameter", "()Z", ACC_PUBLIC); + break; default: throw Kit.codeBug(); } @@ -830,7 +835,11 @@ private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encoded case Do_getParamCount: // Push number of defined parameters - cfw.addPush(n.getParamCount()); + if (n.hasRestParameter()) { + cfw.addPush(n.getParamCount() - 1); + } else { + cfw.addPush(n.getParamCount()); + } cfw.add(ByteCode.IRETURN); break; @@ -920,6 +929,12 @@ private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encoded cfw.add(ByteCode.IRETURN); break; + case Do_hasRestParameter: + // Push boolean of defined hasRestParameter + cfw.addPush(n.hasRestParameter()); + cfw.add(ByteCode.IRETURN); + break; + case Do_getEncodedSource: // Push number encoded source start and end // to prepare for encodedSource.substring(start, end) diff --git a/src/org/mozilla/javascript/resources/Messages.properties b/src/org/mozilla/javascript/resources/Messages.properties index d1d161939e..96d2dd50b1 100644 --- a/src/org/mozilla/javascript/resources/Messages.properties +++ b/src/org/mozilla/javascript/resources/Messages.properties @@ -312,6 +312,9 @@ msg.no.parm =\ msg.no.paren.after.parms =\ missing ) after formal parameters +msg.parm.after.rest =\ + parameter after rest parameter + msg.no.brace.body =\ missing '{' before function body diff --git a/src/org/mozilla/javascript/resources/Messages_fr.properties b/src/org/mozilla/javascript/resources/Messages_fr.properties index 2082b17c72..c464ab1a82 100644 --- a/src/org/mozilla/javascript/resources/Messages_fr.properties +++ b/src/org/mozilla/javascript/resources/Messages_fr.properties @@ -276,6 +276,9 @@ msg.no.parm =\ msg.no.paren.after.parms =\ il manque '')'' apr\u00E8s les param\u00E8tres formels +msg.parm.after.rest =\ + parameter after rest parameter + msg.no.brace.body =\ il manque ''{'' avant le corps d''une fonction diff --git a/testsrc/org/mozilla/javascript/tests/Test262SuiteTest.java b/testsrc/org/mozilla/javascript/tests/Test262SuiteTest.java index 70ee634479..83c4817885 100644 --- a/testsrc/org/mozilla/javascript/tests/Test262SuiteTest.java +++ b/testsrc/org/mozilla/javascript/tests/Test262SuiteTest.java @@ -111,7 +111,6 @@ public class Test262SuiteTest { "regexp-lookbehind", "regexp-named-groups", "regexp-unicode-property-escapes", - "rest-parameters", "super", "String.prototype.matchAll", "Symbol.matchAll", diff --git a/testsrc/org/mozilla/javascript/tests/es6/FunctionsRestParametersTest.java b/testsrc/org/mozilla/javascript/tests/es6/FunctionsRestParametersTest.java new file mode 100644 index 0000000000..84c4755d53 --- /dev/null +++ b/testsrc/org/mozilla/javascript/tests/es6/FunctionsRestParametersTest.java @@ -0,0 +1,318 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.javascript.tests.es6; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.EvaluatorException; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.tests.Utils; + +/** + * Tests for Functions Rest Parameters. + * + * @author Ronald Brill + */ +public class FunctionsRestParametersTest { + + private Context cx; + private ScriptableObject scope; + + @Before + public void setUp() { + cx = Context.enter(); + cx.setLanguageVersion(Context.VERSION_ES6); + scope = cx.initStandardObjects(); + } + + @After + public void tearDown() { + Context.exit(); + } + + @Test + public void oneRestArg() { + String code = + "function rest(...restArgs) {\n" + + " return restArgs;\n" + + "}\n" + + "rest(1, 'abc', 2, '##').toString();\n"; + + test("1,abc,2,##", code); + } + + @Test + public void oneRestArgActivation() { + String code = + "function rest(...restArgs) {\n" + + " try {\n" + + " return restArgs;\n" + + " } catch {}\n" + + "}\n" + + " rest(1, 'abc', 2, '##').toString();\n"; + + test("1,abc,2,##", code); + } + + @Test + public void oneRestArgNothingProvided() { + String code = + "function rest(...restArgs) {\n" + + " return restArgs;\n" + + "}\n" + + "var r = rest();\n" + + "'' + Array.isArray(r) + '-' + r.length;\n"; + + test("true-0", code); + } + + @Test + public void oneRestArgNothingProvidedActivation() { + String code = + "function rest(...restArgs) {\n" + + " try {\n" + + " return restArgs;\n" + + " } catch {}\n" + + "}\n" + + "var r = rest();\n" + + "'' + Array.isArray(r) + '-' + r.length;\n"; + + test("true-0", code); + } + + @Test + public void oneRestArgOneProvided() { + String code = + "function rest(...restArgs) {\n" + + " return restArgs;\n" + + "}\n" + + "var r = rest('xy');\n" + + "'' + Array.isArray(r) + '-' + r.length;\n"; + + test("true-1", code); + } + + @Test + public void oneRestArgOneProvidedActivation() { + String code = + "function rest(...restArgs) {\n" + + " try {\n" + + " return restArgs;\n" + + " } catch {}\n" + + "}\n" + + "var r = rest('xy');\n" + + "'' + Array.isArray(r) + '-' + r.length;\n"; + + test("true-1", code); + } + + @Test + public void twoRestArg() { + String code = + "function rest(v, ...restArgs) {\n" + + " return restArgs;\n" + + "}\n" + + "rest(1, 'abc', 2, '##').toString();\n"; + + test("abc,2,##", code); + } + + @Test + public void twoRestArgActivation() { + String code = + "function rest(v, ...restArgs) {\n" + + " try {\n" + + " return restArgs;\n" + + " } catch {}\n" + + "}\n" + + "rest(1, 'abc', 2, '##').toString();\n"; + + test("abc,2,##", code); + } + + @Test + public void twoRestArgNothingProvided() { + String code = + "function rest(v, ...restArgs) {\n" + + " return '' + typeof v + ' - ' + Array.isArray(restArgs) + '-' + restArgs.length;\n" + + "}\n" + + "rest();\n"; + + test("undefined - true-0", code); + } + + @Test + public void twoRestArgNothingProvidedActivation() { + String code = + "function rest(v, ...restArgs) {\n" + + " try {\n" + + " return '' + typeof v + ' - ' + Array.isArray(restArgs) + '-' + restArgs.length;\n" + + " } catch {}\n" + + "}\n" + + "rest();\n"; + + test("undefined - true-0", code); + } + + @Test + public void twoRestArgOneProvided() { + String code = + "function rest(v, ...restArgs) {\n" + + " return v + ' - ' + Array.isArray(restArgs) + '-' + restArgs.length;\n" + + "}\n" + + "rest('77');"; + + test("77 - true-0", code); + } + + @Test + public void twoRestArgOneProvidedActivation() { + String code = + "function rest(v, ...restArgs) {\n" + + " try {\n" + + " return v + ' - ' + Array.isArray(restArgs) + '-' + restArgs.length;\n" + + " } catch {}\n" + + "}\n" + + "rest('77');"; + + test("77 - true-0", code); + } + + @Test + public void arguments() { + String code = + "function rest(arg, ...restArgs) {\n" + + " return arguments.length;\n" + + "}\n" + + "'' + rest('77') + '-' + rest(1, 2, 3, 4);\n"; + + test("1-4", code); + } + + @Test + public void argumentsActivation() { + String code = + "function rest(arg, ...restArgs) {\n" + + " try {\n" + + " return arguments.length;\n" + + " } catch {}\n" + + "}\n" + + "'' + rest('77') + '-' + rest(1, 2, 3, 4);\n"; + + test("1-4", code); + } + + @Test + public void argLength() { + String code = + "function rest(...restArgs) {\n" + + " return restArgs.length;\n" + + "}\n" + + " rest(1,2) + '-' + rest(1) + '-' + rest();\n"; + + test("2-1-0", code); + } + + @Test + public void argLengthActivation() { + String code = + "function rest(...restArgs) {\n" + + " try {\n" + + " return restArgs.length;\n" + + " } catch {}\n" + + "}\n" + + " rest(1,2) + '-' + rest(1) + '-' + rest();\n"; + + test("2-1-0", code); + } + + @Test + public void length() { + String code = + "function foo1(...restArgs) {}\n" + + "function foo2(arg, ...restArgs) {}\n" + + "foo1.length + '-' + foo2.length;\n"; + + test("0-1", code); + } + + @Test + public void string1() { + String code = + "function rest(...restArgs) {\n" + + " return restArgs.length;\n" + + "}\n" + + "rest.toString();\n"; + + test("\nfunction rest(...restArgs) {\n return restArgs.length;\n}\n", code); + } + + @Test + public void string2() { + String code = + "function rest( arg , ...restArgs ) {\n" + + " return restArgs.length;\n" + + "}\n" + + "rest.toString();\n"; + + test("\nfunction rest(arg, ...restArgs) {\n return restArgs.length;\n}\n", code); + } + + @Test + public void trailingComma() { + String code = "function rest(...restArgs,) {\n" + " return restArgs;\n" + "}\n"; + + assertEvaluatorException("parameter after rest parameter (test#1)", code); + } + + @Test + public void twoRestParams() { + String code = "function rest(...rest1, ...rest2) {\n" + " return restArgs;\n" + "}\n"; + + assertEvaluatorException("parameter after rest parameter (test#1)", code); + } + + @Test + public void paramAfterRestParam() { + String code = "function rest(...rest1, param) {\n" + " return restArgs;\n" + "}\n"; + + assertEvaluatorException("parameter after rest parameter (test#1)", code); + } + + private static void test(Object expected, String js) { + Utils.runWithAllOptimizationLevels( + cx -> { + cx.setLanguageVersion(Context.VERSION_ES6); + ScriptableObject scope = cx.initStandardObjects(); + + Object result = cx.evaluateString(scope, js, "test", 1, null); + assertEquals(expected, result); + + return null; + }); + } + + private static void assertEvaluatorException(String expected, String js) { + Utils.runWithAllOptimizationLevels( + cx -> { + cx.setLanguageVersion(Context.VERSION_ES6); + ScriptableObject scope = cx.initStandardObjects(); + + try { + cx.evaluateString(scope, js, "test", 1, null); + fail("EvaluatorException expected"); + } catch (EvaluatorException e) { + assertEquals(expected, e.getMessage()); + } + + return null; + }); + } +} diff --git a/testsrc/test262.properties b/testsrc/test262.properties index 332b9c6711..3770a6fbc5 100644 --- a/testsrc/test262.properties +++ b/testsrc/test262.properties @@ -2502,7 +2502,7 @@ language/arguments-object 189/260 (72.69%) mapped/nonwritable-nonenumerable-nonconfigurable-descriptors-set-by-param.js non-strict unmapped/via-params-dflt.js unmapped/via-params-dstr.js non-strict - unmapped/via-params-rest.js + unmapped/via-params-rest.js non-strict arguments-caller.js async-gen-meth-args-trailing-comma-multiple.js {unsupported: [async-iteration, async]} async-gen-meth-args-trailing-comma-null.js {unsupported: [async-iteration, async]} @@ -3045,7 +3045,7 @@ language/expressions/addition 9/48 (18.75%) get-symbol-to-prim-err.js order-of-evaluation.js -language/expressions/arrow-function 210/333 (63.06%) +language/expressions/arrow-function 209/333 (62.76%) dstr/ary-init-iter-close.js dstr/ary-init-iter-get-err.js dstr/ary-init-iter-get-err-array-prototype.js @@ -3224,7 +3224,7 @@ language/expressions/arrow-function 210/333 (63.06%) syntax/arrowparameters-cover-initialize-2.js syntax/arrowparameters-cover-rest-concisebody-functionbody.js syntax/arrowparameters-cover-rest-lineterminator-concisebody-functionbody.js - array-destructuring-param-strict-body.js {unsupported: [rest-parameters]} + array-destructuring-param-strict-body.js ArrowFunction_restricted-properties.js dflt-params-abrupt.js {unsupported: [default-parameters]} dflt-params-arg-val-not-undefined.js {unsupported: [default-parameters]} @@ -3243,12 +3243,11 @@ language/expressions/arrow-function 210/333 (63.06%) lexical-super-property.js lexical-super-property-from-within-constructor.js lexical-supercall-from-immediately-invoked-arrow.js - object-destructuring-param-strict-body.js {unsupported: [rest-parameters]} + object-destructuring-param-strict-body.js param-dflt-yield-expr.js {unsupported: [default-parameters]} param-dflt-yield-id-non-strict.js {unsupported: [default-parameters]} param-dflt-yield-id-strict.js {unsupported: [default-parameters]} params-duplicate.js non-strict - rest-param-strict-body.js {unsupported: [rest-parameters]} scope-body-lex-distinct.js non-strict scope-param-elem-var-close.js non-strict scope-param-elem-var-open.js non-strict @@ -3645,7 +3644,7 @@ language/expressions/function 203/248 (81.85%) early-errors 4/4 (100.0%) arguments-with-arguments-fn.js non-strict arguments-with-arguments-lex.js non-strict - array-destructuring-param-strict-body.js {unsupported: [rest-parameters]} + array-destructuring-param-strict-body.js dflt-params-abrupt.js {unsupported: [default-parameters]} dflt-params-arg-val-not-undefined.js {unsupported: [default-parameters]} dflt-params-arg-val-undefined.js {unsupported: [default-parameters]} @@ -3659,7 +3658,7 @@ language/expressions/function 203/248 (81.85%) length-dflt.js {unsupported: [default-parameters]} name-arguments-strict-body.js non-strict name-eval-strict-body.js non-strict - object-destructuring-param-strict-body.js {unsupported: [rest-parameters]} + object-destructuring-param-strict-body.js param-dflt-yield-non-strict.js {unsupported: [default-parameters]} param-dflt-yield-strict.js {unsupported: [default-parameters]} param-duplicated-strict-body-1.js non-strict @@ -3668,7 +3667,7 @@ language/expressions/function 203/248 (81.85%) param-eval-strict-body.js non-strict params-dflt-args-unmapped.js {unsupported: [default-parameters]} params-dflt-ref-arguments.js {unsupported: [default-parameters]} - rest-param-strict-body.js {unsupported: [rest-parameters]} + rest-param-strict-body.js scope-body-lex-distinct.js non-strict scope-name-var-open-non-strict.js non-strict scope-name-var-open-strict.js strict @@ -3855,7 +3854,7 @@ language/expressions/generators 227/275 (82.55%) dstr/obj-ptrn-rest-val-obj.js {unsupported: [object-rest]} arguments-with-arguments-fn.js non-strict arguments-with-arguments-lex.js non-strict - array-destructuring-param-strict-body.js {unsupported: [rest-parameters]} + array-destructuring-param-strict-body.js default-proto.js dflt-params-abrupt.js {unsupported: [default-parameters]} dflt-params-arg-val-not-undefined.js {unsupported: [default-parameters]} @@ -3878,7 +3877,7 @@ language/expressions/generators 227/275 (82.55%) named-yield-spread-arr-multiple.js named-yield-spread-arr-single.js named-yield-spread-obj.js - object-destructuring-param-strict-body.js {unsupported: [rest-parameters]} + object-destructuring-param-strict-body.js param-dflt-yield.js {unsupported: [default-parameters]} params-dflt-args-unmapped.js {unsupported: [default-parameters]} params-dflt-ref-arguments.js {unsupported: [default-parameters]} @@ -3887,7 +3886,7 @@ language/expressions/generators 227/275 (82.55%) prototype-own-properties.js prototype-relation-to-function.js prototype-value.js - rest-param-strict-body.js {unsupported: [rest-parameters]} + rest-param-strict-body.js scope-body-lex-distinct.js non-strict scope-name-var-close.js non-interpreted scope-name-var-open-non-strict.js non-strict @@ -3962,7 +3961,7 @@ language/expressions/multiplication 4/40 (10.0%) bigint-wrapped-values.js {unsupported: [computed-property-names]} order-of-evaluation.js -language/expressions/object 839/1081 (77.61%) +language/expressions/object 836/1081 (77.34%) dstr/async-gen-meth-ary-init-iter-close.js {unsupported: [async-iteration, async]} dstr/async-gen-meth-ary-init-iter-get-err.js {unsupported: [async-iteration]} dstr/async-gen-meth-ary-init-iter-get-err-array-prototype.js {unsupported: [async-iteration]} @@ -4506,7 +4505,7 @@ language/expressions/object 839/1081 (77.61%) method-definition/async-gen-await-as-identifier-reference-escaped.js {unsupported: [async-iteration]} method-definition/async-gen-await-as-label-identifier.js {unsupported: [async-iteration]} method-definition/async-gen-await-as-label-identifier-escaped.js {unsupported: [async-iteration]} - method-definition/async-gen-meth-array-destructuring-param-strict-body.js {unsupported: [rest-parameters, async-iteration]} + method-definition/async-gen-meth-array-destructuring-param-strict-body.js {unsupported: [async-iteration]} method-definition/async-gen-meth-dflt-params-abrupt.js {unsupported: [default-parameters, async-iteration]} method-definition/async-gen-meth-dflt-params-arg-val-not-undefined.js {unsupported: [default-parameters, async-iteration, async]} method-definition/async-gen-meth-dflt-params-arg-val-undefined.js {unsupported: [default-parameters, async-iteration, async]} @@ -4518,10 +4517,10 @@ language/expressions/object 839/1081 (77.61%) method-definition/async-gen-meth-dflt-params-trailing-comma.js {unsupported: [async-iteration, async]} method-definition/async-gen-meth-escaped-async.js {unsupported: [async-iteration]} method-definition/async-gen-meth-eval-var-scope-syntax-err.js {unsupported: [default-parameters, async-iteration]} - method-definition/async-gen-meth-object-destructuring-param-strict-body.js {unsupported: [rest-parameters, async-iteration]} + method-definition/async-gen-meth-object-destructuring-param-strict-body.js {unsupported: [async-iteration]} method-definition/async-gen-meth-params-trailing-comma-multiple.js {unsupported: [async-iteration, async]} method-definition/async-gen-meth-params-trailing-comma-single.js {unsupported: [async-iteration, async]} - method-definition/async-gen-meth-rest-param-strict-body.js {unsupported: [rest-parameters, async-iteration]} + method-definition/async-gen-meth-rest-param-strict-body.js {unsupported: [async-iteration]} method-definition/async-gen-meth-rest-params-trailing-comma-early-error.js {unsupported: [async-iteration]} method-definition/async-gen-yield-as-binding-identifier.js {unsupported: [async-iteration]} method-definition/async-gen-yield-as-binding-identifier-escaped.js {unsupported: [async-iteration]} @@ -4598,7 +4597,7 @@ language/expressions/object 839/1081 (77.61%) method-definition/async-gen-yield-star-sync-next.js {unsupported: [async-iteration, async]} method-definition/async-gen-yield-star-sync-return.js {unsupported: [async-iteration, async]} method-definition/async-gen-yield-star-sync-throw.js {unsupported: [async-iteration, async]} - method-definition/async-meth-array-destructuring-param-strict-body.js {unsupported: [rest-parameters, async-iteration]} + method-definition/async-meth-array-destructuring-param-strict-body.js {unsupported: [async-iteration]} method-definition/async-meth-dflt-params-abrupt.js {unsupported: [default-parameters, async-functions, async]} method-definition/async-meth-dflt-params-arg-val-not-undefined.js {unsupported: [default-parameters, async-functions, async]} method-definition/async-meth-dflt-params-arg-val-undefined.js {unsupported: [default-parameters, async-functions, async]} @@ -4610,10 +4609,10 @@ language/expressions/object 839/1081 (77.61%) method-definition/async-meth-dflt-params-trailing-comma.js {unsupported: [async-functions, async]} method-definition/async-meth-escaped-async.js {unsupported: [async-functions]} method-definition/async-meth-eval-var-scope-syntax-err.js {unsupported: [default-parameters, async-functions, async]} - method-definition/async-meth-object-destructuring-param-strict-body.js {unsupported: [rest-parameters, async-iteration]} + method-definition/async-meth-object-destructuring-param-strict-body.js {unsupported: [async-iteration]} method-definition/async-meth-params-trailing-comma-multiple.js {unsupported: [async-functions, async]} method-definition/async-meth-params-trailing-comma-single.js {unsupported: [async-functions, async]} - method-definition/async-meth-rest-param-strict-body.js {unsupported: [rest-parameters, async-iteration]} + method-definition/async-meth-rest-param-strict-body.js {unsupported: [async-iteration]} method-definition/async-meth-rest-params-trailing-comma-early-error.js {unsupported: [async-iteration]} method-definition/async-super-call-body.js {unsupported: [async]} method-definition/async-super-call-param.js {unsupported: [async]} @@ -4628,7 +4627,6 @@ language/expressions/object 839/1081 (77.61%) method-definition/escaped-set-t.js method-definition/fn-name-fn.js method-definition/fn-name-gen.js - method-definition/gen-meth-array-destructuring-param-strict-body.js {unsupported: [rest-parameters]} method-definition/gen-meth-dflt-params-abrupt.js {unsupported: [default-parameters]} method-definition/gen-meth-dflt-params-arg-val-not-undefined.js {unsupported: [default-parameters]} method-definition/gen-meth-dflt-params-arg-val-undefined.js {unsupported: [default-parameters]} @@ -4639,10 +4637,8 @@ language/expressions/object 839/1081 (77.61%) method-definition/gen-meth-dflt-params-rest.js {unsupported: [default-parameters]} method-definition/gen-meth-dflt-params-trailing-comma.js method-definition/gen-meth-eval-var-scope-syntax-err.js {unsupported: [default-parameters]} - method-definition/gen-meth-object-destructuring-param-strict-body.js {unsupported: [rest-parameters]} method-definition/gen-meth-params-trailing-comma-multiple.js method-definition/gen-meth-params-trailing-comma-single.js - method-definition/gen-meth-rest-param-strict-body.js {unsupported: [rest-parameters]} method-definition/gen-yield-identifier-non-strict.js non-strict method-definition/gen-yield-identifier-spread-non-strict.js non-strict method-definition/gen-yield-spread-arr-multiple.js @@ -4666,7 +4662,7 @@ language/expressions/object 839/1081 (77.61%) method-definition/generator-return.js method-definition/generator-super-prop-body.js method-definition/generator-super-prop-param.js {unsupported: [super, default-parameters]} - method-definition/meth-array-destructuring-param-strict-body.js {unsupported: [rest-parameters]} + method-definition/meth-array-destructuring-param-strict-body.js method-definition/meth-dflt-params-abrupt.js {unsupported: [default-parameters]} method-definition/meth-dflt-params-arg-val-not-undefined.js {unsupported: [default-parameters]} method-definition/meth-dflt-params-arg-val-undefined.js {unsupported: [default-parameters]} @@ -4677,8 +4673,8 @@ language/expressions/object 839/1081 (77.61%) method-definition/meth-dflt-params-rest.js {unsupported: [default-parameters]} method-definition/meth-dflt-params-trailing-comma.js method-definition/meth-eval-var-scope-syntax-err.js {unsupported: [default-parameters]} - method-definition/meth-object-destructuring-param-strict-body.js {unsupported: [rest-parameters]} - method-definition/meth-rest-param-strict-body.js {unsupported: [rest-parameters]} + method-definition/meth-object-destructuring-param-strict-body.js + method-definition/meth-rest-param-strict-body.js method-definition/name-invoke-ctor.js method-definition/name-invoke-fn-strict.js non-strict method-definition/name-length-dflt.js {unsupported: [default-parameters]} @@ -5171,16 +5167,11 @@ language/reserved-words 2/27 (7.41%) await-module.js {unsupported: [module]} await-script.js -language/rest-parameters 10/11 (90.91%) +language/rest-parameters 5/11 (45.45%) array-pattern.js arrow-function.js - expected-argument-count.js no-alias-arguments.js object-pattern.js - rest-index.js - rest-parameters-apply.js - rest-parameters-call.js - rest-parameters-produce-an-array.js with-new-target.js language/source-text 0/1 (0.0%) @@ -6140,7 +6131,7 @@ language/statements/generators 220/259 (84.94%) dstr/obj-ptrn-rest-val-obj.js {unsupported: [object-rest]} arguments-with-arguments-fn.js non-strict arguments-with-arguments-lex.js non-strict - array-destructuring-param-strict-body.js {unsupported: [rest-parameters]} + array-destructuring-param-strict-body.js cptn-decl.js default-proto.js dflt-params-abrupt.js {unsupported: [default-parameters]} @@ -6157,7 +6148,7 @@ language/statements/generators 220/259 (84.94%) has-instance.js invoke-as-constructor.js length-dflt.js {unsupported: [default-parameters]} - object-destructuring-param-strict-body.js {unsupported: [rest-parameters]} + object-destructuring-param-strict-body.js param-dflt-yield.js {unsupported: [default-parameters]} params-dflt-args-unmapped.js {unsupported: [default-parameters]} params-dflt-ref-arguments.js {unsupported: [default-parameters]} @@ -6166,7 +6157,7 @@ language/statements/generators 220/259 (84.94%) prototype-own-properties.js prototype-relation-to-function.js prototype-value.js - rest-param-strict-body.js {unsupported: [rest-parameters]} + rest-param-strict-body.js restricted-properties.js scope-body-lex-distinct.js non-strict scope-param-elem-var-close.js non-strict