Bug 1111386 - Support nested rest in destructuring assignment; r?jorendorff draft
authorArpad Borsos <arpad.borsos@googlemail.com>
Fri, 25 Dec 2015 14:16:58 +0100
changeset 317640 2bf62794e79f3418dbeeafea15a65c7626c8e22f
parent 317639 7116fd86bc65aedc1db1e66be71629d81a26b2e8
child 512336 ccca65c344261f36df9541ce9c4b84d226470f08
push id8738
push userarpad.borsos@googlemail.com
push dateFri, 25 Dec 2015 13:17:25 +0000
reviewersjorendorff
bugs1111386
milestone46.0a1
Bug 1111386 - Support nested rest in destructuring assignment; r?jorendorff
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/jit-test/tests/basic/destructuring-iterator.js
js/src/jit-test/tests/basic/destructuring-rest.js
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -3759,17 +3759,16 @@ bool
 BytecodeEmitter::emitDestructuringDeclsWithEmitter(JSOp prologueOp, ParseNode* pattern)
 {
     if (pattern->isKind(PNK_ARRAY)) {
         for (ParseNode* element = pattern->pn_head; element; element = element->pn_next) {
             if (element->isKind(PNK_ELISION))
                 continue;
             ParseNode* target = element;
             if (element->isKind(PNK_SPREAD)) {
-                MOZ_ASSERT(element->pn_kid->isKind(PNK_NAME));
                 target = element->pn_kid;
             }
             if (target->isKind(PNK_ASSIGN))
                 target = target->pn_left;
             if (target->isKind(PNK_NAME)) {
                 if (!EmitName(this, prologueOp, target))
                     return false;
             } else {
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -4116,21 +4116,16 @@ Parser<FullParseHandler>::checkDestructu
 
         ParseNode* target;
         if (element->isKind(PNK_SPREAD)) {
             if (element->pn_next) {
                 report(ParseError, false, element->pn_next, JSMSG_PARAMETER_AFTER_REST);
                 return false;
             }
             target = element->pn_kid;
-
-            if (handler.isUnparenthesizedDestructuringPattern(target)) {
-                report(ParseError, false, target, JSMSG_BAD_DESTRUCT_TARGET);
-                return false;
-            }
         } else if (handler.isUnparenthesizedAssignment(element)) {
             target = element->pn_left;
         } else {
             target = element;
         }
 
         if (handler.isUnparenthesizedDestructuringPattern(target)) {
             if (!checkDestructuringPattern(data, target))
--- a/js/src/jit-test/tests/basic/destructuring-iterator.js
+++ b/js/src/jit-test/tests/basic/destructuring-iterator.js
@@ -53,26 +53,30 @@ var arraycalls = 0;
 var ArrayIterator = Array.prototype[Symbol.iterator];
 Array.prototype[Symbol.iterator] = function () {
   arraycalls++;
   return ArrayIterator.apply(this, arguments);
 };
 // [...rest] should not call Array#@@iterator for the LHS
 var [...rest] = iterable;
 assertEq(arraycalls, 0, 'calls to Array#@@iterator');
-
+// [...[...rest]] should do so, since it creates an implicit array for the
+// first rest pattern, then destructures that again using @@iterator() for the
+// second rest pattern.
+var [...[...rest]] = iterable;
+assertEq(arraycalls, 1, 'calls to Array#@@iterator');
 
 // loop `fn` a few times, to get it JIT-compiled
 function loop(fn) {
   var i = 1e4;
   while (i--) fn();
 }
 
 loop(() => { doneafter = 4; var [a] = iterable; return a; });
-loop(() => { doneafter = 4; var [a,b,...rest] = iterable; return rest; });
+loop(() => { doneafter = 4; var [a,b,...[...rest]] = iterable; return rest; });
 
 
 // destructuring assignment should always use iterators and not optimize
 // to a "group assignment"
 delete Array.prototype[Symbol.iterator];
 assertThrowsInstanceOf(() => { var [a,b] = [1,2]; }, TypeError);
 Array.prototype[Symbol.iterator] = ArrayIterator;
 
--- a/js/src/jit-test/tests/basic/destructuring-rest.js
+++ b/js/src/jit-test/tests/basic/destructuring-rest.js
@@ -1,16 +1,14 @@
 
 load(libdir + 'asserts.js');
 load(libdir + 'eqArrayHelper.js');
 
 assertThrowsInstanceOf(() => new Function('[...a, ,] = []'), SyntaxError, 'trailing elision');
 assertThrowsInstanceOf(() => new Function('[a, ...b, c] = []'), SyntaxError, 'trailing param');
-assertThrowsInstanceOf(() => new Function('[...[a]] = []'), SyntaxError, 'nested arraypattern');
-assertThrowsInstanceOf(() => new Function('[...{a}] = []'), SyntaxError, 'nested objectpattern');
 assertThrowsInstanceOf(() => new Function('[...a=b] = []'), SyntaxError, 'assignment expression');
 assertThrowsInstanceOf(() => new Function('[...a()] = []'), SyntaxError, 'call expression');
 assertThrowsInstanceOf(() => new Function('[...(a,b)] = []'), SyntaxError, 'comma expression');
 assertThrowsInstanceOf(() => new Function('[...a++] = []'), SyntaxError, 'postfix expression');
 assertThrowsInstanceOf(() => new Function('[...!a] = []'), SyntaxError, 'unary expression');
 assertThrowsInstanceOf(() => new Function('[...a+b] = []'), SyntaxError, 'binary expression');
 assertThrowsInstanceOf(() => new Function('var [...a.x] = []'), SyntaxError, 'lvalue expression in declaration');
 assertThrowsInstanceOf(() => new Function('var [...(b)] = []'), SyntaxError);
@@ -40,28 +38,47 @@ var expectedStr = ['t', 'r'];
 
 function testAll(fn) {
   testDeclaration(fn);
 
   o.prop = null;
   assertEqArray(fn('[, ...(o.prop)]', inputArray, 'o.prop'), expected);
   o.prop = null;
   assertEqArray(fn('[, ...(o.call().prop)]', inputArray, 'o.prop'), expected);
+
+  o.prop = null;
+  assertEqArray(fn('[, ...[...(o.prop)]]', inputArray, 'o.prop'), expected);
+  o.prop = null;
+  assertEqArray(fn('[, ...[...(o.call().prop)]]', inputArray, 'o.prop'), expected);
 }
 function testDeclaration(fn) {
   testStr(fn);
 
   assertEqArray(fn('[, ...rest]', inputArray), expected);
   assertEqArray(fn('[, ...rest]', inputGenerator()), expected);
   assertEqArray(fn('[, [, ...rest]]', inputDeep), expected);
   assertEqArray(fn('{a: [, ...rest]}', inputObject), expected);
+
+  assertEqArray(fn('[, ...[...rest]]', inputArray), expected);
+  assertEqArray(fn('[, ...[...rest]]', inputGenerator()), expected);
+  assertEqArray(fn('[, [, ...[...rest]]]', inputDeep), expected);
+  assertEqArray(fn('{a: [, ...[...rest]]}', inputObject), expected);
+
+  assertEqArray(fn('[, ...{0: a, 1: b}]', inputArray, '[a, b]'), expected);
+  assertEqArray(fn('[, ...{0: a, 1: b}]', inputGenerator(), '[a, b]'), expected);
+  assertEqArray(fn('[, [, ...{0: a, 1: b}]]', inputDeep, '[a, b]'), expected);
+  assertEqArray(fn('{a: [, ...{0: a, 1: b}]}', inputObject, '[a, b]'), expected);
 }
 
 function testStr(fn) {
   assertEqArray(fn('[, ...rest]', inputStr), expectedStr);
+
+  assertEqArray(fn('[, ...[...rest]]', inputStr), expectedStr);
+
+  assertEqArray(fn('[, ...{0: a, 1: b}]', inputStr, '[a, b]'), expectedStr);
 }
 
 function testForIn(pattern, input, binding) {
   binding = binding || 'rest';
   return new Function('input',
     'for (var ' + pattern + ' in {[input]: 1}) {}' +
     'return ' + binding
   )(input);
@@ -83,18 +100,19 @@ function testGlobal(pattern, input, bind
     '(' + pattern + ' = input);' +
     'return ' + binding
   )(input);
 }
 testAll(testGlobal);
 
 function testClosure(pattern, input, binding) {
   binding = binding || 'rest';
+  const decl = binding.replace('[', '').replace(']', '');
   return new Function('input',
-    'var ' + binding + '; (function () {' +
+    'var ' + decl + '; (function () {' +
     '(' + pattern + ' = input);' +
     '})();' +
     'return ' + binding
   )(input);
 }
 testDeclaration(testClosure);
 
 function testArgument(pattern, input, binding) {