Bug 1195578 - Part 2: Get a token next to an arrow function with block body with Operand modifier. r=Waldo
authorTooru Fujisawa <arai_a@mac.com>
Tue, 18 Aug 2015 10:43:48 +0900
changeset 294314 a36d8aa117c42ec45cc361a3d550a7a0e0afa4a2
parent 294313 e5f7eabe6538a97c1cf43179be28ada7465cbcb4
child 294315 2a20dd7465827a0e64abb9b72628c3e5786ed5fc
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1195578
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1195578 - Part 2: Get a token next to an arrow function with block body with Operand modifier. r=Waldo
js/src/frontend/Parser.cpp
js/src/frontend/TokenStream.h
js/src/jit-test/tests/parser/arrow-with-block.js
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -7070,31 +7070,71 @@ Parser<ParseHandler>::assignExpr(InHandl
         // A line terminator between ArrowParameters and the => should trigger a SyntaxError.
         tokenStream.ungetToken();
         TokenKind next;
         if (!tokenStream.peekTokenSameLine(&next) || next != TOK_ARROW) {
             report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
                    "expression", TokenKindToDesc(TOK_ARROW));
             return null();
         }
+        tokenStream.consumeKnownToken(TOK_ARROW);
+
+        bool isBlock = false;
+        if (!tokenStream.peekToken(&next, TokenStream::Operand))
+            return null();
+        if (next == TOK_LC)
+            isBlock = true;
 
         tokenStream.seek(start);
         if (!abortIfSyntaxParser())
             return null();
 
         TokenKind ignored;
         if (!tokenStream.peekToken(&ignored, TokenStream::Operand))
             return null();
 
         if (pc->sc->isFunctionBox() && pc->sc->asFunctionBox()->isDerivedClassConstructor()) {
             report(ParseError, false, null(), JSMSG_DISABLED_DERIVED_CLASS, "arrow functions");
             return null();
         }
 
-        return functionDef(inHandling, yieldHandling, nullptr, Arrow, NotGenerator);
+        Node arrowFunc = functionDef(inHandling, yieldHandling, nullptr, Arrow, NotGenerator);
+        if (!arrowFunc)
+            return null();
+
+        if (isBlock) {
+            // This arrow function could be a non-trailing member of a comma
+            // expression or a semicolon terminating a full expression.  If so,
+            // the next token is that comma/semicolon, gotten with None:
+            //
+            //   a => {}, b; // as if (a => {}), b;
+            //   a => {};
+            //
+            // But if this arrow function ends a statement, ASI permits the
+            // next token to start an expression statement.  In that case the
+            // next token must be gotten as Operand:
+            //
+            //   a => {} // complete expression statement
+            //   /x/g;   // regular expression as a statement, *not* division
+            //
+            // Getting the second case right requires the first token-peek
+            // after the arrow function use Operand, and that peek must occur
+            // before Parser::expr() looks for a comma.  Do so here, then
+            // immediately add the modifier exception needed for the first
+            // case.
+            //
+            // Note that the second case occurs *only* if the arrow function
+            // has block body.  An arrow function not ending in such, ends in
+            // another AssignmentExpression that we can inductively assume was
+            // peeked consistently.
+            if (!tokenStream.peekToken(&ignored, TokenStream::Operand))
+                return null();
+            tokenStream.addModifierException(TokenStream::NoneIsOperand);
+        }
+        return arrowFunc;
       }
 
       default:
         MOZ_ASSERT(!tokenStream.isCurrentTokenAssignment());
         tokenStream.ungetToken();
         return lhs;
     }
 
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -116,24 +116,38 @@ struct Token
         //   var s = `Hello ${entity}!`;
         //                          ^ TemplateTail context
         TemplateTail,
     };
     enum ModifierException
     {
         NoException,
 
-        // After |yield| we look for a token on the same line that starts an
+        // Used in following 2 cases:
+        // a) After |yield| we look for a token on the same line that starts an
         // expression (Operand): |yield <expr>|.  If no token is found, the
         // |yield| stands alone, and the next token on a subsequent line must
         // be: a comma continuing a comma expression, a semicolon terminating
         // the statement that ended with |yield|, or the start of another
         // statement (possibly an expression statement).  The comma/semicolon
         // cases are gotten as operators (None), contrasting with Operand
         // earlier.
+        // b) After an arrow function with a block body in an expression
+        // statement, the next token must be: a colon in a conditional
+        // expression, a comma continuing a comma expression, a semicolon
+        // terminating the statement, or the token on a subsequent line that is
+        // the start of another statement (possibly an expression statement).
+        // Colon is gotten as operator (None), and it should only be gotten in
+        // conditional expression and missing it results in SyntaxError.
+        // Comma/semicolon cases are also gotten as operators (None), and 4th
+        // case is gotten after them.  If no comma/semicolon found but EOL,
+        // the next token should be gotten as operand in 4th case (especially if
+        // '/' is the first character).  So we should peek the token as
+        // operand before try getting colon/comma/semicolon.
+        // See also the comment in Parser::assignExpr().
         NoneIsOperand,
 
         // If a semicolon is inserted automatically, the next token is already
         // gotten with None, but we expect Operand.
         OperandIsNone,
 
         // If name of method definition is `get` or `set`, the next token is
         // already gotten with KeywordIsName, but we expect None.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parser/arrow-with-block.js
@@ -0,0 +1,92 @@
+load(libdir + "asserts.js");
+
+let x = 10;
+let g = 4;
+
+assertEq(eval(`
+a => {}
+/x/g;
+`).toString(), "/x/g");
+assertEq(eval(`
+a => {}
+/x/;
+`).toString(), "/x/");
+assertThrowsInstanceOf(() => eval(`
+a => {} /x/g;
+`), SyntaxError);
+
+assertEq(eval(`
+a => {},
+/x/;
+`).toString(), "/x/");
+assertEq(eval(`
+a => {}
+,
+/x/;
+`).toString(), "/x/");
+
+assertEq(eval(`
+false ?
+a => {} :
+/x/;
+`).toString(), "/x/");
+assertEq(eval(`
+false ?
+a => {}
+:
+/x/;
+`).toString(), "/x/");
+
+assertEq(eval(`
+a => {};
+/x/;
+`).toString(), "/x/");
+assertEq(eval(`
+a => {}
+;
+/x/;
+`).toString(), "/x/");
+
+assertEq(eval(`
+a => 200
+/x/g;
+`) instanceof Function, true);
+assertEq(eval(`
+a => 200
+/x/g;
+`)(), 5);
+assertEq(eval(`
+a => 200 /x/g;
+`)(), 5);
+
+assertEq(eval(`
+a => 1,
+/x/;
+`).toString(), "/x/");
+assertEq(eval(`
+a => 1
+,
+/x/;
+`).toString(), "/x/");
+
+assertEq(eval(`
+false ?
+a => 1 :
+/x/;
+`).toString(), "/x/");
+assertEq(eval(`
+false ?
+a => 1
+:
+/x/;
+`).toString(), "/x/");
+
+assertEq(eval(`
+a => 1;
+/x/;
+`).toString(), "/x/");
+assertEq(eval(`
+a => 1
+;
+/x/;
+`).toString(), "/x/");