Improve type parameter recovery

This fixes another crash in the fasta parser
and improves the way it recovers when parsing type parameters
before a map or list literal.

Change-Id: Ic02513b98a31e9ed3f59c8391c260a2a214983ae
Reviewed-on: https://dart-review.googlesource.com/62340
Commit-Queue: Dan Rubel <danrubel@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/test/generated/parser_test.dart b/pkg/analyzer/test/generated/parser_test.dart
index 3c85986..19a76ce 100644
--- a/pkg/analyzer/test/generated/parser_test.dart
+++ b/pkg/analyzer/test/generated/parser_test.dart
@@ -4451,16 +4451,11 @@
     // It doesn't try to advance past the invalid token `!` to find the
     // valid `>`. If it did we'd get less cascading errors, at least for this
     // particular example.
-    createParser('void m<E, hello!>() {}', expectedEndOffset: 6);
+    createParser('void m<E, hello!>() {}');
     ClassMember member = parser.parseClassMember('C');
     expectNotNullIfNoErrors(member);
     listener.assertErrors(usingFastaParser
-        ? [
-            // TODO(danrubel): Improve recovery
-            expectedError(
-                ParserErrorCode.MISSING_METHOD_PARAMETERS, 5, 1) /*<*/,
-            expectedError(ParserErrorCode.MISSING_FUNCTION_BODY, 6, 1) /*E*/
-          ]
+        ? [expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 15, 1)]
         : [
             expectedError(ParserErrorCode.EXPECTED_TOKEN, 0, 0) /*>*/,
             expectedError(ParserErrorCode.MISSING_IDENTIFIER, 0, 0),
@@ -4470,10 +4465,8 @@
           ]);
     expect(member, new isInstanceOf<MethodDeclaration>());
     MethodDeclaration method = member;
-    if (!usingFastaParser) {
-      expect(method.typeParameters.toString(), '<E, hello>',
-          reason: 'parser recovers what it can');
-    }
+    expect(method.typeParameters.toString(), '<E, hello>',
+        reason: 'parser recovers what it can');
   }
 
   void test_missingAssignableSelector_identifiersAssigned() {
@@ -6758,6 +6751,17 @@
     expect(literal.typeArguments, isNotNull);
   }
 
+  void test_parseConstExpression_mapLiteral_typed_missingGt() {
+    Expression expression = parseExpression('const <A, B {}',
+        errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 12, 1)]);
+    expect(expression, isNotNull);
+    var literal = expression as MapLiteral;
+    expect(literal.leftBracket, isNotNull);
+    expect(literal.entries, hasLength(0));
+    expect(literal.rightBracket, isNotNull);
+    expect(literal.typeArguments, isNotNull);
+  }
+
   void test_parseConstExpression_mapLiteral_typed_genericComment() {
     enableGenericMethodComments = true;
     Expression expression = parseConstExpression('const /*<A, B>*/ {}');
@@ -6836,6 +6840,17 @@
     expect(binaryExpression.rightOperand, isNotNull);
   }
 
+  void test_parseExpression_constAndTypeParameters() {
+    Expression expression = parseExpression('const <E>',
+        codes: usingFastaParser
+            ? [
+                // TODO(danrubel): Improve this error message.
+                ParserErrorCode.EXPECTED_TOKEN
+              ]
+            : [ParserErrorCode.EXPECTED_LIST_OR_MAP_LITERAL]);
+    expect(expression, isNotNull);
+  }
+
   void test_parseExpression_function_async() {
     Expression expression = parseExpression('() async {}');
     var functionExpression = expression as FunctionExpression;
@@ -7022,6 +7037,27 @@
     expect((expression.body as ExpressionFunctionBody).semicolon, isNull);
   }
 
+  void test_parseFunctionExpression_constAndTypeParameters2() {
+    FunctionExpression expression =
+        parseFunctionExpression('const <E>(E i) => i++');
+    expect(expression, isNotNull);
+    assertErrorsWithCodes(usingFastaParser
+        ? [ParserErrorCode.UNEXPECTED_TOKEN]
+        : [
+            ParserErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_IDENTIFIER,
+            ParserErrorCode.FUNCTION_TYPED_PARAMETER_VAR,
+            ParserErrorCode.MISSING_CLOSING_PARENTHESIS,
+          ]);
+    expect(expression.body, isNotNull);
+    if (usingFastaParser) {
+      expect(expression.typeParameters, isNotNull);
+      expect(expression.parameters, isNotNull);
+      expect((expression.body as ExpressionFunctionBody).semicolon, isNull);
+    }
+  }
+
   void test_parseFunctionExpression_typeParameterComments() {
     enableGenericMethodComments = true;
     FunctionExpression expression =
diff --git a/pkg/analyzer/test/src/fasta/recovery/invalid_code_test.dart b/pkg/analyzer/test/src/fasta/recovery/invalid_code_test.dart
index f080fb8..6e680c9 100644
--- a/pkg/analyzer/test/src/fasta/recovery/invalid_code_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/invalid_code_test.dart
@@ -34,16 +34,43 @@
 ''', expectedErrorsInValidCode: [ParserErrorCode.MISSING_IDENTIFIER]);
   }
 
-  @failingTest
   void test_expressionInPlaceOfTypeName() {
     // https://github.com/dart-lang/sdk/issues/30370
     testRecovery('''
 f() {
   return <g('')>[0, 1, 2];
 }
-''', [], '''
+''', [ParserErrorCode.UNEXPECTED_TOKEN], '''
 f() {
-  return <_s_>[0, 1, 2];
+  return <g>[0, 1, 2];
+}
+''');
+  }
+
+  void test_expressionInPlaceOfTypeName2() {
+    // https://github.com/dart-lang/sdk/issues/30370
+    testRecovery('''
+f() {
+  return <test('', (){})>[0, 1, 2];
+}
+''', [ParserErrorCode.UNEXPECTED_TOKEN], '''
+f() {
+  return <test>[0, 1, 2];
+}
+''');
+  }
+
+  @failingTest
+  void test_functionInPlaceOfTypeName() {
+    // https://github.com/dart-lang/sdk/issues/30370
+    // TODO(danrubel): Fix this crash
+    testRecovery('''
+f() {
+  return <test('', (){});>[0, 1, 2];
+}
+''', [ParserErrorCode.UNEXPECTED_TOKEN], '''
+f() {
+  return <test>[0, 1, 2];
 }
 ''');
   }
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 6178e4b..d12753f 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -4105,25 +4105,31 @@
   ///   genericFunctionLiteral ::=
   ///       typeParameters formalParameterList functionBody
   /// Provide token for [constKeyword] if preceded by 'const', null if not.
-  Token parseLiteralListOrMapOrFunction(Token token, Token constKeyword) {
+  Token parseLiteralListOrMapOrFunction(final Token start, Token constKeyword) {
+    assert(optional('<', start.next));
+    TypeParamOrArgInfo typeParamOrArg = computeTypeParamOrArg(start, true);
+    Token token = typeParamOrArg.skip(start);
     Token next = token.next;
-    assert(optional('<', next));
-    Token closeBrace = next.endGroup;
-    if (constKeyword == null &&
-        closeBrace != null &&
-        identical(closeBrace.next.kind, OPEN_PAREN_TOKEN)) {
-      token = computeTypeParamOrArg(token).parseVariables(token, this);
-      return parseLiteralFunctionSuffix(token);
-    } else {
-      token = computeTypeParamOrArg(token).parseArguments(token, this);
-      Token next = token.next;
-      if (optional('{', next)) {
-        return parseLiteralMapSuffix(token, constKeyword);
-      } else if ((optional('[', next)) || (optional('[]', next))) {
-        return parseLiteralListSuffix(token, constKeyword);
+    if (optional('(', next)) {
+      if (constKeyword != null) {
+        reportRecoverableErrorWithToken(
+            constKeyword, fasta.templateUnexpectedToken);
       }
-      return reportUnexpectedToken(token.next);
+      token = typeParamOrArg.parseVariables(start, this);
+      return parseLiteralFunctionSuffix(token);
     }
+    token = typeParamOrArg.parseArguments(start, this);
+    if (optional('{', next)) {
+      return parseLiteralMapSuffix(token, constKeyword);
+    }
+    if (!optional('[', next) && !optional('[]', next)) {
+      // TODO(danrubel): Improve this error message.
+      reportRecoverableError(
+          next, fasta.templateExpectedButGot.withArguments('['));
+      rewriter.insertTokenAfter(
+          token, new SyntheticToken(TokenType.INDEX, next.charOffset));
+    }
+    return parseLiteralListSuffix(token, constKeyword);
   }
 
   /// ```
diff --git a/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart b/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart
index 9a1a945..8d05a29 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart
@@ -603,9 +603,12 @@
       assert(optional('>', endGroup) || optional('>>', endGroup));
       // If `>>`, then the end or last consumed token is the token before `>>`.
       end = optional('>>', next) ? token : next;
-    } else if (inDeclaration && start.endGroup == null) {
-      // Recovery: Unbalanced `<`
-      end = token;
+    } else if (inDeclaration) {
+      end = start.endGroup;
+      if (end == null) {
+        // Recovery: Unbalanced `<`
+        end = token;
+      }
     } else {
       return noTypeParamOrArg;
     }
@@ -614,6 +617,7 @@
 
   @override
   Token parseArguments(Token token, Parser parser) {
+    assert(identical(token.next, start));
     Token next = start;
     Token innerEndGroup = processBeginGroup(start, parser);
     parser.listener.beginTypeArguments(start);
@@ -654,6 +658,7 @@
 
   @override
   Token parseVariables(Token token, Parser parser) {
+    assert(identical(token.next, start));
     Token next = start;
     Listener listener = parser.listener;
     listener.beginTypeVariables(start);