[ES6] Allow trailing comma in ArrayBindingPattern and ObjectBindingPattern
https://bugs.webkit.org/show_bug.cgi?id=146192

Reviewed by Darin Adler.

Source/JavaScriptCore:

According to the ES6 spec, trailing comma in ArrayBindingPattern and ObjectBindingPattern is allowed.
And empty ArrayBindingPattern and ObjectBindingPattern is also allowed.

This patch allows trailing comma and empty binding patterns.

* bytecompiler/NodesCodegen.cpp:
(JSC::ArrayPatternNode::bindValue):
* parser/Parser.cpp:
(JSC::Parser<LexerType>::parseDeconstructionPattern):
* tests/stress/trailing-comma-in-patterns.js: Added.
(shouldBe):
(iterator):

LayoutTests:

* js/object-literal-syntax-expected.txt:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@185853 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 5dc0d5c..7d96e88 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,23 @@
+2015-06-22  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [ES6] Allow trailing comma in ArrayBindingPattern and ObjectBindingPattern
+        https://bugs.webkit.org/show_bug.cgi?id=146192
+
+        Reviewed by Darin Adler.
+
+        According to the ES6 spec, trailing comma in ArrayBindingPattern and ObjectBindingPattern is allowed.
+        And empty ArrayBindingPattern and ObjectBindingPattern is also allowed.
+
+        This patch allows trailing comma and empty binding patterns.
+
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::ArrayPatternNode::bindValue):
+        * parser/Parser.cpp:
+        (JSC::Parser<LexerType>::parseDeconstructionPattern):
+        * tests/stress/trailing-comma-in-patterns.js: Added.
+        (shouldBe):
+        (iterator):
+
 2015-06-20  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [ES6] Destructuring assignment need to accept iterables
diff --git a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
index 7643e0b..81a5819 100644
--- a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
+++ b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
@@ -3139,8 +3139,12 @@
         generator.emitCall(iterator.get(), iterator.get(), NoExpectedFunction, args, divot(), divotStart(), divotEnd());
     }
 
+    if (m_targetPatterns.isEmpty()) {
+        generator.emitIteratorClose(iterator.get(), this);
+        return;
+    }
+
     RefPtr<RegisterID> done;
-    ASSERT(!m_targetPatterns.isEmpty());
     for (auto& target : m_targetPatterns) {
         RefPtr<RegisterID> value = generator.newTemporary();
 
diff --git a/Source/JavaScriptCore/parser/Parser.cpp b/Source/JavaScriptCore/parser/Parser.cpp
index f9d1d84..dab6b1a 100644
--- a/Source/JavaScriptCore/parser/Parser.cpp
+++ b/Source/JavaScriptCore/parser/Parser.cpp
@@ -600,15 +600,17 @@
         JSTextPosition divotStart = tokenStartPosition();
         auto arrayPattern = context.createArrayPattern(m_token.m_location);
         next();
-        if (kind == DeconstructToExpressions && match(CLOSEBRACKET))
-            return 0;
-        failIfTrue(match(CLOSEBRACKET), "There must be at least one bound property in an array deconstruction pattern");
+
         do {
             while (match(COMMA)) {
                 context.appendArrayPatternSkipEntry(arrayPattern, m_token.m_location);
                 next();
             }
             propagateError();
+
+            if (match(CLOSEBRACKET))
+                break;
+
             JSTokenLocation location = m_token.m_location;
             auto innerPattern = parseDeconstructionPattern(context, kind, depth + 1);
             if (kind == DeconstructToExpressions && !innerPattern)
@@ -618,25 +620,24 @@
             failIfTrue(kind == DeconstructToParameters && defaultValue,  "Default values in destructuring parameters are currently not supported");
             context.appendArrayPatternEntry(arrayPattern, location, innerPattern, defaultValue);
         } while (consume(COMMA));
-        
+
         if (kind == DeconstructToExpressions && !match(CLOSEBRACKET))
             return 0;
-
         consumeOrFail(CLOSEBRACKET, "Expected either a closing ']' or a ',' following an element deconstruction pattern");
         context.finishArrayPattern(arrayPattern, divotStart, divotStart, lastTokenEndPosition());
         pattern = arrayPattern;
         break;
     }
     case OPENBRACE: {
-        next();
-        
-        if (kind == DeconstructToExpressions && match(CLOSEBRACE))
-            return 0;
-
-        failIfTrue(match(CLOSEBRACE), "There must be at least one bound property in an object deconstruction pattern");
         auto objectPattern = context.createObjectPattern(m_token.m_location);
-        bool wasString = false;
+        next();
+
         do {
+            bool wasString = false;
+
+            if (match(CLOSEBRACE))
+                break;
+
             Identifier propertyName;
             TreeDeconstructionPattern innerPattern = 0;
             JSTokenLocation location = m_token.m_location;
@@ -687,6 +688,7 @@
             failIfTrue(kind == DeconstructToParameters && defaultValue, "Default values in destructuring parameters are currently not supported");
             context.appendObjectPatternEntry(objectPattern, location, wasString, propertyName, innerPattern, defaultValue);
         } while (consume(COMMA));
+
         if (kind == DeconstructToExpressions && !match(CLOSEBRACE))
             return 0;
         consumeOrFail(CLOSEBRACE, "Expected either a closing '}' or an ',' after a property deconstruction pattern");
diff --git a/Source/JavaScriptCore/tests/stress/trailing-comma-in-patterns.js b/Source/JavaScriptCore/tests/stress/trailing-comma-in-patterns.js
new file mode 100644
index 0000000..7353e90
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/trailing-comma-in-patterns.js
@@ -0,0 +1,157 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function iterator(array) {
+    var nextCount = 0;
+    var returnCount = 0;
+    var original =  array.values();
+    return {
+        [Symbol.iterator]() {
+            return this;
+        },
+
+        next() {
+            ++nextCount;
+            return original.next();
+        },
+
+        return() {
+            ++returnCount;
+            return { done: true };
+        },
+
+        reportNext() {
+            return nextCount;
+        },
+
+        reportReturn() {
+            return returnCount;
+        }
+    };
+};
+
+(function () {
+    var iter = iterator([1, 2, 3]);
+    var [] = iter;
+    shouldBe(iter.reportNext(), 0);
+    shouldBe(iter.reportReturn(), 1);
+}());
+
+(function () {
+    var iter = iterator([1, 2, 3]);
+    var [,] = iter;
+    shouldBe(iter.reportNext(), 1);
+    shouldBe(iter.reportReturn(), 1);
+}());
+
+(function () {
+    var iter = iterator([1, 2, 3]);
+    var [,,] = iter;
+    shouldBe(iter.reportNext(), 2);
+    shouldBe(iter.reportReturn(), 1);
+}());
+
+(function () {
+    var iter = iterator([1, 2, 3]);
+    var [,,,] = iter;
+    shouldBe(iter.reportNext(), 3);
+    shouldBe(iter.reportReturn(), 1);
+}());
+
+(function () {
+    var iter = iterator([1, 2, 3]);
+    var [,,,,] = iter;
+    shouldBe(iter.reportNext(), 4);
+    shouldBe(iter.reportReturn(), 0);
+}());
+
+(function () {
+    var iter = iterator([1, 2, 3]);
+    var [,,,,,] = iter;
+    shouldBe(iter.reportNext(), 4);
+    shouldBe(iter.reportReturn(), 0);
+}());
+
+(function () {
+    var iter = iterator([1, 2, 3]);
+    var [,a,] = iter;
+    shouldBe(iter.reportNext(), 2);
+    shouldBe(iter.reportReturn(), 1);
+    shouldBe(a, 2);
+}());
+
+(function () {
+    var iter = iterator([1, 2, 3]);
+    var [a,] = iter;
+    shouldBe(iter.reportNext(), 1);
+    shouldBe(iter.reportReturn(), 1);
+    shouldBe(a, 1);
+}());
+
+(function () {
+    var iter = iterator([1, 2, 3]);
+    var [a,,] = iter;
+    shouldBe(iter.reportNext(), 2);
+    shouldBe(iter.reportReturn(), 1);
+    shouldBe(a, 1);
+}());
+
+(function () {
+    var iter = iterator([1, 2, 3]);
+    var [a,b = 42,] = iter;
+    shouldBe(iter.reportNext(), 2);
+    shouldBe(iter.reportReturn(), 1);
+    shouldBe(a, 1);
+    shouldBe(b, 2);
+}());
+
+(function () {
+    var {} = { Cocoa: 15, Cappuccino: 13 };
+}());
+
+(function () {
+    var {Cocoa,} = { Cocoa: 15, Cappuccino: 13 };
+    shouldBe(Cocoa, 15);
+}());
+
+(function () {
+    var {Cocoa = 'Cocoa',} = { Cocoa: 15, Cappuccino: 13 };
+    shouldBe(Cocoa, 15);
+}());
+
+(function () {
+    var {Cocoa, Kilimanjaro = 'Coffee'} = { Cocoa: 15, Cappuccino: 13 };
+    shouldBe(Cocoa, 15);
+    shouldBe(Kilimanjaro, 'Coffee');
+}());
+
+(function () {
+    var {Cocoa, Kilimanjaro = 'Coffee'} = {};
+    shouldBe(Cocoa, undefined);
+    shouldBe(Kilimanjaro, 'Coffee');
+}());
+
+(function () {
+    var {Cocoa, Kilimanjaro = 'Coffee',} = { Cocoa: 15, Cappuccino: 13 };
+    shouldBe(Cocoa, 15);
+    shouldBe(Kilimanjaro, 'Coffee');
+}());
+
+function testSyntaxError(script, message) {
+    var error = null;
+    try {
+        eval(script);
+    } catch (e) {
+        error = e;
+    }
+    if (!error)
+        throw new Error("Expected syntax error not thrown");
+
+    if (String(error) !== message)
+        throw new Error("Bad error: " + String(error));
+}
+
+testSyntaxError(String.raw`var {,} = {Cocoa: 15}`, String.raw`SyntaxError: Unexpected token ','. Expected a property name.`);
+testSyntaxError(String.raw`var {,} = {}`, String.raw`SyntaxError: Unexpected token ','. Expected a property name.`);