Bug 1243717 - Part 1: Allow destructuring for rest parameter (ES2016). r=arai
authorAndré Bargull <andre.bargull@gmail.com>
Mon, 10 Oct 2016 13:13:58 -0700
changeset 318396 70c98b5e580efc270e3595a897bc5d2a26a429a8
parent 318395 ca0c73cdf43779fac2f1941f7e42093454b90c22
child 318397 17973c94a5ab7e95900f31ab3c58923ec2e41242
push id7
push usermaklebus@msu.edu
push dateWed, 19 Oct 2016 22:03:57 +0000
reviewersarai
bugs1243717
milestone52.0a1
Bug 1243717 - Part 1: Allow destructuring for rest parameter (ES2016). r=arai
js/src/builtin/ReflectParse.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/NameAnalysisTypes.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/tests/ecma_7/Destructuring/rest-parameter-aray-iterator.js
js/src/tests/ecma_7/Destructuring/rest-parameter-arguments.js
js/src/tests/ecma_7/Destructuring/rest-parameter-function-length.js
js/src/tests/ecma_7/Destructuring/rest-parameter-spread-call-optimization.js
js/src/tests/ecma_7/Destructuring/rest-parameter-syntax.js
js/src/tests/ecma_7/Destructuring/rest-parameter.js
js/src/tests/ecma_7/extensions/browser.js
js/src/tests/ecma_7/extensions/parse-rest-destructuring-parameter.js
js/src/tests/ecma_7/extensions/shell.js
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -3491,17 +3491,16 @@ ASTSerializer::functionArgs(ParseNode* p
             defNode = arg->pn_right;
         }
 
         // Process the name or pattern.
         MOZ_ASSERT(pat->isKind(PNK_NAME) || pat->isKind(PNK_ARRAY) || pat->isKind(PNK_OBJECT));
         if (!pattern(pat, &node))
             return false;
         if (rest.isUndefined() && arg->pn_next == pnargs->last()) {
-            MOZ_ASSERT(arg->isKind(PNK_NAME));
             rest.setObject(node.toObject());
         } else {
             if (!args.append(node))
                 return false;
         }
 
         // Process its default (or lack thereof).
         if (defNode) {
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -7853,17 +7853,20 @@ BytecodeEmitter::isRestParameter(ParseNo
         return true;
     }
 
     JSAtom* name = pn->name();
     Maybe<NameLocation> paramLoc = locationOfNameBoundInFunctionScope(name);
     if (paramLoc && lookupName(name) == *paramLoc) {
         FunctionScope::Data* bindings = funbox->functionScopeBindings();
         if (bindings->nonPositionalFormalStart > 0) {
-            *result = name == bindings->names[bindings->nonPositionalFormalStart - 1].name();
+            // |paramName| can be nullptr when the rest destructuring syntax is
+            // used: `function f(...[]) {}`.
+            JSAtom* paramName = bindings->names[bindings->nonPositionalFormalStart - 1].name();
+            *result = paramName && name == paramName;
             return true;
         }
     }
 
     return true;
 }
 
 bool
--- a/js/src/frontend/NameAnalysisTypes.h
+++ b/js/src/frontend/NameAnalysisTypes.h
@@ -66,16 +66,17 @@ class EnvironmentCoordinate
 namespace frontend {
 
 // A detailed kind used for tracking declarations in the Parser. Used for
 // specific early error semantics and better error messages.
 enum class DeclarationKind : uint8_t
 {
     PositionalFormalParameter,
     FormalParameter,
+    CoverArrowParameter,
     Var,
     ForOfVar,
     Let,
     Const,
     Import,
     BodyLevelFunction,
     LexicalFunction,
     VarForAnnexBLexicalFunction,
@@ -84,16 +85,17 @@ enum class DeclarationKind : uint8_t
 };
 
 static inline BindingKind
 DeclarationKindToBindingKind(DeclarationKind kind)
 {
     switch (kind) {
       case DeclarationKind::PositionalFormalParameter:
       case DeclarationKind::FormalParameter:
+      case DeclarationKind::CoverArrowParameter:
         return BindingKind::FormalParameter;
 
       case DeclarationKind::Var:
       case DeclarationKind::BodyLevelFunction:
       case DeclarationKind::VarForAnnexBLexicalFunction:
       case DeclarationKind::ForOfVar:
         return BindingKind::Var;
 
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -88,16 +88,18 @@ PropagateTransitiveParseFlags(const T* i
 
 static const char*
 DeclarationKindString(DeclarationKind kind)
 {
     switch (kind) {
       case DeclarationKind::PositionalFormalParameter:
       case DeclarationKind::FormalParameter:
         return "formal parameter";
+      case DeclarationKind::CoverArrowParameter:
+        return "cover arrow parameter";
       case DeclarationKind::Var:
         return "var";
       case DeclarationKind::Let:
         return "let";
       case DeclarationKind::Const:
         return "const";
       case DeclarationKind::Import:
         return "import";
@@ -1295,16 +1297,20 @@ Parser<ParseHandler>::noteDeclaredName(H
         }
 
         if (!p && !scope->addDeclaredName(pc, p, name, kind))
             return false;
 
         break;
       }
 
+      case DeclarationKind::CoverArrowParameter:
+        // CoverArrowParameter is only used as a placeholder declaration kind.
+        break;
+
       case DeclarationKind::PositionalFormalParameter:
         MOZ_CRASH("Positional formal parameter names should use "
                   "notePositionalFormalParameter");
         break;
 
       case DeclarationKind::VarForAnnexBLexicalFunction:
         MOZ_CRASH("Synthesized Annex B vars should go through "
                   "tryDeclareVarForAnnexBLexicalFunction");
@@ -2623,22 +2629,49 @@ Parser<ParseHandler>::functionArguments(
                 report(ParseError, false, null(), JSMSG_PARAMETER_AFTER_REST);
                 return false;
             }
 
             TokenKind tt;
             if (!tokenStream.getToken(&tt, TokenStream::Operand))
                 return false;
             MOZ_ASSERT_IF(parenFreeArrow, tt == TOK_NAME || tt == TOK_YIELD);
+
+            if (tt == TOK_TRIPLEDOT) {
+                if (IsSetterKind(kind)) {
+                    report(ParseError, false, null(),
+                           JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
+                    return false;
+                }
+
+                disallowDuplicateParams = true;
+                if (duplicatedParam) {
+                    // Has duplicated args before the rest parameter.
+                    report(ParseError, false, null(), JSMSG_BAD_DUP_ARGS);
+                    return false;
+                }
+
+                hasRest = true;
+                funbox->function()->setHasRest();
+
+                if (!tokenStream.getToken(&tt))
+                    return false;
+
+                if (tt != TOK_NAME && tt != TOK_YIELD && tt != TOK_LB && tt != TOK_LC) {
+                    report(ParseError, false, null(), JSMSG_NO_REST_NAME);
+                    return false;
+                }
+            }
+
             switch (tt) {
               case TOK_LB:
               case TOK_LC: {
-                /* See comment below in the TOK_NAME case. */
                 disallowDuplicateParams = true;
                 if (duplicatedParam) {
+                    // Has duplicated args before the destructuring parameter.
                     report(ParseError, false, null(), JSMSG_BAD_DUP_ARGS);
                     return false;
                 }
 
                 funbox->hasDestructuringArgs = true;
 
                 Node destruct = destructuringDeclarationWithoutYield(
                     DeclarationKind::FormalParameter,
@@ -2648,41 +2681,16 @@ Parser<ParseHandler>::functionArguments(
                     return false;
 
                 if (!noteDestructuredPositionalFormalParameter(funcpn, destruct))
                     return false;
 
                 break;
               }
 
-              case TOK_TRIPLEDOT:
-                if (IsSetterKind(kind)) {
-                    report(ParseError, false, null(),
-                           JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
-                    return false;
-                }
-
-                hasRest = true;
-                funbox->function()->setHasRest();
-
-                disallowDuplicateParams = true;
-                if (duplicatedParam) {
-                    // Has duplicated args before the rest parameter.
-                    report(ParseError, false, null(), JSMSG_BAD_DUP_ARGS);
-                    return false;
-                }
-
-                if (!tokenStream.getToken(&tt))
-                    return false;
-                if (tt != TOK_NAME && tt != TOK_YIELD) {
-                    report(ParseError, false, null(), JSMSG_NO_REST_NAME);
-                    return false;
-                }
-                MOZ_FALLTHROUGH;
-
               case TOK_NAME:
               case TOK_YIELD: {
                 if (parenFreeArrow)
                     funbox->setStart(tokenStream);
 
                 RootedPropertyName name(context, bindingIdentifier(yieldHandling));
                 if (!name)
                     return false;
@@ -3936,30 +3944,28 @@ Parser<FullParseHandler>::checkDestructu
  * In both cases, other code parses the pattern as an arbitrary
  * primaryExpr, and then, here in checkDestructuringPattern, verify
  * that the tree is a valid AssignmentPattern or BindingPattern.
  *
  * In assignment-like contexts, we parse the pattern with
  * pc->inDestructuringDecl clear, so the lvalue expressions in the
  * pattern are parsed normally.  primaryExpr links variable references
  * into the appropriate use chains; creates placeholder definitions;
- * and so on.  checkDestructuringPattern is called with |data| nullptr
- * (since we won't be binding any new names), and we specialize lvalues
- * as appropriate.
+ * and so on.  checkDestructuringPattern won't bind any new names and
+ * we specialize lvalues as appropriate.
  *
  * In declaration-like contexts, the normal variable reference
  * processing would just be an obstruction, because we're going to
  * define the names that appear in the property value positions as new
  * variables anyway.  In this case, we parse the pattern with
  * pc->inDestructuringDecl set, which directs primaryExpr to leave
  * whatever name nodes it creates unconnected.  Then, here in
  * checkDestructuringPattern, we require the pattern's property value
  * positions to be simple names, and define them as appropriate to the
- * context.  For these calls, |data| points to the right sort of
- * BindData.
+ * context.
  */
 template <>
 bool
 Parser<FullParseHandler>::checkDestructuringPattern(ParseNode* pattern,
                                                     Maybe<DeclarationKind> maybeDecl,
                                                     PossibleError* possibleError /* = nullptr */)
 {
     if (pattern->isKind(PNK_ARRAYCOMP)) {
@@ -9080,23 +9086,34 @@ Parser<ParseHandler>::primaryExpr(YieldH
                    "expression", TokenKindToDesc(tt));
             return null();
         }
 
         TokenKind next;
         if (!tokenStream.getToken(&next))
             return null();
 
-        // This doesn't check that the provided name is allowed, e.g. if the
-        // enclosing code is strict mode code, any of "arguments", "let", or
-        // "yield" should be prohibited.  Argument-parsing code handles that.
-        if (next != TOK_NAME && next != TOK_YIELD) {
-            report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
-                   "rest argument name", TokenKindToDesc(next));
-            return null();
+        if (next == TOK_LB || next == TOK_LC) {
+            // Validate, but don't store the pattern right now. The whole arrow
+            // function is reparsed in functionFormalParametersAndBody().
+            if (!destructuringDeclaration(DeclarationKind::CoverArrowParameter, yieldHandling,
+                                          next))
+            {
+                return null();
+            }
+        } else {
+            // This doesn't check that the provided name is allowed, e.g. if
+            // the enclosing code is strict mode code, any of "let", "yield",
+            // or "arguments" should be prohibited.  Argument-parsing code
+            // handles that.
+            if (next != TOK_NAME && next != TOK_YIELD) {
+                report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+                    "rest argument name", TokenKindToDesc(next));
+                return null();
+            }
         }
 
         if (!tokenStream.getToken(&next))
             return null();
         if (next != TOK_RP) {
             report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
                    "closing parenthesis", TokenKindToDesc(next));
             return null();
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -11,16 +11,17 @@
 
 #include "mozilla/Array.h"
 #include "mozilla/Maybe.h"
 
 #include "jspubtd.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/FullParseHandler.h"
+#include "frontend/NameAnalysisTypes.h"
 #include "frontend/NameCollections.h"
 #include "frontend/SharedContext.h"
 #include "frontend/SyntaxParseHandler.h"
 
 namespace js {
 
 class ModuleObject;
 
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/Destructuring/rest-parameter-aray-iterator.js
@@ -0,0 +1,40 @@
+/* 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/. */
+
+// Destructuring rest arrays call the array iterator. This behaviour is
+// observable when Array.prototype[Symbol.iterator] is overridden.
+
+const oldArrayIterator = Array.prototype[Symbol.iterator];
+try {
+    let callCount = 0;
+    Array.prototype[Symbol.iterator] = function() {
+        callCount += 1;
+        return oldArrayIterator.call(this);
+    };
+
+    // Array iterator called exactly once.
+    function arrayIterCalledOnce(...[]) { }
+    assertEq(callCount, 0);
+    arrayIterCalledOnce();
+    assertEq(callCount, 1);
+
+    // Array iterator not called before rest parameter.
+    callCount = 0;
+    function arrayIterNotCalledBeforeRest(t = assertEq(callCount, 0), ...[]) { }
+    assertEq(callCount, 0);
+    arrayIterNotCalledBeforeRest();
+    assertEq(callCount, 1);
+
+    // Array iterator called when rest parameter is processed.
+    callCount = 0;
+    function arrayIterCalledWhenDestructuring(...[t = assertEq(callCount, 1)]) { }
+    assertEq(callCount, 0);
+    arrayIterCalledWhenDestructuring();
+    assertEq(callCount, 1);
+} finally {
+    Array.prototype[Symbol.iterator] = oldArrayIterator;
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/Destructuring/rest-parameter-arguments.js
@@ -0,0 +1,101 @@
+/* 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/. */
+
+// Ensure the |arguments| object works as expected when a destructuring rest
+// parameter is present.
+
+// |arguments.length| with destructuring rest array.
+function argsLengthEmptyRestArray(...[]) {
+    return arguments.length;
+}
+assertEq(argsLengthEmptyRestArray(), 0);
+assertEq(argsLengthEmptyRestArray(10), 1);
+assertEq(argsLengthEmptyRestArray(10, 20), 2);
+
+function argsLengthRestArray(...[a]) {
+    return arguments.length;
+}
+assertEq(argsLengthRestArray(), 0);
+assertEq(argsLengthRestArray(10), 1);
+assertEq(argsLengthRestArray(10, 20), 2);
+
+function argsLengthRestArrayWithDefault(...[a = 0]) {
+    return arguments.length;
+}
+assertEq(argsLengthRestArrayWithDefault(), 0);
+assertEq(argsLengthRestArrayWithDefault(10), 1);
+assertEq(argsLengthRestArrayWithDefault(10, 20), 2);
+
+
+// |arguments.length| with destructuring rest object.
+function argsLengthEmptyRestObject(...{}) {
+    return arguments.length;
+}
+assertEq(argsLengthEmptyRestObject(), 0);
+assertEq(argsLengthEmptyRestObject(10), 1);
+assertEq(argsLengthEmptyRestObject(10, 20), 2);
+
+function argsLengthRestObject(...{a}) {
+    return arguments.length;
+}
+assertEq(argsLengthRestObject(), 0);
+assertEq(argsLengthRestObject(10), 1);
+assertEq(argsLengthRestObject(10, 20), 2);
+
+function argsLengthRestObjectWithDefault(...{a = 0}) {
+    return arguments.length;
+}
+assertEq(argsLengthRestObjectWithDefault(), 0);
+assertEq(argsLengthRestObjectWithDefault(10), 1);
+assertEq(argsLengthRestObjectWithDefault(10, 20), 2);
+
+
+// |arguments| access with destructuring rest array.
+function argsAccessEmptyRestArray(...[]) {
+    return arguments[0];
+}
+assertEq(argsAccessEmptyRestArray(), undefined);
+assertEq(argsAccessEmptyRestArray(10), 10);
+assertEq(argsAccessEmptyRestArray(10, 20), 10);
+
+function argsAccessRestArray(...[a]) {
+    return arguments[0];
+}
+assertEq(argsAccessRestArray(), undefined);
+assertEq(argsAccessRestArray(10), 10);
+assertEq(argsAccessRestArray(10, 20), 10);
+
+function argsAccessRestArrayWithDefault(...[a = 0]) {
+    return arguments[0];
+}
+assertEq(argsAccessRestArrayWithDefault(), undefined);
+assertEq(argsAccessRestArrayWithDefault(10), 10);
+assertEq(argsAccessRestArrayWithDefault(10, 20), 10);
+
+
+// |arguments| access with destructuring rest object.
+function argsAccessEmptyRestObject(...{}) {
+    return arguments[0];
+}
+assertEq(argsAccessEmptyRestObject(), undefined);
+assertEq(argsAccessEmptyRestObject(10), 10);
+assertEq(argsAccessEmptyRestObject(10, 20), 10);
+
+function argsAccessRestObject(...{a}) {
+    return arguments[0];
+}
+assertEq(argsAccessRestObject(), undefined);
+assertEq(argsAccessRestObject(10), 10);
+assertEq(argsAccessRestObject(10, 20), 10);
+
+function argsAccessRestObjectWithDefault(...{a = 0}) {
+    return arguments[0];
+}
+assertEq(argsAccessRestObjectWithDefault(), undefined);
+assertEq(argsAccessRestObjectWithDefault(10), 10);
+assertEq(argsAccessRestObjectWithDefault(10, 20), 10);
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/Destructuring/rest-parameter-function-length.js
@@ -0,0 +1,41 @@
+/* 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/. */
+
+// Ensure function length is set correctly when a destructuring rest parameter
+// is present.
+
+assertEq(function(...[]) {}.length, 0);
+assertEq(function(...[a]) {}.length, 0);
+assertEq(function(...[a = 0]) {}.length, 0);
+assertEq(function(...{}) {}.length, 0);
+assertEq(function(...{p: a}) {}.length, 0);
+assertEq(function(...{p: a = 0}) {}.length, 0);
+assertEq(function(...{a = 0}) {}.length, 0);
+
+assertEq(function(x, ...[]) {}.length, 1);
+assertEq(function(x, ...[a]) {}.length, 1);
+assertEq(function(x, ...[a = 0]) {}.length, 1);
+assertEq(function(x, ...{}) {}.length, 1);
+assertEq(function(x, ...{p: a}) {}.length, 1);
+assertEq(function(x, ...{p: a = 0}) {}.length, 1);
+assertEq(function(x, ...{a = 0}) {}.length, 1);
+
+assertEq(function(x, y, ...[]) {}.length, 2);
+assertEq(function(x, y, ...[a]) {}.length, 2);
+assertEq(function(x, y, ...[a = 0]) {}.length, 2);
+assertEq(function(x, y, ...{}) {}.length, 2);
+assertEq(function(x, y, ...{p: a}) {}.length, 2);
+assertEq(function(x, y, ...{p: a = 0}) {}.length, 2);
+assertEq(function(x, y, ...{a = 0}) {}.length, 2);
+
+assertEq(function(x, y = 0, ...[]) {}.length, 1);
+assertEq(function(x, y = 0, ...[a]) {}.length, 1);
+assertEq(function(x, y = 0, ...[a = 0]) {}.length, 1);
+assertEq(function(x, y = 0, ...{}) {}.length, 1);
+assertEq(function(x, y = 0, ...{p: a}) {}.length, 1);
+assertEq(function(x, y = 0, ...{p: a = 0}) {}.length, 1);
+assertEq(function(x, y = 0, ...{a = 0}) {}.length, 1);
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/Destructuring/rest-parameter-spread-call-optimization.js
@@ -0,0 +1,29 @@
+/* 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/. */
+
+// Ensure the spread-call optimization doesn't break when a destructuring rest
+// parameter is used.
+
+function spreadTarget() { return arguments.length; }
+
+function spreadOpt(...[r]){ return spreadTarget(...r); }
+assertEq(spreadOpt([]), 0);
+assertEq(spreadOpt([10]), 1);
+assertEq(spreadOpt([10, 20]), 2);
+assertEq(spreadOpt([10, 20, 30]), 3);
+
+function spreadOpt2(...[...r]){ return spreadTarget(...r); }
+assertEq(spreadOpt2(), 0);
+assertEq(spreadOpt2(10), 1);
+assertEq(spreadOpt2(10, 20), 2);
+assertEq(spreadOpt2(10, 20, 30), 3);
+
+function spreadOpt3(r, ...[]){ return spreadTarget(...r); }
+assertEq(spreadOpt3([]), 0);
+assertEq(spreadOpt3([10]), 1);
+assertEq(spreadOpt3([10, 20]), 2);
+assertEq(spreadOpt3([10, 20, 30]), 3);
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/Destructuring/rest-parameter-syntax.js
@@ -0,0 +1,87 @@
+/* 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/. */
+
+
+const bindingPatterns = [
+    "[]",
+    "[a]",
+    "[a, b]",
+    "[a, ...b]",
+    "[...a]",
+    "[...[]]",
+
+    "{}",
+    "{p: a}",
+    "{p: a = 0}",
+    "{p: {}}",
+    "{p: a, q: b}",
+    "{a}",
+    "{a, b}",
+    "{a = 0}",
+];
+
+const functions = [
+    p => `function f(${p}) {}`,
+    p => `function* g(${p}) {}`,
+    p => `({m(${p}) {}});`,
+    p => `(class {m(${p}) {}});`,
+    p => `(${p}) => {};`,
+];
+
+for (let pattern of bindingPatterns) {
+    for (let fn of functions) {
+        // No leading parameters.
+        eval(fn(`...${pattern}`));
+
+        // Leading normal parameters.
+        eval(fn(`x, ...${pattern}`));
+        eval(fn(`x, y, ...${pattern}`));
+
+        // Leading parameters with defaults.
+        eval(fn(`x = 0, ...${pattern}`));
+        eval(fn(`x = 0, y = 0, ...${pattern}`));
+
+        // Leading array destructuring parameters.
+        eval(fn(`[], ...${pattern}`));
+        eval(fn(`[x], ...${pattern}`));
+        eval(fn(`[x = 0], ...${pattern}`));
+        eval(fn(`[...x], ...${pattern}`));
+
+        // Leading object destructuring parameters.
+        eval(fn(`{}, ...${pattern}`));
+        eval(fn(`{p: x}, ...${pattern}`));
+        eval(fn(`{x}, ...${pattern}`));
+        eval(fn(`{x = 0}, ...${pattern}`));
+
+        // Trailing parameters after rest parameter.
+        assertThrowsInstanceOf(() => eval(fn(`...${pattern},`)), SyntaxError);
+        assertThrowsInstanceOf(() => eval(fn(`...${pattern}, x`)), SyntaxError);
+        assertThrowsInstanceOf(() => eval(fn(`...${pattern}, x = 0`)), SyntaxError);
+        assertThrowsInstanceOf(() => eval(fn(`...${pattern}, ...x`)), SyntaxError);
+        assertThrowsInstanceOf(() => eval(fn(`...${pattern}, []`)), SyntaxError);
+        assertThrowsInstanceOf(() => eval(fn(`...${pattern}, {}`)), SyntaxError);
+
+        // Rest parameter with defaults.
+        assertThrowsInstanceOf(() => eval(fn(`...${pattern} = 0`)), SyntaxError);
+    }
+}
+
+for (let fn of functions) {
+    // Missing name, incomplete patterns.
+    assertThrowsInstanceOf(() => eval(fn(`...`)), SyntaxError);
+    assertThrowsInstanceOf(() => eval(fn(`...[`)), SyntaxError);
+    assertThrowsInstanceOf(() => eval(fn(`...{`)), SyntaxError);
+
+    // Invalid binding name.
+    assertThrowsInstanceOf(() => eval(fn(`...[0]`)), SyntaxError);
+    assertThrowsInstanceOf(() => eval(fn(`...[p.q]`)), SyntaxError);
+}
+
+// Rest parameters aren't valid in getter/setter methods.
+assertThrowsInstanceOf(() => eval(`({get p(...[]) {}})`), SyntaxError);
+assertThrowsInstanceOf(() => eval(`({set p(...[]) {}})`), SyntaxError);
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/Destructuring/rest-parameter.js
@@ -0,0 +1,54 @@
+/* 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/. */
+
+// Simple functional test for destructuring rest parameters.
+
+function arrayRest(...[a, b]) {
+    return a + b;
+}
+assertEq(arrayRest(3, 7), 10);
+
+
+function arrayRestWithDefault(...[a, b = 1]) {
+    return a + b;
+}
+assertEq(arrayRestWithDefault(3, 7), 10);
+assertEq(arrayRestWithDefault(4), 5);
+assertEq(arrayRestWithDefault(4, undefined), 5);
+
+
+function objectRest(...{length: len}) {
+    return len;
+}
+assertEq(objectRest(), 0);
+assertEq(objectRest(10), 1);
+assertEq(objectRest(10, 20), 2);
+
+
+function objectRestWithDefault(...{0: a, 1: b = 1}) {
+    return a + b;
+}
+assertEq(objectRestWithDefault(3, 7), 10);
+assertEq(objectRestWithDefault(4), 5);
+assertEq(objectRestWithDefault(4, undefined), 5);
+
+
+function arrayRestWithNestedRest(...[...r]) {
+    return r.length;
+}
+assertEq(arrayRestWithNestedRest(), 0);
+assertEq(arrayRestWithNestedRest(10), 1);
+assertEq(arrayRestWithNestedRest(10, 20), 2);
+
+
+function arrayRestTDZ(...[a = a]) { }
+assertThrowsInstanceOf(() => arrayRestTDZ(), ReferenceError);
+
+
+function objectRestTDZ(...{a = a}) { }
+assertThrowsInstanceOf(() => objectRestTDZ(), ReferenceError);
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/extensions/parse-rest-destructuring-parameter.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!xulRuntime.shell)
+/* 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/. */
+
+function funArgs(params) {
+    return Reflect.parse(`function f(${params}) {}`).body[0].rest;
+}
+
+var arrayRest = funArgs("...[]");
+assertEq(arrayRest.type, "ArrayPattern");
+assertEq(arrayRest.elements.length, 0);
+
+arrayRest = funArgs("...[a]");
+assertEq(arrayRest.type, "ArrayPattern");
+assertEq(arrayRest.elements.length, 1);
+
+var objectRest = funArgs("...{}");
+assertEq(objectRest.type, "ObjectPattern");
+assertEq(objectRest.properties.length, 0);
+
+objectRest = funArgs("...{p: a}");
+assertEq(objectRest.type, "ObjectPattern");
+assertEq(objectRest.properties.length, 1);
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644