Improve fasta parser expression recovery

This improves the parser's ability to distinguish between an expression
and the start of a new statement.

Change-Id: I07871dc0e2e6f81996bfc99a53bd3dede3db21ae
Reviewed-on: https://dart-review.googlesource.com/62180
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/assert_statement_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/assert_statement_test.dart
index f80505e..decfa5e 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/assert_statement_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/assert_statement_test.dart
@@ -31,7 +31,14 @@
                 ParserErrorCode.EXPECTED_TOKEN
               ],
               "assert (_s_);",
-              failing: allExceptEof),
+              failing: [
+                'assert',
+                'block',
+                'labeled',
+                'localFunctionNonVoid',
+                'localFunctionVoid',
+                'return'
+              ]),
           new TestDescriptor(
               'condition',
               'assert (a',
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/field_declaration_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/field_declaration_test.dart
index a4ddf6b..af989fd 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/field_declaration_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/field_declaration_test.dart
@@ -56,7 +56,13 @@
           'const f =',
           [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
           'const f = _s_;',
-          failing: allExceptEof,
+          failing: [
+            'fieldConst',
+            'methodNonVoid',
+            'methodVoid',
+            'getter',
+            'setter'
+          ],
         ),
         new TestDescriptor(
           'const_initializer',
@@ -86,7 +92,13 @@
           'final f =',
           [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
           'final f = _s_;',
-          failing: allExceptEof,
+          failing: [
+            'fieldConst',
+            'methodNonVoid',
+            'methodVoid',
+            'getter',
+            'setter'
+          ],
         ),
         new TestDescriptor(
           'final_initializer',
@@ -116,7 +128,13 @@
           'var f =',
           [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
           'var f = _s_;',
-          failing: allExceptEof,
+          failing: [
+            'fieldConst',
+            'methodNonVoid',
+            'methodVoid',
+            'getter',
+            'setter'
+          ],
         ),
         new TestDescriptor(
           'var_initializer',
@@ -148,7 +166,13 @@
           'A f =',
           [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
           'A f = _s_;',
-          failing: allExceptEof,
+          failing: [
+            'fieldConst',
+            'methodNonVoid',
+            'methodVoid',
+            'getter',
+            'setter'
+          ],
         ),
         new TestDescriptor(
           'type_initializer',
@@ -187,7 +211,13 @@
           'static const f =',
           [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
           'static const f = _s_;',
-          failing: allExceptEof,
+          failing: [
+            'fieldConst',
+            'methodNonVoid',
+            'methodVoid',
+            'getter',
+            'setter'
+          ],
         ),
         new TestDescriptor(
           'static_const_initializer',
@@ -217,7 +247,13 @@
           'static final f =',
           [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
           'static final f = _s_;',
-          failing: allExceptEof,
+          failing: [
+            'fieldConst',
+            'methodNonVoid',
+            'methodVoid',
+            'getter',
+            'setter'
+          ],
         ),
         new TestDescriptor(
           'static_final_initializer',
@@ -247,7 +283,13 @@
           'static var f =',
           [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
           'static var f = _s_;',
-          failing: allExceptEof,
+          failing: [
+            'fieldConst',
+            'methodNonVoid',
+            'methodVoid',
+            'getter',
+            'setter'
+          ],
         ),
         new TestDescriptor(
           'static_var_initializer',
@@ -279,7 +321,13 @@
           'static A f =',
           [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
           'static A f = _s_;',
-          failing: allExceptEof,
+          failing: [
+            'fieldConst',
+            'methodNonVoid',
+            'methodVoid',
+            'getter',
+            'setter'
+          ],
         ),
         new TestDescriptor(
           'static_type_initializer',
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/local_variable_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/local_variable_test.dart
index 388eec1..dc875ff 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/local_variable_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/local_variable_test.dart
@@ -139,7 +139,14 @@
                 ParserErrorCode.EXPECTED_TOKEN
               ],
               "var a = _s_;",
-              failing: allExceptEof),
+              failing: [
+                'block',
+                'assert',
+                'labeled',
+                'localFunctionNonVoid',
+                'localFunctionVoid',
+                'return'
+              ]),
           new TestDescriptor('varNameEqualsExpression', 'var a = b',
               [ParserErrorCode.EXPECTED_TOKEN], "var a = b;"),
         ],
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/top_level_variable_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/top_level_variable_test.dart
index bb3264c..76e6f1c 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/top_level_variable_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/top_level_variable_test.dart
@@ -214,7 +214,15 @@
               ParserErrorCode.EXPECTED_TOKEN
             ],
             "var a = _s_;",
-            failing: allExceptEof,
+            failing: [
+              'class',
+              'typedef',
+              'functionVoid',
+              'functionNonVoid',
+              'const',
+              'getter',
+              'setter'
+            ],
           ),
           new TestDescriptor(
             'varNameEqualsExpression',
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/yield_statement_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/yield_statement_test.dart
index 0234d1f..bc15d07 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/yield_statement_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/yield_statement_test.dart
@@ -26,19 +26,10 @@
               failing: [
                 'assert',
                 'block',
-                'break',
-                'continue',
-                'do',
-                'if',
-                'for',
                 'labeled',
                 'localFunctionNonVoid',
                 'localFunctionVoid',
-                'localVariable',
-                'switch',
-                'try',
                 'return',
-                'while'
               ]),
           new TestDescriptor('expression', 'yield a',
               [ParserErrorCode.EXPECTED_TOKEN], "yield a;"),
@@ -53,19 +44,10 @@
               failing: [
                 'assert',
                 'block',
-                'break',
-                'continue',
-                'do',
-                'if',
-                'for',
                 'labeled',
                 'localFunctionNonVoid',
                 'localFunctionVoid',
-                'localVariable',
-                'switch',
-                'try',
                 'return',
-                'while'
               ]),
           new TestDescriptor('star_expression', 'yield * a',
               [ParserErrorCode.EXPECTED_TOKEN], "yield * a;"),
diff --git a/pkg/front_end/lib/src/fasta/parser/identifier_context.dart b/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
index 601835b..43c121c 100644
--- a/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
+++ b/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import '../../scanner/token.dart' show Token;
+import '../../scanner/token.dart' show Token, TokenType;
 
 import '../fasta_codes.dart' show Message, Template, templateExpectedIdentifier;
 
@@ -12,6 +12,8 @@
 
 import 'parser.dart' show Parser;
 
+import 'util.dart' show isOneOfOrEof, optional;
+
 /// Information about the parser state that is passed to the listener at the
 /// time an identifier is encountered. It is also used by the parser for error
 /// recovery when a recovery template is defined.
@@ -269,5 +271,45 @@
   }
 }
 
+/// Return `true` if the given [token] should be treated like the start of
+/// an expression for the purposes of recovery.
+bool looksLikeExpressionStart(Token next) =>
+    next.isIdentifier ||
+    next.isKeyword && !looksLikeStatementStart(next) ||
+    next.type == TokenType.DOUBLE ||
+    next.type == TokenType.HASH ||
+    next.type == TokenType.HEXADECIMAL ||
+    next.type == TokenType.IDENTIFIER ||
+    next.type == TokenType.INT ||
+    next.type == TokenType.STRING ||
+    optional('{', next) ||
+    optional('(', next) ||
+    optional('[', next) ||
+    optional('[]', next) ||
+    optional('<', next) ||
+    optional('!', next) ||
+    optional('-', next) ||
+    optional('~', next) ||
+    optional('++', next) ||
+    optional('--', next);
+
+/// Return `true` if the given [token] should be treated like the start of
+/// a new statement for the purposes of recovery.
+bool looksLikeStatementStart(Token token) => isOneOfOrEof(token, const [
+      'assert',
+      'break',
+      'continue',
+      'do',
+      'final',
+      'for',
+      'if',
+      'return',
+      'switch',
+      'try',
+      'var',
+      'void',
+      'while'
+    ]);
+
 // TODO(ahe): Remove when analyzer supports generalized function syntax.
 typedef _MessageWithArgument<T> = Message Function(T);
diff --git a/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart b/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart
index e8cab59..c79c8a4 100644
--- a/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart
+++ b/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart
@@ -31,7 +31,7 @@
 
     // Recovery
     parser.reportRecoverableError(identifier, fasta.messageCatchSyntax);
-    if (looksLikeStartOfNextStatement(identifier) ||
+    if (looksLikeStatementStart(identifier) ||
         isOneOfOrEof(identifier, const [',', ')'])) {
       return parser.rewriter.insertSyntheticIdentifier(token);
     } else if (!identifier.isKeywordOrIdentifier) {
@@ -291,16 +291,18 @@
     // Recovery
     parser.reportRecoverableErrorWithToken(
         identifier, fasta.templateExpectedIdentifier);
-    if (identifier.isKeywordOrIdentifier) {
-      if (!isOneOfOrEof(identifier, const ['as', 'is'])) {
-        return identifier;
+    if (!looksLikeStatementStart(identifier)) {
+      if (identifier.isKeywordOrIdentifier) {
+        if (!isOneOfOrEof(identifier, const ['as', 'is'])) {
+          return identifier;
+        }
+      } else if (!identifier.isOperator &&
+          !isOneOfOrEof(identifier,
+              const ['.', ',', '(', ')', '[', ']', '}', '?', ':', ';'])) {
+        // When in doubt, consume the token to ensure we make progress
+        token = identifier;
+        identifier = token.next;
       }
-    } else if (!identifier.isOperator &&
-        !isOneOfOrEof(identifier,
-            const ['.', ',', '(', ')', '[', ']', '}', '?', ':', ';'])) {
-      // When in doubt, consume the token to ensure we make progress
-      token = identifier;
-      identifier = token.next;
     }
     // Insert a synthetic identifier to satisfy listeners.
     return parser.rewriter.insertSyntheticIdentifier(token);
@@ -377,7 +379,7 @@
     // Recovery
     const followingValues = const [':', '=', ',', '(', ')', '[', ']', '{', '}'];
     if (looksLikeStartOfNextClassMember(identifier) ||
-        looksLikeStartOfNextStatement(identifier) ||
+        looksLikeStatementStart(identifier) ||
         isOneOfOrEof(identifier, followingValues)) {
       identifier = parser.insertSyntheticIdentifier(token, this,
           message: fasta.templateExpectedIdentifier.withArguments(identifier));
@@ -474,7 +476,7 @@
 
     // Recovery
     if (isOneOfOrEof(identifier, const ['.', '(', '{', '=>']) ||
-        looksLikeStartOfNextStatement(identifier)) {
+        looksLikeStatementStart(identifier)) {
       identifier = parser.insertSyntheticIdentifier(token, this,
           message: fasta.templateExpectedIdentifier.withArguments(identifier));
     } else {
@@ -506,7 +508,7 @@
 
     // Recovery
     if (isOneOfOrEof(identifier, const [':']) ||
-        looksLikeStartOfNextStatement(identifier)) {
+        looksLikeStatementStart(identifier)) {
       identifier = parser.insertSyntheticIdentifier(token, this,
           message: fasta.templateExpectedIdentifier.withArguments(identifier));
     } else {
@@ -622,7 +624,7 @@
 
     // Recovery
     if (isOneOfOrEof(identifier, const [';', '=', ',', '{', '}']) ||
-        looksLikeStartOfNextStatement(identifier)) {
+        looksLikeStatementStart(identifier)) {
       identifier = parser.insertSyntheticIdentifier(token, this,
           message: fasta.templateExpectedIdentifier.withArguments(identifier));
     } else {
@@ -664,7 +666,7 @@
     if (isOneOfOrEof(identifier, const ['{', '}', '(', ')', ']']) ||
         looksLikeStartOfNextTopLevelDeclaration(identifier) ||
         looksLikeStartOfNextClassMember(identifier) ||
-        looksLikeStartOfNextStatement(identifier)) {
+        looksLikeStatementStart(identifier)) {
       identifier = parser.insertSyntheticIdentifier(token, this,
           message: fasta.templateExpectedIdentifier.withArguments(identifier));
     } else {
@@ -908,7 +910,7 @@
     const followingValues = const ['<', '>', ';', '}', 'extends', 'super'];
     if (looksLikeStartOfNextTopLevelDeclaration(identifier) ||
         looksLikeStartOfNextClassMember(identifier) ||
-        looksLikeStartOfNextStatement(identifier) ||
+        looksLikeStatementStart(identifier) ||
         isOneOfOrEof(identifier, followingValues)) {
       parser.reportRecoverableErrorWithToken(
           identifier, fasta.templateExpectedIdentifier);
@@ -944,23 +946,6 @@
 bool looksLikeStartOfNextClassMember(Token token) =>
     token.isModifier || isOneOfOrEof(token, const ['get', 'set', 'void']);
 
-bool looksLikeStartOfNextStatement(Token token) => isOneOfOrEof(token, const [
-      'assert',
-      'break',
-      'const',
-      'continue',
-      'do',
-      'final',
-      'for',
-      'if',
-      'return',
-      'switch',
-      'try',
-      'var',
-      'void',
-      'while'
-    ]);
-
 bool looksLikeStartOfNextTopLevelDeclaration(Token token) =>
     token.isTopLevelKeyword ||
     isOneOfOrEof(token, const ['const', 'get', 'final', 'set', 'var', 'void']);
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 170db1e..6178e4b 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -63,7 +63,8 @@
 
 import 'forwarding_listener.dart' show ForwardingListener;
 
-import 'identifier_context.dart' show IdentifierContext;
+import 'identifier_context.dart'
+    show IdentifierContext, looksLikeExpressionStart;
 
 import 'listener.dart' show Listener;
 
@@ -1903,27 +1904,6 @@
     return identifier;
   }
 
-  /// Return `true` if the given [token] should be treated like the start of
-  /// an expression for the purposes of recovery.
-  bool isExpressionStartForRecovery(Token next) =>
-      next.isKeywordOrIdentifier ||
-      next.type == TokenType.DOUBLE ||
-      next.type == TokenType.HASH ||
-      next.type == TokenType.HEXADECIMAL ||
-      next.type == TokenType.IDENTIFIER ||
-      next.type == TokenType.INT ||
-      next.type == TokenType.STRING ||
-      optional('{', next) ||
-      optional('(', next) ||
-      optional('[', next) ||
-      optional('[]', next) ||
-      optional('<', next) ||
-      optional('!', next) ||
-      optional('-', next) ||
-      optional('~', next) ||
-      optional('++', next) ||
-      optional('--', next);
-
   Token expect(String string, Token token) {
     // TODO(danrubel): update all uses of expect(';'...) to ensureSemicolon
     // then add assert(!identical(';', string));
@@ -4010,7 +3990,7 @@
         }
 
         // Recovery
-        if (!isExpressionStartForRecovery(next)) {
+        if (!looksLikeExpressionStart(next)) {
           if (beginToken.endGroup.isSynthetic) {
             // The scanner has already reported an error,
             // but inserted `]` in the wrong place.
@@ -4070,7 +4050,7 @@
           break;
         }
         // Recovery
-        if (isExpressionStartForRecovery(next)) {
+        if (looksLikeExpressionStart(next)) {
           // If this looks like the start of an expression,
           // then report an error, insert the comma, and continue parsing.
           next = rewriteAndRecover(
@@ -4487,7 +4467,7 @@
           break;
         }
         // Recovery
-        if (isExpressionStartForRecovery(next)) {
+        if (looksLikeExpressionStart(next)) {
           // If this looks like the start of an expression,
           // then report an error, insert the comma, and continue parsing.
           next = rewriteAndRecover(