[JSC] Support optional catch binding
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 31 Jul 2017 16:15:32 +0000 (16:15 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 31 Jul 2017 16:15:32 +0000 (16:15 +0000)
https://bugs.webkit.org/show_bug.cgi?id=174981

Reviewed by Saam Barati.

JSTests:

* stress/optional-catch-binding-syntax.js: Added.
(testSyntax):
(testSyntaxError):
(catch.catch):
* stress/optional-catch-binding.js: Added.
(shouldBe):
(throwException):

Source/JavaScriptCore:

This patch implements optional catch binding proposal[1], which is now stage 3.
This proposal adds a new `catch` brace with no error value binding.

    ```
        try {
            ...
        } catch {
            ...
        }
    ```

Sometimes we do not need to get error value actually. For example, the function returns
boolean which means whether the function succeeds.

    ```
    function parse(result) // -> bool
    {
         try {
             parseInner(result);
         } catch {
             return false;
         }
         return true;
    }
    ```

In the above case, we are not interested in the actual error value. Without this syntax,
we always need to introduce a binding for an error value that is just ignored.

[1]: https://michaelficarra.github.io/optional-catch-binding-proposal/

* bytecompiler/NodesCodegen.cpp:
(JSC::TryNode::emitBytecode):
* parser/Parser.cpp:
(JSC::Parser<LexerType>::parseTryStatement):

LayoutTests:

Rebaseline existing tests.

* js/parser-syntax-check-expected.txt:
* js/script-tests/parser-syntax-check.js:
* sputnik/Conformance/12_Statement/12.14_The_try_Statement/S12.14_A16_T4-expected.txt:
* sputnik/Conformance/12_Statement/12.14_The_try_Statement/S12.14_A16_T4.html:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@220068 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/stress/optional-catch-binding-syntax.js [new file with mode: 0644]
JSTests/stress/optional-catch-binding.js [new file with mode: 0644]
LayoutTests/ChangeLog
LayoutTests/js/parser-syntax-check-expected.txt
LayoutTests/js/script-tests/parser-syntax-check.js
LayoutTests/sputnik/Conformance/12_Statement/12.14_The_try_Statement/S12.14_A16_T4-expected.txt
LayoutTests/sputnik/Conformance/12_Statement/12.14_The_try_Statement/S12.14_A16_T4.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Source/JavaScriptCore/parser/Parser.cpp

index e3dabd9..9d31d9a 100644 (file)
@@ -1,3 +1,18 @@
+2017-07-31  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [JSC] Support optional catch binding
+        https://bugs.webkit.org/show_bug.cgi?id=174981
+
+        Reviewed by Saam Barati.
+
+        * stress/optional-catch-binding-syntax.js: Added.
+        (testSyntax):
+        (testSyntaxError):
+        (catch.catch):
+        * stress/optional-catch-binding.js: Added.
+        (shouldBe):
+        (throwException):
+
 2017-07-28  Mark Lam  <mark.lam@apple.com>
 
         ObjectToStringAdaptiveStructureWatchpoint should not fire if it's dying imminently.
diff --git a/JSTests/stress/optional-catch-binding-syntax.js b/JSTests/stress/optional-catch-binding-syntax.js
new file mode 100644 (file)
index 0000000..f4b9854
--- /dev/null
@@ -0,0 +1,30 @@
+function testSyntax(script) {
+    try {
+        eval(script);
+    } catch (error) {
+        if (error instanceof SyntaxError)
+            throw new Error("Bad error: " + String(error));
+    }
+}
+
+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));
+}
+
+testSyntax(`try { } catch { }`);
+testSyntax(`try { } catch { } finally { }`);
+testSyntaxError(`try { } catch { { }`, `SyntaxError: Unexpected end of script`);
+testSyntaxError(`try { } catch () { }`, `SyntaxError: Unexpected token ')'. Expected a parameter pattern or a ')' in parameter list.`);
+testSyntaxError(`try { } catch }`, `SyntaxError: Unexpected token '}'. Expected '(' to start a 'catch' target.`);
+testSyntaxError(`try { } catch {`, `SyntaxError: Unexpected end of script`);
+testSyntaxError(`try { } catch {`, `SyntaxError: Unexpected end of script`);
diff --git a/JSTests/stress/optional-catch-binding.js b/JSTests/stress/optional-catch-binding.js
new file mode 100644 (file)
index 0000000..3419753
--- /dev/null
@@ -0,0 +1,50 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function throwException() {
+    throw new Error(`Cocoa`);
+}
+
+shouldBe(function () {
+    try {
+        throwException();
+    } catch {
+        return true;
+    }
+    return false;
+}(), true);
+
+shouldBe(function () {
+    var ok = false;
+    try {
+        throwException();
+    } catch {
+        ok = true;
+        return false;
+    } finally {
+        return ok;
+    }
+    return false;
+}(), true);
+
+shouldBe(function () {
+    let value = 'Cocoa';
+    try {
+        throwException();
+    } catch {
+        let value = 'Cappuccino';
+        return value;
+    }
+}(), 'Cappuccino');
+
+shouldBe(function () {
+    var value = 'Cocoa';
+    try {
+        throwException();
+    } catch {
+        let value = 'Cappuccino';
+    }
+    return value;
+}(), 'Cocoa');
index 22209ad..5d4ee2c 100644 (file)
@@ -1,3 +1,17 @@
+2017-07-31  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [JSC] Support optional catch binding
+        https://bugs.webkit.org/show_bug.cgi?id=174981
+
+        Reviewed by Saam Barati.
+
+        Rebaseline existing tests.
+
+        * js/parser-syntax-check-expected.txt:
+        * js/script-tests/parser-syntax-check.js:
+        * sputnik/Conformance/12_Statement/12.14_The_try_Statement/S12.14_A16_T4-expected.txt:
+        * sputnik/Conformance/12_Statement/12.14_The_try_Statement/S12.14_A16_T4.html:
+
 2017-07-31  Per Arne Vollan  <pvollan@apple.com>
 
         Many web-platform tests are slow on Windows.
index a3a7673..12f08f5 100644 (file)
@@ -631,8 +631,8 @@ PASS Invalid: "try {} finally {} catch(e) {}". Produced the following syntax err
 PASS Invalid: "function f() { try {} finally {} catch(e) {} }". Produced the following syntax error: "SyntaxError: Unexpected keyword 'catch'"
 PASS Invalid: "try {} catch (...) {}". Produced the following syntax error: "SyntaxError: Unexpected token '...'. Expected a parameter pattern or a ')' in parameter list."
 PASS Invalid: "function f() { try {} catch (...) {} }". Produced the following syntax error: "SyntaxError: Unexpected token '...'. Expected a parameter pattern or a ')' in parameter list."
-PASS Invalid: "try {} catch {}". Produced the following syntax error: "SyntaxError: Unexpected token '{'. Expected '(' to start a 'catch' target."
-PASS Invalid: "function f() { try {} catch {} }". Produced the following syntax error: "SyntaxError: Unexpected token '{'. Expected '(' to start a 'catch' target."
+PASS Valid:   "try {} catch {}"
+PASS Valid:   "function f() { try {} catch {} }"
 PASS Valid:   "if (a) try {} finally {} else b;"
 PASS Valid:   "function f() { if (a) try {} finally {} else b; }"
 PASS Valid:   "if (--a()) do with(1) try {} catch(ke) { f() ; g() } while (a in b) else {}" with ReferenceError
index 3952d38..26a0d13 100644 (file)
@@ -411,7 +411,7 @@ invalid("try {} catch(e)");
 invalid("try {} finally");
 invalid("try {} finally {} catch(e) {}");
 invalid("try {} catch (...) {}");
-invalid("try {} catch {}");
+valid  ("try {} catch {}");
 valid  ("if (a) try {} finally {} else b;");
 valid  ("if (--a()) do with(1) try {} catch(ke) { f() ; g() } while (a in b) else {}");
 invalid("if (a) try {} else b; catch (e) { }");
index a95163c..7ef7a2a 100644 (file)
@@ -1,7 +1,6 @@
-CONSOLE MESSAGE: line 78: SyntaxError: Unexpected token '{'. Expected '(' to start a 'catch' target.
 S12.14_A16_T4
 
-PASS Expected parsing failure
+PASS No error detected
 
 TEST COMPLETE
 
index bcdf20e..492eceb 100644 (file)
@@ -86,11 +86,11 @@ var successfullyParsed = true;
 
 <script>
 if (!successfullyParsed)
-    printTestPassed('Expected parsing failure');
+    printTestFailed('Expected parsing failure');
 else if (sputnikException)
-    printTestPassed(sputnikException);
+    printTestFailed(sputnikException);
 else
-    printTestFailed("No error detected");
+    printTestPassed("No error detected");
 testPrint('<br /><span class="pass">TEST COMPLETE</span>');
 </script>
 </body>
index 203fe2d..cc244a1 100644 (file)
@@ -1,5 +1,48 @@
 2017-07-31  Yusuke Suzuki  <utatane.tea@gmail.com>
 
+        [JSC] Support optional catch binding
+        https://bugs.webkit.org/show_bug.cgi?id=174981
+
+        Reviewed by Saam Barati.
+
+        This patch implements optional catch binding proposal[1], which is now stage 3.
+        This proposal adds a new `catch` brace with no error value binding.
+
+            ```
+                try {
+                    ...
+                } catch {
+                    ...
+                }
+            ```
+
+        Sometimes we do not need to get error value actually. For example, the function returns
+        boolean which means whether the function succeeds.
+
+            ```
+            function parse(result) // -> bool
+            {
+                 try {
+                     parseInner(result);
+                 } catch {
+                     return false;
+                 }
+                 return true;
+            }
+            ```
+
+        In the above case, we are not interested in the actual error value. Without this syntax,
+        we always need to introduce a binding for an error value that is just ignored.
+
+        [1]: https://michaelficarra.github.io/optional-catch-binding-proposal/
+
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::TryNode::emitBytecode):
+        * parser/Parser.cpp:
+        (JSC::Parser<LexerType>::parseTryStatement):
+
+2017-07-31  Yusuke Suzuki  <utatane.tea@gmail.com>
+
         Merge WTFThreadData to Thread::current
         https://bugs.webkit.org/show_bug.cgi?id=174716
 
index f255d4b..6f39a1e 100644 (file)
@@ -3413,15 +3413,20 @@ void TryNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
             tryData = generator.pushTry(*catchLabel, *finallyViaThrowLabel, HandlerType::Finally);
         }
 
-        generator.emitPushCatchScope(m_lexicalVariables);
-        m_catchPattern->bindValue(generator, thrownValueRegister.get());
+        if (m_catchPattern) {
+            generator.emitPushCatchScope(m_lexicalVariables);
+            m_catchPattern->bindValue(generator, thrownValueRegister.get());
+        }
+
         generator.emitProfileControlFlow(m_tryBlock->endOffset() + 1);
         if (m_finallyBlock)
             generator.emitNode(dst, m_catchBlock);
         else
             generator.emitNodeInTailPosition(dst, m_catchBlock);
         generator.emitLoad(thrownValueRegister.get(), jsUndefined());
-        generator.emitPopCatchScope(m_lexicalVariables);
+
+        if (m_catchPattern)
+            generator.emitPopCatchScope(m_lexicalVariables);
 
         if (m_finallyBlock) {
             generator.emitSetCompletionType(CompletionType::Normal);
index fdea482..ca6d820 100644 (file)
@@ -1633,27 +1633,32 @@ template <class TreeBuilder> TreeStatement Parser<LexerType>::parseTryStatement(
     if (match(CATCH)) {
         next();
         
-        handleProductionOrFail(OPENPAREN, "(", "start", "'catch' target");
-        AutoPopScopeRef catchScope(this, pushScope());
-        catchScope->setIsLexicalScope();
-        catchScope->preventVarDeclarations();
-        const Identifier* ident = nullptr;
-        if (matchSpecIdentifier()) {
-            ident = m_token.m_data.ident;
-            catchPattern = context.createBindingLocation(m_token.m_location, *ident, m_token.m_startPosition, m_token.m_endPosition, AssignmentContext::DeclarationStatement);
-            next();
-            failIfTrueIfStrict(catchScope->declareLexicalVariable(ident, false) & DeclarationResult::InvalidStrictMode, "Cannot declare a catch variable named '", ident->impl(), "' in strict mode");
+        if (match(OPENBRACE)) {
+            catchBlock = parseBlockStatement(context);
+            failIfFalse(catchBlock, "Unable to parse 'catch' block");
         } else {
-            catchPattern = parseDestructuringPattern(context, DestructuringKind::DestructureToCatchParameters, ExportType::NotExported);
-            failIfFalse(catchPattern, "Cannot parse this destructuring pattern");
+            handleProductionOrFail(OPENPAREN, "(", "start", "'catch' target");
+            AutoPopScopeRef catchScope(this, pushScope());
+            catchScope->setIsLexicalScope();
+            catchScope->preventVarDeclarations();
+            const Identifier* ident = nullptr;
+            if (matchSpecIdentifier()) {
+                ident = m_token.m_data.ident;
+                catchPattern = context.createBindingLocation(m_token.m_location, *ident, m_token.m_startPosition, m_token.m_endPosition, AssignmentContext::DeclarationStatement);
+                next();
+                failIfTrueIfStrict(catchScope->declareLexicalVariable(ident, false) & DeclarationResult::InvalidStrictMode, "Cannot declare a catch variable named '", ident->impl(), "' in strict mode");
+            } else {
+                catchPattern = parseDestructuringPattern(context, DestructuringKind::DestructureToCatchParameters, ExportType::NotExported);
+                failIfFalse(catchPattern, "Cannot parse this destructuring pattern");
+            }
+            handleProductionOrFail(CLOSEPAREN, ")", "end", "'catch' target");
+            matchOrFail(OPENBRACE, "Expected exception handler to be a block statement");
+            catchBlock = parseBlockStatement(context);
+            failIfFalse(catchBlock, "Unable to parse 'catch' block");
+            catchEnvironment = catchScope->finalizeLexicalEnvironment();
+            RELEASE_ASSERT(!ident || (catchEnvironment.size() == 1 && catchEnvironment.contains(ident->impl())));
+            popScope(catchScope, TreeBuilder::NeedsFreeVariableInfo);
         }
-        handleProductionOrFail(CLOSEPAREN, ")", "end", "'catch' target");
-        matchOrFail(OPENBRACE, "Expected exception handler to be a block statement");
-        catchBlock = parseBlockStatement(context);
-        failIfFalse(catchBlock, "Unable to parse 'catch' block");
-        catchEnvironment = catchScope->finalizeLexicalEnvironment();
-        RELEASE_ASSERT(!ident || (catchEnvironment.size() == 1 && catchEnvironment.contains(ident->impl())));
-        popScope(catchScope, TreeBuilder::NeedsFreeVariableInfo);
     }
     
     if (match(FINALLY)) {