Revise fasta type arg/param parsing to ignore "<".endGroup

The scanner associates '<' with '>' and '>>',
but the parser cannot use '<'.endToken/endGroup reliably
and wastes time updating that token's endToken/endGroup field.

This CL updates type info parsing to ignore '<'.endGroup
and step through the token stream (which it has to do anyway),
for improved parsing.

This CL causes a handful of existing tests to fail (marked as @failingTest)
and test failures will be addressed in a subsequent CL.

Change-Id: I788e9a74cd2891f98f44bba15d5669febfec6c64
Reviewed-on: https://dart-review.googlesource.com/70001
Commit-Queue: Dan Rubel <danrubel@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/test/src/computer/closingLabels_computer_test.dart b/pkg/analysis_server/test/src/computer/closingLabels_computer_test.dart
index 2c9e9dd..f63237e 100644
--- a/pkg/analysis_server/test/src/computer/closingLabels_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/closingLabels_computer_test.dart
@@ -412,8 +412,4 @@
 class ClosingLabelsComputerTest_UseCFE extends ClosingLabelsComputerTest {
   @override
   bool get useCFE => true;
-
-  @failingTest
-  @override
-  test_knownBadCode1() => super.test_knownBadCode1();
 }
diff --git a/pkg/analyzer/test/generated/parser_fasta_test.dart b/pkg/analyzer/test/generated/parser_fasta_test.dart
index 92a6f6a..24cd719 100644
--- a/pkg/analyzer/test/generated/parser_fasta_test.dart
+++ b/pkg/analyzer/test/generated/parser_fasta_test.dart
@@ -1018,7 +1018,21 @@
 
 @reflectiveTest
 class SimpleParserTest_Fasta extends FastaParserTestCase
-    with SimpleParserTestMixin {}
+    with SimpleParserTestMixin {
+  @override
+  @failingTest
+  void test_parseTypeParameterList_parameterizedWithTrailingEquals() {
+    // TODO(danrubel): Update TypeInfo to handle '>>='
+    super.test_parseTypeParameterList_parameterizedWithTrailingEquals();
+  }
+
+  @override
+  @failingTest
+  void test_parseTypeParameterList_parameterizedWithTrailingEquals2() {
+    // TODO(danrubel): Update TypeInfo to handle '>>='
+    super.test_parseTypeParameterList_parameterizedWithTrailingEquals2();
+  }
+}
 
 /**
  * Tests of the fasta parser based on [StatementParserTestMixin].
diff --git a/pkg/analyzer/test/generated/parser_test.dart b/pkg/analyzer/test/generated/parser_test.dart
index e7298b6..9f24489 100644
--- a/pkg/analyzer/test/generated/parser_test.dart
+++ b/pkg/analyzer/test/generated/parser_test.dart
@@ -2674,7 +2674,6 @@
             expectedError(
                 CompileTimeErrorCode.TYPE_PARAMETER_ON_CONSTRUCTOR, 11, 2),
             expectedError(ParserErrorCode.MISSING_IDENTIFIER, 13, 1),
-            expectedError(ParserErrorCode.EXPECTED_TOKEN, 13, 1),
             expectedError(ParserErrorCode.MISSING_METHOD_PARAMETERS, 10, 1),
             expectedError(ParserErrorCode.MISSING_FUNCTION_BODY, 13, 1),
           ]
@@ -2694,7 +2693,6 @@
             expectedError(
                 CompileTimeErrorCode.TYPE_PARAMETER_ON_CONSTRUCTOR, 11, 6),
             expectedError(ParserErrorCode.MISSING_IDENTIFIER, 17, 1),
-            expectedError(ParserErrorCode.EXPECTED_TOKEN, 17, 1),
             expectedError(ParserErrorCode.MISSING_METHOD_PARAMETERS, 10, 1),
             expectedError(ParserErrorCode.MISSING_FUNCTION_BODY, 17, 1)
           ]
@@ -2714,7 +2712,6 @@
             expectedError(
                 CompileTimeErrorCode.TYPE_PARAMETER_ON_CONSTRUCTOR, 11, 13),
             expectedError(ParserErrorCode.MISSING_IDENTIFIER, 24, 1),
-            expectedError(ParserErrorCode.EXPECTED_TOKEN, 24, 1),
             expectedError(ParserErrorCode.MISSING_METHOD_PARAMETERS, 10, 1),
             expectedError(ParserErrorCode.MISSING_FUNCTION_BODY, 24, 1)
           ]
@@ -6751,7 +6748,7 @@
 
   void test_parseConstExpression_mapLiteral_typed_missingGt() {
     Expression expression = parseExpression('const <A, B {}',
-        errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 12, 1)]);
+        errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 10, 1)]);
     expect(expression, isNotNull);
     var literal = expression as MapLiteral;
     expect(literal.leftBracket, isNotNull);
@@ -7043,20 +7040,17 @@
   void test_parseFunctionExpression_functionInPlaceOfTypeName() {
     Expression expression = parseExpression('<test(' ', (){});>[0, 1, 2]',
         codes: usingFastaParser
-            ? [
-                ParserErrorCode.EXPECTED_TOKEN,
-                ParserErrorCode.UNEXPECTED_TOKEN,
-                ParserErrorCode.MISSING_IDENTIFIER,
-                ParserErrorCode.MISSING_IDENTIFIER,
-                ParserErrorCode.EXPECTED_TOKEN,
-                ParserErrorCode.MISSING_FUNCTION_BODY,
-              ]
+            ? [ParserErrorCode.UNEXPECTED_TOKEN]
             : [
                 ParserErrorCode.EXPECTED_TOKEN,
                 ParserErrorCode.MISSING_IDENTIFIER,
                 ParserErrorCode.EXPECTED_LIST_OR_MAP_LITERAL,
               ]);
     expect(expression, isNotNull);
+    if (usingFastaParser) {
+      ListLiteral literal = expression;
+      expect(literal.typeArguments.arguments, hasLength(1));
+    }
   }
 
   void test_parseFunctionExpression_typeParameterComments() {
@@ -11281,7 +11275,7 @@
     result[new Symbol(name)] = value;
   });
   return result;
-}''', errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 36, 1)]);
+}''', errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 12, 24)]);
     }
   }
 
@@ -11610,7 +11604,7 @@
     CompilationUnit unit = parseCompilationUnit(r'''
 class C {
   final List<int f;
-}''', errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 27, 1)]);
+}''', errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 23, 3)]);
     // one class
     List<CompilationUnitMember> declarations = unit.declarations;
     expect(declarations, hasLength(1));
@@ -11637,7 +11631,7 @@
   void test_incompleteTypeParameters() {
     CompilationUnit unit = parseCompilationUnit(r'''
 class C<K {
-}''', errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 10, 1)]);
+}''', errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 8, 1)]);
     // one class
     List<CompilationUnitMember> declarations = unit.declarations;
     expect(declarations, hasLength(1));
@@ -11654,7 +11648,7 @@
   void test_incompleteTypeParameters2() {
     CompilationUnit unit = parseCompilationUnit(r'''
 class C<K extends L<T> {
-}''', errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 23, 1)]);
+}''', errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 21, 1)]);
     // one class
     List<CompilationUnitMember> declarations = unit.declarations;
     expect(declarations, hasLength(1));
@@ -11671,10 +11665,13 @@
   void test_incompleteTypeParameters3() {
     CompilationUnit unit = parseCompilationUnit(r'''
 class C<K extends L<T {
-}''', errors: [
-      expectedError(ParserErrorCode.EXPECTED_TOKEN, 22, 1),
-      expectedError(ParserErrorCode.EXPECTED_TOKEN, 22, 1)
-    ]);
+}''',
+        errors: usingFastaParser
+            ? [expectedError(ParserErrorCode.EXPECTED_TOKEN, 20, 1)]
+            : [
+                expectedError(ParserErrorCode.EXPECTED_TOKEN, 20, 1),
+                expectedError(ParserErrorCode.EXPECTED_TOKEN, 22, 1)
+              ]);
     // one class
     List<CompilationUnitMember> declarations = unit.declarations;
     expect(declarations, hasLength(1));
@@ -11693,6 +11690,29 @@
         codes: [ParserErrorCode.MISSING_STAR_AFTER_SYNC]);
   }
 
+  void test_invalidMapLiteral() {
+    parseCompilationUnit("class C { var f = Map<A, B> {}; }",
+        codes: usingFastaParser
+            ? [
+                // TODO(danrubel): Improve error message to indicate
+                // that "Map" should be removed.
+                ParserErrorCode.EXPECTED_TOKEN,
+                ParserErrorCode.MISSING_KEYWORD_OPERATOR,
+                ParserErrorCode.MISSING_METHOD_PARAMETERS,
+                ParserErrorCode.EXPECTED_CLASS_MEMBER,
+              ]
+            : [
+                ParserErrorCode.EXPECTED_TOKEN,
+                ParserErrorCode.EXPECTED_CLASS_MEMBER,
+                ParserErrorCode.EXPECTED_CLASS_MEMBER,
+                ParserErrorCode.UNEXPECTED_TOKEN,
+                ParserErrorCode.UNEXPECTED_TOKEN,
+                ParserErrorCode.UNEXPECTED_TOKEN,
+                ParserErrorCode.UNEXPECTED_TOKEN,
+                ParserErrorCode.EXPECTED_EXECUTABLE,
+              ]);
+  }
+
   void test_invalidTypeParameters() {
     CompilationUnit unit = parseCompilationUnit(r'''
 class C {
diff --git a/pkg/analyzer/test/src/fasta/recovery/paired_tokens_test.dart b/pkg/analyzer/test/src/fasta/recovery/paired_tokens_test.dart
index edc6d29..74849c3 100644
--- a/pkg/analyzer/test/src/fasta/recovery/paired_tokens_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/paired_tokens_test.dart
@@ -62,6 +62,7 @@
 ''');
   }
 
+  @failingTest
   void test_typeParameters_gtGtEq() {
     testRecovery('''
 f<T extends List<int>>=() => null;
diff --git a/pkg/front_end/lib/src/fasta/parser/token_stream_rewriter.dart b/pkg/front_end/lib/src/fasta/parser/token_stream_rewriter.dart
index 0c8d710..cc01a67 100644
--- a/pkg/front_end/lib/src/fasta/parser/token_stream_rewriter.dart
+++ b/pkg/front_end/lib/src/fasta/parser/token_stream_rewriter.dart
@@ -14,9 +14,6 @@
         Token,
         TokenType;
 
-import 'util.dart'
-    show optional, splitGtEq, splitGtGt, splitGtGtEq, syntheticGt;
-
 /// Provides the capability of inserting tokens into a token stream. This
 /// implementation does this by rewriting the previous token to point to the
 /// inserted token.
@@ -95,7 +92,7 @@
     return previousToken;
   }
 
-  /// Move [endGroup] (a synthetic `)`, `]`, `}`, or `>` token) and associated
+  /// Move [endGroup] (a synthetic `)`, `]`, or `}` token) and associated
   /// error token after [token] in the token stream and return [endGroup].
   Token moveSynthetic(Token token, Token endGroup) {
     assert(endGroup.beforeSynthetic != null);
@@ -134,43 +131,6 @@
     return replacementToken;
   }
 
-  /// Split a `>>` token into two separate `>` tokens, updates the token stream,
-  /// and returns the first `>`. If [start].endGroup is `>>` then sets
-  /// [start].endGroup to the second `>` but does not set the inner group's
-  /// endGroup, otherwise sets [start].endGroup to the first `>`.
-  Token splitEndGroup(BeginToken start, [Token end]) {
-    end ??= start.endGroup;
-    assert(end != null);
-
-    Token gt;
-    if (optional('>>', end)) {
-      gt = splitGtGt(end);
-    } else if (optional('>=', end)) {
-      gt = splitGtEq(end);
-    } else if (optional('>>=', end)) {
-      gt = splitGtGtEq(end);
-    } else {
-      gt = syntheticGt(end);
-    }
-
-    Token token = start;
-    Token next = token.next;
-    while (!identical(next, end)) {
-      token = next;
-      next = token.next;
-    }
-    token.setNext(gt);
-
-    if (start.endGroup != null) {
-      assert(optional('>>', start.endGroup));
-      start.endGroup = gt.next;
-    } else {
-      // Recovery
-      start.endGroup = gt;
-    }
-    return gt;
-  }
-
   /// Given the [firstToken] in a chain of tokens to be inserted, return the
   /// last token in the chain.
   ///
diff --git a/pkg/front_end/lib/src/fasta/parser/type_info.dart b/pkg/front_end/lib/src/fasta/parser/type_info.dart
index b463e33..317cc51 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info.dart
@@ -125,17 +125,13 @@
 ///
 /// If [inDeclaration] is `true`, then this will more aggressively recover
 /// given unbalanced `<` `>` and invalid parameters or arguments.
-///
-/// If this method is called by [computeTypeParamOrArg] and the outer group ends
-/// with `>>`, then then [innerEndGroup] is set to either `>>` if the token
-/// has not been split or the first `>` if the `>>` token has been split.
 TypeInfo computeType(final Token token, bool required,
-    [bool inDeclaration = false, Token innerEndGroup]) {
+    [bool inDeclaration = false]) {
   Token next = token.next;
   if (!isValidTypeReference(next)) {
     if (next.type.isBuiltIn) {
       TypeParamOrArgInfo typeParamOrArg =
-          computeTypeParamOrArg(next, inDeclaration, innerEndGroup);
+          computeTypeParamOrArg(next, inDeclaration);
       if (typeParamOrArg != noTypeParamOrArg) {
         // Recovery: built-in `<` ... `>`
         if (required || looksLikeName(typeParamOrArg.skip(next).next)) {
@@ -156,7 +152,7 @@
     } else if (required && optional('.', next)) {
       // Recovery: looks like prefixed type missing the prefix
       return new ComplexTypeInfo(
-              token, computeTypeParamOrArg(next, inDeclaration, innerEndGroup))
+              token, computeTypeParamOrArg(next, inDeclaration))
           .computePrefixedType(required);
     }
     return noType;
@@ -182,7 +178,7 @@
   // We've seen an identifier.
 
   TypeParamOrArgInfo typeParamOrArg =
-      computeTypeParamOrArg(next, inDeclaration, innerEndGroup);
+      computeTypeParamOrArg(next, inDeclaration);
   if (typeParamOrArg != noTypeParamOrArg) {
     if (typeParamOrArg.isSimpleTypeArgument) {
       // We've seen identifier `<` identifier `>`
@@ -212,8 +208,7 @@
     next = next.next;
     if (isValidTypeReference(next)) {
       // We've seen identifier `.` identifier
-      typeParamOrArg =
-          computeTypeParamOrArg(next, inDeclaration, innerEndGroup);
+      typeParamOrArg = computeTypeParamOrArg(next, inDeclaration);
       next = next.next;
       if (typeParamOrArg == noTypeParamOrArg &&
           !isGeneralizedFunctionType(next)) {
@@ -231,8 +226,7 @@
     }
     // identifier `.` non-identifier
     if (required) {
-      typeParamOrArg =
-          computeTypeParamOrArg(token.next.next, inDeclaration, innerEndGroup);
+      typeParamOrArg = computeTypeParamOrArg(token.next.next, inDeclaration);
       return new ComplexTypeInfo(token, typeParamOrArg)
           .computePrefixedType(required);
     }
@@ -259,42 +253,30 @@
 ///
 /// If [inDeclaration] is `true`, then this will more aggressively recover
 /// given unbalanced `<` `>` and invalid parameters or arguments.
-///
-/// If this method is called by [computeType] and the outer group ends
-/// with `>>`, then then [innerEndGroup] is set to either `>>` if the token
-/// has not been split or the first `>` if the `>>` token has been split.
 TypeParamOrArgInfo computeTypeParamOrArg(Token token,
-    [bool inDeclaration = false, Token innerEndGroup]) {
+    [bool inDeclaration = false]) {
   Token beginGroup = token.next;
   if (!optional('<', beginGroup)) {
     return noTypeParamOrArg;
   }
-  Token endGroup = beginGroup.endGroup ?? innerEndGroup;
-  if (endGroup == null) {
-    if (!inDeclaration) {
-      return noTypeParamOrArg;
-    }
-    // Recovery:
-    // Since the leading `<` cannot be part of an expression, fall through and
-    // try to more aggressively recover given an unbalanced '<'.
-  }
 
   // identifier `<` `void` `>` and `<` `dynamic` `>`
   // are handled by ComplexTypeInfo.
-  Token identifier = beginGroup.next;
-  if ((identifier.kind == IDENTIFIER_TOKEN || identifier.type.isPseudo)) {
-    if (optional('>', identifier.next)) {
+  Token next = beginGroup.next;
+  if ((next.kind == IDENTIFIER_TOKEN || next.type.isPseudo)) {
+    if (optional('>', next.next)) {
       return simpleTypeArgument1;
-    } else if (optional('>>', identifier.next)) {
+    } else if (optional('>>', next.next)) {
       return simpleTypeArgument1GtGt;
-    } else if (optional('>=', identifier.next)) {
+    } else if (optional('>=', next.next)) {
       return simpleTypeArgument1GtEq;
     }
+  } else if (optional('(', next)) {
+    return noTypeParamOrArg;
   }
 
   // TODO(danrubel): Consider adding additional const for common situations.
-  return new ComplexTypeParamOrArgInfo(token, inDeclaration)
-      .compute(innerEndGroup);
+  return new ComplexTypeParamOrArgInfo(token, inDeclaration).compute();
 }
 
 /// Called by the parser to obtain information about a possible group of type
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 a327d8f..2324a81 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
@@ -5,7 +5,7 @@
 library fasta.parser.type_info_impl;
 
 import '../../scanner/token.dart'
-    show BeginToken, SyntheticToken, Token, TokenType;
+    show SimpleToken, SyntheticToken, Token, TokenType;
 
 import '../fasta_codes.dart' as fasta;
 
@@ -23,7 +23,8 @@
 
 import 'type_info.dart';
 
-import 'util.dart' show isOneOf, optional, skipMetadata, splitGtEq, splitGtGt;
+import 'util.dart'
+    show optional, skipMetadata, splitGtEq, splitGtGt, syntheticGt;
 
 /// [SimpleType] is a specialized [TypeInfo] returned by [computeType]
 /// when there is a single identifier as the type reference.
@@ -275,7 +276,8 @@
   if (inDeclaration && token.kind == IDENTIFIER_TOKEN) {
     Token next = token.next;
     if (next.kind == IDENTIFIER_TOKEN ||
-        isOneOf(next, const [',', '>', '>>'])) {
+        optional(',', next) ||
+        isCloser(next)) {
       return true;
     }
   }
@@ -330,8 +332,10 @@
           token, IdentifierContext.prefixedTypeReference);
     }
 
+    final typeVariableEndGroups = <Token>[];
     for (Link<Token> t = typeVariableStarters; t.isNotEmpty; t = t.tail) {
-      computeTypeParamOrArg(t.head, true).parseVariables(t.head, parser);
+      typeVariableEndGroups.add(
+          computeTypeParamOrArg(t.head, true).parseVariables(t.head, parser));
       parser.listener.beginFunctionType(start);
     }
 
@@ -364,14 +368,17 @@
       }
     }
 
+    int endGroupIndex = typeVariableEndGroups.length - 1;
     for (Link<Token> t = typeVariableStarters; t.isNotEmpty; t = t.tail) {
       token = token.next;
       assert(optional('Function', token));
       Token functionToken = token;
       if (optional("<", token.next)) {
         // Skip type parameters, they were parsed above.
-        token = token.next.endGroup;
+        token = typeVariableEndGroups[endGroupIndex];
+        assert(optional('>', token));
       }
+      --endGroupIndex;
       token = parser.parseFormalParametersRequiredOpt(
           token, MemberKind.GeneralizedFunctionType);
       parser.listener.endFunctionType(functionToken);
@@ -545,9 +552,9 @@
 
   @override
   Token parseArguments(Token token, Parser parser) {
-    BeginToken beginGroup = token.next;
+    Token beginGroup = token.next;
     assert(optional('<', beginGroup));
-    Token endGroup = updateEndGroup(beginGroup, beginGroup.next);
+    Token endGroup = parseEndGroup(beginGroup, beginGroup.next);
     Listener listener = parser.listener;
     listener.beginTypeArguments(beginGroup);
     simpleType.parseType(beginGroup, parser);
@@ -557,10 +564,10 @@
 
   @override
   Token parseVariables(Token token, Parser parser) {
-    BeginToken beginGroup = token.next;
+    Token beginGroup = token.next;
     assert(optional('<', beginGroup));
     token = beginGroup.next;
-    Token endGroup = updateEndGroup(beginGroup, token);
+    Token endGroup = parseEndGroup(beginGroup, token);
     Listener listener = parser.listener;
     listener.beginTypeVariables(beginGroup);
     listener.beginMetadataStar(token);
@@ -589,10 +596,9 @@
     return token;
   }
 
-  Token updateEndGroup(BeginToken beginGroup, Token token) {
+  Token parseEndGroup(Token beginGroup, Token token) {
     token = token.next;
     assert(optional('>', token));
-    beginGroup.endGroup = token;
     return token;
   }
 }
@@ -609,12 +615,12 @@
     return splitGtEq(token);
   }
 
-  Token updateEndGroup(BeginToken beginGroup, Token beforeEndGroup) {
+  Token parseEndGroup(Token beginGroup, Token beforeEndGroup) {
     Token endGroup = beforeEndGroup.next;
     if (!optional('>', endGroup)) {
       endGroup = splitGtEq(endGroup);
+      endGroup.next.setNext(endGroup.next.next);
     }
-    beginGroup.endGroup = endGroup;
     beforeEndGroup.setNext(endGroup);
     return endGroup;
   }
@@ -627,16 +633,17 @@
   TypeInfo get typeInfo => simpleTypeWith1ArgumentGtGt;
 
   Token skipEndGroup(Token token) {
-    assert(optional('>>', token.next));
-    return token;
+    token = token.next;
+    assert(optional('>>', token));
+    return splitGtGt(token);
   }
 
-  Token updateEndGroup(BeginToken beginGroup, Token beforeEndGroup) {
+  Token parseEndGroup(Token beginGroup, Token beforeEndGroup) {
     Token endGroup = beforeEndGroup.next;
     if (!optional('>', endGroup)) {
       endGroup = splitGtGt(endGroup);
+      endGroup.next.setNext(endGroup.next.next);
     }
-    beginGroup.endGroup = endGroup;
     beforeEndGroup.setNext(endGroup);
     return endGroup;
   }
@@ -644,16 +651,15 @@
 
 class ComplexTypeParamOrArgInfo extends TypeParamOrArgInfo {
   /// The first token in the type var.
-  final BeginToken start;
+  final Token start;
 
   /// If [inDeclaration] is `true`, then this will more aggressively recover
   /// given unbalanced `<` `>` and invalid parameters or arguments.
   final bool inDeclaration;
 
-  /// The last token in the group (typically `>`).
-  /// If a `>>` has not yet been split, then this field will be
-  /// `>>` for the outer group and the token before `>>` for the inner group.
-  Token end;
+  /// The token before the end group token (e.g. `>`, `>>`, `>=`, or `>>=`)
+  /// or after which a synthetic end group token should be inserted.
+  Token beforeEnd;
 
   ComplexTypeParamOrArgInfo(Token token, this.inDeclaration)
       : assert(optional('<', token.next)),
@@ -663,29 +669,25 @@
   /// Parse the tokens and return the receiver or [noTypeParamOrArg] if there
   /// are no type parameters or arguments. This does not modify the token
   /// stream.
-  ///
-  /// If this group is enclosed and the outer group ends with `>>`, then
-  /// [endGroup] is set to either `>>` if the token has not been split
-  /// or the first `>` if the `>>` token has been split.
-  TypeParamOrArgInfo compute(Token endGroup) {
-    Token innerEndGroup;
-    if (start.endGroup != null && optional('>>', start.endGroup)) {
-      innerEndGroup = start.endGroup;
-    }
-
+  TypeParamOrArgInfo compute() {
     Token token;
     Token next = start;
     while (true) {
-      TypeInfo typeInfo = computeType(next, true, inDeclaration, innerEndGroup);
+      TypeInfo typeInfo = computeType(next, true, inDeclaration);
       if (typeInfo == noType) {
         while (typeInfo == noType && optional('@', next.next)) {
           next = skipMetadata(next);
-          typeInfo = computeType(next, true, inDeclaration, innerEndGroup);
+          typeInfo = computeType(next, true, inDeclaration);
         }
-        if (typeInfo == noType && !optional(',', next.next)) {
-          token = next;
-          next = token.next;
-          break;
+        if (typeInfo == noType) {
+          if (next == start && !inDeclaration && !isCloser(next.next)) {
+            return noTypeParamOrArg;
+          }
+          if (!optional(',', next.next)) {
+            token = next;
+            next = token.next;
+            break;
+          }
         }
         assert(typeInfo != noType || optional(',', next.next));
         // Fall through to process type (if any) and consume `,`
@@ -693,33 +695,41 @@
       token = typeInfo.skipType(next);
       next = token.next;
       if (optional('extends', next) || optional('super', next)) {
-        token = computeType(next, true, inDeclaration, innerEndGroup)
-            .skipType(next);
+        token = computeType(next, true, inDeclaration).skipType(next);
         next = token.next;
       }
       if (!optional(',', next)) {
+        if (isCloser(next)) {
+          beforeEnd = token;
+          return this;
+        }
+        if (!inDeclaration) {
+          return noTypeParamOrArg;
+        }
+
+        // Recovery
         if (!looksLikeTypeParamOrArg(inDeclaration, next)) {
           break;
         }
-        // Recovery: Missing comma. Continue looping
+        // Looks like missing comma. Continue looping.
+        next = token;
       }
     }
 
-    if (next == start.endGroup) {
-      end = next;
-    } else if (next == endGroup) {
-      assert(start.endGroup == null);
-      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) {
-      end = start.endGroup;
-      if (end == null) {
-        // Recovery: Unbalanced `<`
-        end = token;
+    // Recovery
+    beforeEnd = token;
+    if (!isCloser(next)) {
+      if (optional('(', next)) {
+        token = next.endGroup;
+        next = token.next;
       }
-    } else {
-      return noTypeParamOrArg;
+      if (!isCloser(next)) {
+        token = next;
+        next = token.next;
+      }
+      if (isCloser(next)) {
+        beforeEnd = token;
+      }
     }
     return this;
   }
@@ -728,18 +738,17 @@
   Token parseArguments(Token token, Parser parser) {
     assert(identical(token.next, start));
     Token next = start;
-    Token innerEndGroup = processBeginGroup(start, parser);
     parser.listener.beginTypeArguments(start);
     int count = 0;
     while (true) {
-      TypeInfo typeInfo = computeType(next, true, inDeclaration, innerEndGroup);
+      TypeInfo typeInfo = computeType(next, true, inDeclaration);
       if (typeInfo == noType) {
         // Recovery
         while (typeInfo == noType && optional('@', next.next)) {
           parser.reportRecoverableErrorWithToken(
               next.next, fasta.templateUnexpectedToken);
           next = skipMetadata(next);
-          typeInfo = computeType(next, true, inDeclaration, innerEndGroup);
+          typeInfo = computeType(next, true, inDeclaration);
         }
         // Fall through to process type (if any) and consume `,`
       }
@@ -747,22 +756,23 @@
       next = token.next;
       ++count;
       if (!optional(',', next)) {
-        if (!looksLikeTypeParamOrArg(inDeclaration, next)) {
+        if (parseCloser(token)) {
+          beforeEnd = token;
           break;
         }
 
-        // Recovery: missing comma
-        parser.reportRecoverableError(
-            next, fasta.templateExpectedButGot.withArguments(','));
-        next = parser.rewriter
-            .insertTokenAfter(
-                token, new SyntheticToken(TokenType.COMMA, next.charOffset))
-            .next;
+        // Recovery
+        if (!looksLikeTypeParamOrArg(inDeclaration, next)) {
+          parseUnexpectedEnd(token, parser);
+          break;
+        }
+        // Missing comma. Report error, insert comma, and continue looping.
+        next = parseMissingComma(token, parser);
       }
     }
-    end = processEndGroup(token, start, parser);
-    parser.listener.endTypeArguments(count, start, end);
-    return end;
+    Token endGroup = beforeEnd.next;
+    parser.listener.endTypeArguments(count, start, endGroup);
+    return endGroup;
   }
 
   @override
@@ -771,7 +781,6 @@
     Token next = start;
     Listener listener = parser.listener;
     listener.beginTypeVariables(start);
-    Token innerEndGroup = processBeginGroup(start, parser);
     int count = 0;
 
     Link<Token> typeStarts = const Link<Token>();
@@ -779,15 +788,18 @@
 
     while (true) {
       token = parser.parseMetadataStar(next);
-      token = parser.ensureIdentifier(
+      next = parser.ensureIdentifier(
           token, IdentifierContext.typeVariableDeclaration);
+      if (beforeEnd == token) {
+        beforeEnd = next;
+      }
+      token = next;
       listener.beginTypeVariable(token);
       typeStarts = typeStarts.prepend(token);
 
       next = token.next;
       if (optional('extends', next) || optional('super', next)) {
-        TypeInfo typeInfo =
-            computeType(next, true, inDeclaration, innerEndGroup);
+        TypeInfo typeInfo = computeType(next, true, inDeclaration);
         token = typeInfo.skipType(next);
         next = token.next;
         superTypeInfos = superTypeInfos.prepend(typeInfo);
@@ -797,17 +809,16 @@
 
       ++count;
       if (!optional(',', next)) {
-        if (!looksLikeTypeParamOrArg(inDeclaration, next)) {
+        if (isCloser(token)) {
           break;
         }
 
-        // Recovery: missing comma
-        parser.reportRecoverableError(
-            next, fasta.templateExpectedButGot.withArguments(','));
-        next = parser.rewriter
-            .insertTokenAfter(
-                token, new SyntheticToken(TokenType.COMMA, next.charOffset))
-            .next;
+        // Recovery
+        if (!looksLikeTypeParamOrArg(inDeclaration, next)) {
+          break;
+        }
+        // Missing comma. Report error, insert comma, and continue looping.
+        next = parseMissingComma(token, parser);
       }
     }
 
@@ -841,53 +852,92 @@
       superTypeInfos = superTypeInfos.tail;
     }
 
-    end = processEndGroup(token, start, parser);
-    listener.endTypeVariables(start, end);
-    return end;
+    if (parseCloser(token)) {
+      beforeEnd = token;
+    } else {
+      parseUnexpectedEnd(token, parser);
+    }
+    Token endGroup = beforeEnd.next;
+    listener.endTypeVariables(start, endGroup);
+    return endGroup;
+  }
+
+  Token parseMissingComma(Token token, Parser parser) {
+    Token next = token.next;
+    parser.reportRecoverableError(
+        next, fasta.templateExpectedButGot.withArguments(','));
+    return parser.rewriter.insertToken(
+        token, new SyntheticToken(TokenType.COMMA, next.charOffset));
+  }
+
+  void parseUnexpectedEnd(Token token, Parser parser) {
+    if (beforeEnd.isSynthetic && beforeEnd.charOffset == token.charOffset) {
+      // Ensure that beforeEnd is in the token stream
+      // as a nested type argument or parameter may have inserted
+      // a synthetic closer.
+      beforeEnd = token;
+    }
+    if (parseCloser(beforeEnd)) {
+      parser.reportRecoverableErrorWithToken(
+          token.next, fasta.templateUnexpectedToken);
+    } else {
+      // If token is synthetic, then an error has already been reported.
+      if (!token.isSynthetic) {
+        parser.reportRecoverableError(
+            token, fasta.templateExpectedAfterButGot.withArguments('>'));
+      }
+      Token next = beforeEnd.next;
+      Token endGroup = syntheticGt(next);
+      endGroup.setNext(next);
+      beforeEnd.setNext(endGroup);
+    }
   }
 
   @override
-  Token skip(Token token) => end;
+  Token skip(Token token) {
+    final next = beforeEnd.next;
+    final value = next.stringValue;
+    if (identical(value, '>')) {
+      return next;
+    } else if (identical(value, '>>')) {
+      return splitGtGt(next);
+    } else if (identical(value, '>=')) {
+      return splitGtEq(next);
+    } else if (identical(value, '>>=')) {
+      // TODO(danrubel): Add support for this
+    }
+    return syntheticGt(next);
+  }
 }
 
-Token processBeginGroup(BeginToken start, Parser parser) {
-  if (start.endGroup != null && optional('>>', start.endGroup)) {
-    return parser.rewriter.splitEndGroup(start);
-  }
-  return null;
+/// Return `true` if [token] is one of `>`, `>>`, `>=', or `>>=`.
+bool isCloser(Token token) {
+  final value = token.stringValue;
+  return identical(value, '>') ||
+      identical(value, '>>') ||
+      identical(value, '>=');
+  // TODO(danrubel): Add support for `>>=`.
 }
 
-Token processEndGroup(Token token, BeginToken start, Parser parser) {
-  Token next = token.next;
-  if (next == start.endGroup) {
-    return next;
-  } else if (optional('>', next) && !next.isSynthetic) {
-    // When `>>` is split, the inner group's endGroup updated here.
-    assert(start.endGroup == null);
-    start.endGroup = next;
-    return next;
+/// If [token] is one of `>`, `>>`, `>=', or `>>=`,
+/// then update the token stream and return `true`.
+bool parseCloser(Token beforeCloser) {
+  Token closer = beforeCloser.next;
+  String value = closer.stringValue;
+  if (identical(value, '>')) {
+    return true;
+  } else if (identical(value, '>>')) {
+    SimpleToken split = splitGtGt(closer);
+    split.next.setNext(closer.next);
+    beforeCloser.setNext(split);
+    return true;
+  } else if (identical(value, '>=')) {
+    Token split = splitGtEq(closer);
+    split.next.setNext(closer.next);
+    beforeCloser.setNext(split);
+    return true;
+  } else if (identical(value, '>>=')) {
+    // TODO(danrubel): Add support for parsing `>>=`
   }
-
-  // Recovery
-  if (start.endGroup != null) {
-    // Extraneous tokens between `<` and `>`.
-    parser.reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
-    return start.endGroup;
-  } else if (isOneOf(next, const ['>>', '>=', '>>='])) {
-    // Found single unbalanced `<`.
-    return parser.rewriter.splitEndGroup(start, next);
-  }
-  // Ensure that `>` is inserted after any newly inserted synthetic tokens.
-  while (next.isSynthetic && !next.isEof) {
-    token = next;
-    next = token.next;
-  }
-  // Unbalanced `<` `>`
-  parser.reportRecoverableError(
-      next, fasta.templateExpectedButGot.withArguments('>'));
-  start.endGroup = parser.rewriter
-      .insertTokenAfter(
-          token, new SyntheticToken(TokenType.GT, next.charOffset))
-      .next;
-  return start.endGroup;
+  return false;
 }
diff --git a/pkg/front_end/lib/src/fasta/parser/util.dart b/pkg/front_end/lib/src/fasta/parser/util.dart
index 527d4b4..f617749 100644
--- a/pkg/front_end/lib/src/fasta/parser/util.dart
+++ b/pkg/front_end/lib/src/fasta/parser/util.dart
@@ -136,42 +136,59 @@
 }
 
 /// Split `>=` into two separate tokens.
+/// Call [Token.setNext] to add the token to the stream.
 Token splitGtEq(Token token) {
   assert(optional('>=', token));
   return new SimpleToken(
       TokenType.GT, token.charOffset, token.precedingComments)
     ..setNext(new SimpleToken(TokenType.EQ, token.charOffset + 1)
-      ..setNext(token.next));
+      // Set next rather than calling Token.setNext
+      // so that the previous token is not set.
+      ..next = token.next);
 }
 
 /// Split `>>` into two separate tokens.
+/// Call [Token.setNext] to add the token to the stream.
 SimpleToken splitGtGt(Token token) {
   assert(optional('>>', token));
   return new SimpleToken(
       TokenType.GT, token.charOffset, token.precedingComments)
     ..setNext(new SimpleToken(TokenType.GT, token.charOffset + 1)
-      ..setNext(token.next));
+      // Set next rather than calling Token.setNext
+      // so that the previous token is not set.
+      ..next = token.next);
 }
 
 /// Split `>>=` into three separate tokens.
+/// Call [Token.setNext] to add the token to the stream.
 Token splitGtGtEq(Token token) {
   assert(optional('>>=', token));
   return new SimpleToken(
       TokenType.GT, token.charOffset, token.precedingComments)
     ..setNext(new SimpleToken(TokenType.GT, token.charOffset + 1)
       ..setNext(new SimpleToken(TokenType.EQ, token.charOffset + 2)
-        ..setNext(token.next)));
+        // Set next rather than calling Token.setNext
+        // so that the previous token is not set.
+        ..next = token.next));
 }
 
 /// Split `>>=` into two separate tokens... `>` followed by `>=`.
+/// Call [Token.setNext] to add the token to the stream.
 Token splitGtFromGtGtEq(Token token) {
   assert(optional('>>=', token));
   return new SimpleToken(
       TokenType.GT, token.charOffset, token.precedingComments)
     ..setNext(new SimpleToken(TokenType.GT_EQ, token.charOffset + 1)
-      ..setNext(token.next));
+      // Set next rather than calling Token.setNext
+      // so that the previous token is not set.
+      ..next = token.next);
 }
 
-Token syntheticGt(Token token) {
-  return new SyntheticToken(TokenType.GT, token.charOffset)..setNext(token);
+/// Return a synthetic `<` followed by [next].
+/// Call [Token.setNext] to add the token to the stream.
+Token syntheticGt(Token next) {
+  return new SyntheticToken(TokenType.GT, next.charOffset)
+    // Set next rather than calling Token.setNext
+    // so that the previous token is not set.
+    ..next = next;
 }
diff --git a/pkg/front_end/test/fasta/parser/type_info_test.dart b/pkg/front_end/test/fasta/parser/type_info_test.dart
index 9164339..1c8618a 100644
--- a/pkg/front_end/test/fasta/parser/type_info_test.dart
+++ b/pkg/front_end/test/fasta/parser/type_info_test.dart
@@ -18,8 +18,11 @@
     defineReflectiveTests(SimpleTypeInfoTest);
     defineReflectiveTests(SimpleTypeWith1ArgumentTest);
     defineReflectiveTests(TypeInfoTest);
-    defineReflectiveTests(TypeParamOrArgInfoTest);
     defineReflectiveTests(VoidTypeInfoTest);
+
+    defineReflectiveTests(NoTypeParamOrArgTest);
+    defineReflectiveTests(SimpleTypeParamOrArgTest);
+    defineReflectiveTests(TypeParamOrArgInfoTest);
   });
 }
 
@@ -32,6 +35,70 @@
     expect(noType.skipType(start), start);
   }
 
+  void test_compute() {
+    expectInfo(noType, '');
+    expectInfo(noType, ';');
+    expectInfo(noType, '( foo');
+    expectInfo(noType, '< foo');
+    expectInfo(noType, '= foo');
+    expectInfo(noType, '* foo');
+    expectInfo(noType, 'do foo');
+    expectInfo(noType, 'get foo');
+    expectInfo(noType, 'set foo');
+    expectInfo(noType, 'operator *');
+
+    expectInfo(noType, 'C', required: false);
+    expectInfo(noType, 'C;', required: false);
+    expectInfo(noType, 'C(', required: false);
+    expectInfo(noType, 'C<', required: false);
+    expectInfo(noType, 'C.', required: false);
+    expectInfo(noType, 'C=', required: false);
+    expectInfo(noType, 'C*', required: false);
+    expectInfo(noType, 'C do', required: false);
+
+    expectInfo(noType, 'C.a', required: false);
+    expectInfo(noType, 'C.a;', required: false);
+    expectInfo(noType, 'C.a(', required: false);
+    expectInfo(noType, 'C.a<', required: false);
+    expectInfo(noType, 'C.a=', required: false);
+    expectInfo(noType, 'C.a*', required: false);
+    expectInfo(noType, 'C.a do', required: false);
+
+    expectInfo(noType, 'C<T>', required: false);
+    expectInfo(noType, 'C<T>;', required: false);
+    expectInfo(noType, 'C<T>(', required: false);
+    expectInfo(noType, 'C<T> do', required: false);
+    expectInfo(noType, 'C<void>', required: false);
+
+    expectInfo(noType, 'C<T>= foo', required: false);
+    expectInfo(noType, 'C<T>= get', required: false);
+    expectInfo(noType, 'C<T>= set', required: false);
+    expectInfo(noType, 'C<T>= operator', required: false);
+    expectInfo(noType, 'C<T>= Function', required: false);
+
+    expectInfo(noType, 'C<T>> foo', required: false);
+    expectInfo(noType, 'C<T>> get', required: false);
+    expectInfo(noType, 'C<T>> set', required: false);
+    expectInfo(noType, 'C<T>> operator', required: false);
+    expectInfo(noType, 'C<T>> Function', required: false);
+
+    expectInfo(noType, 'C<T>>= foo', required: false);
+    expectInfo(noType, 'C<T>>= get', required: false);
+    expectInfo(noType, 'C<T>>= set', required: false);
+    expectInfo(noType, 'C<T>>= operator', required: false);
+    expectInfo(noType, 'C<T>>= Function', required: false);
+
+    expectInfo(noType, 'C<S,T>', required: false);
+    expectInfo(noType, 'C<S<T>>', required: false);
+    expectInfo(noType, 'C.a<T>', required: false);
+    expectInfo(noType, 'C<S,T>=', required: false);
+    expectInfo(noType, 'C<S<T>>=', required: false);
+    expectInfo(noType, 'C.a<T>=', required: false);
+
+    expectInfo(noType, 'Function(int x)', required: false);
+    expectInfo(noType, 'Function<T>(int x)', required: false);
+  }
+
   void test_ensureTypeNotVoid() {
     final Token start = scanString('before ;').tokens;
     final TypeInfoListener listener = new TypeInfoListener();
@@ -88,6 +155,29 @@
     expect(voidType.couldBeExpression, isFalse);
   }
 
+  void test_compute() {
+    expectInfo(voidType, 'void');
+    expectInfo(voidType, 'void;');
+    expectInfo(voidType, 'void(');
+    expectInfo(voidType, 'void<');
+    expectInfo(voidType, 'void=');
+    expectInfo(voidType, 'void*');
+    expectInfo(voidType, 'void<T>');
+    expectInfo(voidType, 'void do');
+    expectInfo(voidType, 'void foo');
+    expectInfo(voidType, 'void get');
+    expectInfo(voidType, 'void set');
+    expectInfo(voidType, 'void operator');
+    expectInfo(voidType, 'void Function');
+
+    expectInfo(voidType, 'void Function()', required: false);
+    expectInfo(voidType, 'void Function<T>()', required: false);
+    expectInfo(voidType, 'void Function(int)', required: false);
+    expectInfo(voidType, 'void Function<T>(int)', required: false);
+    expectInfo(voidType, 'void Function(int x)', required: false);
+    expectInfo(voidType, 'void Function<T>(int x)', required: false);
+  }
+
   void test_ensureTypeNotVoid() {
     final Token start = scanString('before void ;').tokens;
     final TypeInfoListener listener = new TypeInfoListener();
@@ -135,6 +225,22 @@
 
 @reflectiveTest
 class PrefixedTypeInfoTest {
+  void test_compute() {
+    expectInfo(prefixedType, 'C.a', required: true);
+    expectInfo(prefixedType, 'C.a;', required: true);
+    expectInfo(prefixedType, 'C.a(', required: true);
+    expectInfo(prefixedType, 'C.a<', required: true);
+    expectInfo(prefixedType, 'C.a=', required: true);
+    expectInfo(prefixedType, 'C.a*', required: true);
+    expectInfo(prefixedType, 'C.a do', required: true);
+
+    expectInfo(prefixedType, 'C.a foo');
+    expectInfo(prefixedType, 'C.a get');
+    expectInfo(prefixedType, 'C.a set');
+    expectInfo(prefixedType, 'C.a operator');
+    expectInfo(prefixedType, 'C.a Function');
+  }
+
   void test_prefixedTypeInfo() {
     final Token start = scanString('before C.a ;').tokens;
     final Token expectedEnd = start.next.next.next;
@@ -171,6 +277,32 @@
 
 @reflectiveTest
 class SimpleTypeInfoTest {
+  void test_compute() {
+    expectInfo(simpleType, 'C', required: true);
+    expectInfo(simpleType, 'C;', required: true);
+    expectInfo(simpleType, 'C(', required: true);
+    expectInfo(simpleType, 'C<', required: true);
+    expectComplexInfo('C.',
+        required: true, expectedErrors: [error(codeExpectedType, 2, 0)]);
+    expectInfo(simpleType, 'C=', required: true);
+    expectInfo(simpleType, 'C*', required: true);
+    expectInfo(simpleType, 'C do', required: true);
+
+    expectInfo(simpleType, 'C foo');
+    expectInfo(simpleType, 'C get');
+    expectInfo(simpleType, 'C set');
+    expectInfo(simpleType, 'C operator');
+    expectInfo(simpleType, 'C this');
+    expectInfo(simpleType, 'C Function');
+
+    expectInfo(simpleType, 'C Function()', required: false);
+    expectInfo(simpleType, 'C Function<T>()', required: false);
+    expectInfo(simpleType, 'C Function(int)', required: false);
+    expectInfo(simpleType, 'C Function<T>(int)', required: false);
+    expectInfo(simpleType, 'C Function(int x)', required: false);
+    expectInfo(simpleType, 'C Function<T>(int x)', required: false);
+  }
+
   void test_simpleTypeInfo() {
     final Token start = scanString('before C ;').tokens;
     final Token expectedEnd = start.next;
@@ -205,6 +337,45 @@
 
 @reflectiveTest
 class SimpleTypeWith1ArgumentTest {
+  void test_compute_gt() {
+    expectInfo(simpleTypeWith1Argument, 'C<T>', required: true);
+    expectInfo(simpleTypeWith1Argument, 'C<T>;', required: true);
+    expectInfo(simpleTypeWith1Argument, 'C<T>(', required: true);
+    expectInfo(simpleTypeWith1Argument, 'C<T> do', required: true);
+
+    expectInfo(simpleTypeWith1Argument, 'C<T> foo');
+    expectInfo(simpleTypeWith1Argument, 'C<T> get');
+    expectInfo(simpleTypeWith1Argument, 'C<T> set');
+    expectInfo(simpleTypeWith1Argument, 'C<T> operator');
+    expectInfo(simpleTypeWith1Argument, 'C<T> Function');
+  }
+
+  void test_compute_gt_eq() {
+    expectInfo(simpleTypeWith1ArgumentGtEq, 'C<T>=', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtEq, 'C<T>=;', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtEq, 'C<T>=(', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtEq, 'C<T>= do', required: true);
+
+    expectInfo(simpleTypeWith1ArgumentGtEq, 'C<T>= foo', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtEq, 'C<T>= get', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtEq, 'C<T>= set', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtEq, 'C<T>= operator', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtEq, 'C<T>= Function', required: true);
+  }
+
+  void test_compute_gt_gt() {
+    expectInfo(simpleTypeWith1ArgumentGtGt, 'C<T>>', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtGt, 'C<T>>;', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtGt, 'C<T>>(', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtGt, 'C<T>> do', required: true);
+
+    expectInfo(simpleTypeWith1ArgumentGtGt, 'C<T>> foo', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtGt, 'C<T>> get', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtGt, 'C<T>> set', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtGt, 'C<T>> operator', required: true);
+    expectInfo(simpleTypeWith1ArgumentGtGt, 'C<T>> Function', required: true);
+  }
+
   void test_gt() {
     final Token start = scanString('before C<T> ;').tokens;
     final Token expectedEnd = start.next.next.next.next;
@@ -293,11 +464,13 @@
 
   void test_gt_gt() {
     final Token start = scanString('before C<T>> ;').tokens;
-    final Token t = start.next.next.next;
-    final Token semicolon = t.next.next;
+    final Token semicolon = start.next.next.next.next.next;
     expect(semicolon.lexeme, ';');
 
-    expect(simpleTypeWith1ArgumentGtGt.skipType(start), t);
+    Token skip = simpleTypeWith1ArgumentGtGt.skipType(start);
+    expect(skip.lexeme, '>');
+    expect(skip.next.lexeme, '>');
+    expect(skip.next.next, semicolon);
     expect(simpleTypeWith1ArgumentGtGt.couldBeExpression, isFalse);
 
     TypeInfoListener listener;
@@ -338,17 +511,6 @@
 @reflectiveTest
 class TypeInfoTest {
   void test_computeType_basic() {
-    expectInfo(noType, '');
-    expectInfo(noType, ';');
-    expectInfo(noType, '( foo');
-    expectInfo(noType, '< foo');
-    expectInfo(noType, '= foo');
-    expectInfo(noType, '* foo');
-    expectInfo(noType, 'do foo');
-    expectInfo(noType, 'get foo');
-    expectInfo(noType, 'set foo');
-    expectInfo(noType, 'operator *');
-
     expectInfo(noType, '.', required: false);
     expectComplexInfo('.', required: true, expectedErrors: [
       error(codeExpectedType, 0, 1),
@@ -449,9 +611,6 @@
       'endFunctionType Function',
     ]);
 
-    expectInfo(noType, 'Function(int x)', required: false);
-    expectInfo(noType, 'Function<T>(int x)', required: false);
-
     expectComplexInfo('Function(int x)', required: true);
     expectComplexInfo('Function<T>(int x)', required: true);
 
@@ -464,42 +623,7 @@
         expectedAfter: 'm');
   }
 
-  void test_computeType_identifier() {
-    expectInfo(noType, 'C', required: false);
-    expectInfo(noType, 'C;', required: false);
-    expectInfo(noType, 'C(', required: false);
-    expectInfo(noType, 'C<', required: false);
-    expectInfo(noType, 'C.', required: false);
-    expectInfo(noType, 'C=', required: false);
-    expectInfo(noType, 'C*', required: false);
-    expectInfo(noType, 'C do', required: false);
-
-    expectInfo(simpleType, 'C', required: true);
-    expectInfo(simpleType, 'C;', required: true);
-    expectInfo(simpleType, 'C(', required: true);
-    expectInfo(simpleType, 'C<', required: true);
-    expectComplexInfo('C.',
-        required: true, expectedErrors: [error(codeExpectedType, 2, 0)]);
-    expectInfo(simpleType, 'C=', required: true);
-    expectInfo(simpleType, 'C*', required: true);
-    expectInfo(simpleType, 'C do', required: true);
-
-    expectInfo(simpleType, 'C foo');
-    expectInfo(simpleType, 'C get');
-    expectInfo(simpleType, 'C set');
-    expectInfo(simpleType, 'C operator');
-    expectInfo(simpleType, 'C this');
-    expectInfo(simpleType, 'C Function');
-  }
-
   void test_computeType_identifierComplex() {
-    expectInfo(simpleType, 'C Function()', required: false);
-    expectInfo(simpleType, 'C Function<T>()', required: false);
-    expectInfo(simpleType, 'C Function(int)', required: false);
-    expectInfo(simpleType, 'C Function<T>(int)', required: false);
-    expectInfo(simpleType, 'C Function(int x)', required: false);
-    expectInfo(simpleType, 'C Function<T>(int x)', required: false);
-
     expectComplexInfo('C Function()', required: true);
     expectComplexInfo('C Function<T>()', required: true);
     expectComplexInfo('C Function(int)', required: true);
@@ -525,16 +649,6 @@
   }
 
   void test_computeType_identifierTypeArg() {
-    expectInfo(noType, 'C<T>', required: false);
-    expectInfo(noType, 'C<T>;', required: false);
-    expectInfo(noType, 'C<T>(', required: false);
-    expectInfo(noType, 'C<T> do', required: false);
-    expectInfo(noType, 'C<void>', required: false);
-
-    expectInfo(simpleTypeWith1Argument, 'C<T>', required: true);
-    expectInfo(simpleTypeWith1Argument, 'C<T>;', required: true);
-    expectInfo(simpleTypeWith1Argument, 'C<T>(', required: true);
-    expectInfo(simpleTypeWith1Argument, 'C<T> do', required: true);
     expectComplexInfo('C<void>', required: true, expectedCalls: [
       'handleIdentifier C typeReference',
       'beginTypeArguments <',
@@ -542,19 +656,9 @@
       'endTypeArguments 1 < >',
       'handleType C ',
     ]);
-
-    expectInfo(simpleTypeWith1Argument, 'C<T> foo');
-    expectInfo(simpleTypeWith1Argument, 'C<T> get');
-    expectInfo(simpleTypeWith1Argument, 'C<T> set');
-    expectInfo(simpleTypeWith1Argument, 'C<T> operator');
-    expectInfo(simpleTypeWith1Argument, 'C<T> Function');
   }
 
   void test_computeType_identifierTypeArgComplex() {
-    expectInfo(noType, 'C<S,T>', required: false);
-    expectInfo(noType, 'C<S<T>>', required: false);
-    expectInfo(noType, 'C.a<T>', required: false);
-
     expectComplexInfo('C<S,T>', required: true, expectedCalls: [
       'handleIdentifier C typeReference',
       'beginTypeArguments <',
@@ -704,30 +808,6 @@
     expectNestedInfo(simpleTypeWith1Argument, '<T<S>,>');
   }
 
-  void test_computeType_prefixed() {
-    expectInfo(noType, 'C.a', required: false);
-    expectInfo(noType, 'C.a;', required: false);
-    expectInfo(noType, 'C.a(', required: false);
-    expectInfo(noType, 'C.a<', required: false);
-    expectInfo(noType, 'C.a=', required: false);
-    expectInfo(noType, 'C.a*', required: false);
-    expectInfo(noType, 'C.a do', required: false);
-
-    expectInfo(prefixedType, 'C.a', required: true);
-    expectInfo(prefixedType, 'C.a;', required: true);
-    expectInfo(prefixedType, 'C.a(', required: true);
-    expectInfo(prefixedType, 'C.a<', required: true);
-    expectInfo(prefixedType, 'C.a=', required: true);
-    expectInfo(prefixedType, 'C.a*', required: true);
-    expectInfo(prefixedType, 'C.a do', required: true);
-
-    expectInfo(prefixedType, 'C.a foo');
-    expectInfo(prefixedType, 'C.a get');
-    expectInfo(prefixedType, 'C.a set');
-    expectInfo(prefixedType, 'C.a operator');
-    expectInfo(prefixedType, 'C.a Function');
-  }
-
   void test_computeType_prefixedComplex() {
     expectComplexInfo('a < b, c > d', expectedAfter: 'd');
     expectComplexInfo('a < b, c > d', expectedAfter: 'd');
@@ -852,19 +932,6 @@
   }
 
   void test_computeType_void() {
-    expectInfo(voidType, 'void');
-    expectInfo(voidType, 'void;');
-    expectInfo(voidType, 'void(');
-    expectInfo(voidType, 'void<');
-    expectInfo(voidType, 'void=');
-    expectInfo(voidType, 'void*');
-    expectInfo(voidType, 'void<T>');
-    expectInfo(voidType, 'void do');
-    expectInfo(voidType, 'void foo');
-    expectInfo(voidType, 'void get');
-    expectInfo(voidType, 'void set');
-    expectInfo(voidType, 'void operator');
-    expectInfo(voidType, 'void Function');
     expectComplexInfo('void Function(', // Scanner inserts synthetic ')'.
         required: true,
         expectedCalls: [
@@ -878,7 +945,6 @@
   }
 
   void test_computeType_voidComplex() {
-    expectInfo(voidType, 'void Function()', required: false);
     expectComplexInfo('void Function()', required: true, expectedCalls: [
       'handleNoTypeVariables (',
       'beginFunctionType void',
@@ -888,12 +954,6 @@
       'endFunctionType Function',
     ]);
 
-    expectInfo(voidType, 'void Function<T>()', required: false);
-    expectInfo(voidType, 'void Function(int)', required: false);
-    expectInfo(voidType, 'void Function<T>(int)', required: false);
-    expectInfo(voidType, 'void Function(int x)', required: false);
-    expectInfo(voidType, 'void Function<T>(int x)', required: false);
-
     expectComplexInfo('void Function<T>()', required: true);
     expectComplexInfo('void Function(int)', required: true);
     expectComplexInfo('void Function<T>(int)', required: true);
@@ -908,65 +968,129 @@
 }
 
 @reflectiveTest
-class TypeParamOrArgInfoTest {
-  void test_noTypeParamOrArg() {
-    final Token start = scanString('before after').tokens;
+class NoTypeParamOrArgTest {
+  void test_basic() {
+    expect(noTypeParamOrArg.isSimpleTypeArgument, isFalse);
 
+    final Token start = scanString('before after').tokens;
     expect(noTypeParamOrArg.skip(start), start);
+    validateTokens(start);
   }
 
-  void test_noTypeParamOrArg_parseArguments() {
+  void test_compute() {
+    expectTypeParamOrArg(noTypeParamOrArg, '');
+    expectTypeParamOrArg(noTypeParamOrArg, 'a');
+    expectTypeParamOrArg(noTypeParamOrArg, 'a b');
+    expectTypeParamOrArg(noTypeParamOrArg, '<');
+    expectTypeParamOrArg(noTypeParamOrArg, '< b');
+    expectTypeParamOrArg(noTypeParamOrArg, '< 3 >');
+    expectTypeParamOrArg(noTypeParamOrArg, '< (');
+    expectTypeParamOrArg(noTypeParamOrArg, '< (', inDeclaration: true);
+  }
+
+  void test_parseArguments() {
     final Token start = scanString('before after').tokens;
     final TypeInfoListener listener = new TypeInfoListener();
 
     expect(noTypeParamOrArg.parseArguments(start, new Parser(listener)), start);
+    validateTokens(start);
     expect(listener.calls, ['handleNoTypeArguments after']);
     expect(listener.errors, isNull);
   }
 
-  void test_noTypeParamOrArg_parseVariables() {
+  void test_parseVariables() {
     final Token start = scanString('before after').tokens;
     final TypeInfoListener listener = new TypeInfoListener();
 
     expect(noTypeParamOrArg.parseVariables(start, new Parser(listener)), start);
+    validateTokens(start);
     expect(listener.calls, ['handleNoTypeVariables after']);
     expect(listener.errors, isNull);
   }
+}
 
-  void test_simple_skip() {
+@reflectiveTest
+class SimpleTypeParamOrArgTest {
+  void test_basic_gt() {
+    expect(simpleTypeArgument1.isSimpleTypeArgument, isTrue);
+    expect(simpleTypeArgument1.typeInfo, simpleTypeWith1Argument);
+
     final Token start = scanString('before <T> after').tokens;
     final Token gt = start.next.next.next;
     expect(gt.lexeme, '>');
 
-    expect(simpleTypeArgument1.skip(start), gt);
+    Token skip = simpleTypeArgument1.skip(start);
+    validateTokens(start);
+    expect(skip, gt);
   }
 
-  void test_simple_skip2() {
-    final Token start = scanString('before <S<T>> after').tokens.next.next;
-    Token t = start.next.next;
-    expect(t.next.lexeme, '>>');
+  void test_basic_gt_eq() {
+    expect(simpleTypeArgument1GtEq.isSimpleTypeArgument, isTrue);
+    expect(simpleTypeArgument1GtEq.typeInfo, simpleTypeWith1ArgumentGtEq);
 
-    expect(simpleTypeArgument1GtGt.skip(start), t);
-  }
-
-  void test_simple_skip3() {
     final Token start = scanString('before <T>= after').tokens;
     Token t = start.next.next;
     expect(t.next.lexeme, '>=');
 
     Token skip = simpleTypeArgument1GtEq.skip(start);
+    validateTokens(start);
     expect(skip.lexeme, '>');
     expect(skip.next.lexeme, '=');
     expect(skip.next.next, t.next.next);
   }
 
-  void test_simple_parseArguments() {
-    final Token start = scanString('before <T> after').tokens;
-    final Token gt = start.next.next.next;
-    expect(gt.lexeme, '>');
+  void test_basic_gt_gt() {
+    expect(simpleTypeArgument1GtGt.isSimpleTypeArgument, isTrue);
+    expect(simpleTypeArgument1GtGt.typeInfo, simpleTypeWith1ArgumentGtGt);
+
+    final Token start = scanString('before <S<T>> after').tokens.next.next;
+    var gtgt = start.next.next.next;
+    expect(gtgt.lexeme, '>>');
+    Token after = gtgt.next;
+    expect(after.lexeme, 'after');
+
+    Token skip = simpleTypeArgument1GtGt.skip(start);
+    validateTokens(start);
+    expect(skip.lexeme, '>');
+    expect(skip.next.lexeme, '>');
+    expect(skip.next.next, after);
+  }
+
+  void test_compute_gt() {
+    expectTypeParamOrArg(simpleTypeArgument1, '<T>');
+  }
+
+  void test_compute_gt_eq() {
+    expectTypeParamOrArg(simpleTypeArgument1GtEq, '<T>=');
+  }
+
+  void test_compute_gt_gt() {
+    String source = '<C<T>>';
+    Token start = scan(source).next.next;
+    expect(start.lexeme, 'C');
+    Token gtgt = start.next.next.next;
+    expect(gtgt.lexeme, '>>');
+
+    expect(computeTypeParamOrArg(start, false), simpleTypeArgument1GtGt);
+    validateTokens(start);
+  }
+
+  void testParseArguments(TypeParamOrArgInfo typeArg, String source,
+      [String next]) {
+    final Token start = scanString('before $source after').tokens;
+    final Token after = start.next.next.next.next;
+    expect(after.lexeme, 'after');
     final TypeInfoListener listener = new TypeInfoListener();
 
-    expect(simpleTypeArgument1.parseArguments(start, new Parser(listener)), gt);
+    var token = typeArg.parseArguments(start, new Parser(listener));
+    validateTokens(start);
+    expect(token.lexeme, '>');
+    token = token.next;
+    if (next != null) {
+      expect(token.lexeme, next);
+      token = token.next;
+    }
+    expect(token, after);
     expect(listener.calls, [
       'beginTypeArguments <',
       'handleIdentifier T typeReference',
@@ -977,13 +1101,34 @@
     expect(listener.errors, isNull);
   }
 
-  void test_simple_parseVariables() {
-    final Token start = scanString('before <T> after').tokens;
-    final Token gt = start.next.next.next;
-    expect(gt.lexeme, '>');
+  void test_parseArguments_gt() {
+    testParseArguments(simpleTypeArgument1, '<T>');
+  }
+
+  void test_parseArguments_gt_eq() {
+    testParseArguments(simpleTypeArgument1GtEq, '<T>=', '=');
+  }
+
+  void test_parseArguments_gt_gt() {
+    testParseArguments(simpleTypeArgument1GtGt, '<T>>', '>');
+  }
+
+  void testParseVariables(TypeParamOrArgInfo typeParam, String source,
+      [String next]) {
+    final Token start = scanString('before $source after').tokens;
+    final Token after = start.next.next.next.next;
+    expect(after.lexeme, 'after');
     final TypeInfoListener listener = new TypeInfoListener();
 
-    expect(simpleTypeArgument1.parseVariables(start, new Parser(listener)), gt);
+    Token token = typeParam.parseVariables(start, new Parser(listener));
+    validateTokens(start);
+    expect(token.lexeme, '>');
+    token = token.next;
+    if (next != null) {
+      expect(token.lexeme, next);
+      token = token.next;
+    }
+    expect(token, after);
     expect(listener.calls, [
       'beginTypeVariables <',
       'beginMetadataStar T',
@@ -998,34 +1143,21 @@
     expect(listener.errors, isNull);
   }
 
-  void test_computeTypeParamOrArg_basic() {
-    expectTypeParamOrArg(noTypeParamOrArg, '');
-    expectTypeParamOrArg(noTypeParamOrArg, 'a');
-    expectTypeParamOrArg(noTypeParamOrArg, 'a b');
-    expectTypeParamOrArg(noTypeParamOrArg, '<');
-    expectTypeParamOrArg(noTypeParamOrArg, '< b');
-    expectTypeParamOrArg(noTypeParamOrArg, '< 3 >');
+  void test_parseVariables_gt() {
+    testParseVariables(simpleTypeArgument1, '<T>');
   }
 
-  void test_computeTypeParamOrArg_simple() {
-    expectTypeParamOrArg(simpleTypeArgument1, '<T>');
+  void test_parseVariables_gt_eq() {
+    testParseVariables(simpleTypeArgument1GtEq, '<T>=', '=');
   }
 
-  void test_computeTypeParamOrArg_simple2() {
-    expectTypeParamOrArg(simpleTypeArgument1GtEq, '<T>=', inDeclaration: true);
+  void test_parseVariables_gt_gt() {
+    testParseVariables(simpleTypeArgument1GtGt, '<T>>', '>');
   }
+}
 
-  void test_computeTypeParamOrArg_simple_nested() {
-    String source = '<C<T>>';
-    Token start = scan(source).next.next;
-    expect(start.lexeme, 'C');
-    Token gtgt = start.next.next.next;
-    expect(gtgt.lexeme, '>>');
-
-    TypeParamOrArgInfo typeVarInfo = computeTypeParamOrArg(start, false, gtgt);
-    expect(typeVarInfo, simpleTypeArgument1GtGt, reason: source);
-  }
-
+@reflectiveTest
+class TypeParamOrArgInfoTest {
   void test_computeTypeArg_complex() {
     expectComplexTypeArg('<S,T>', expectedCalls: [
       'beginTypeArguments <',
@@ -1159,10 +1291,24 @@
     expectComplexTypeArg('<S T>',
         inDeclaration: true, expectedErrors: [error(codeExpectedButGot, 3, 1)]);
     expectComplexTypeArg('<S',
-        inDeclaration: true, expectedErrors: [error(codeExpectedButGot, 2, 0)]);
+        inDeclaration: true,
+        expectedErrors: [error(codeExpectedAfterButGot, 1, 1)]);
     expectComplexTypeArg('<@Foo S', inDeclaration: true, expectedErrors: [
       error(codeUnexpectedToken, 1, 1),
-      error(codeExpectedButGot, 7, 0)
+      error(codeExpectedAfterButGot, 6, 1)
+    ]);
+    expectComplexTypeArg('<S<T', inDeclaration: true, expectedErrors: [
+      error(codeExpectedAfterButGot, 3, 1)
+    ], expectedCalls: [
+      'beginTypeArguments <',
+      'handleIdentifier S typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier T typeReference',
+      'handleNoTypeArguments ',
+      'handleType T ',
+      'endTypeArguments 1 < >',
+      'handleType S ',
+      'endTypeArguments 1 < >'
     ]);
   }
 
@@ -1369,10 +1515,36 @@
       error(codeExpectedButGot, 3, 1),
     ]);
     expectComplexTypeParam('<S', inDeclaration: true, expectedErrors: [
-      error(codeExpectedButGot, 2, 0),
+      error(codeExpectedAfterButGot, 1, 1),
     ]);
     expectComplexTypeParam('<@Foo S',
-        inDeclaration: true, expectedErrors: [error(codeExpectedButGot, 7, 0)]);
+        inDeclaration: true,
+        expectedErrors: [error(codeExpectedAfterButGot, 6, 1)]);
+    expectComplexTypeParam('<@Foo }',
+        inDeclaration: true,
+        expectedAfter: '}',
+        expectedErrors: [error(codeExpectedIdentifier, 6, 1)]);
+    expectComplexTypeParam('<S extends List<T fieldName;',
+        inDeclaration: true,
+        expectedErrors: [error(codeExpectedAfterButGot, 16, 1)],
+        expectedAfter: 'fieldName',
+        expectedCalls: [
+          'beginTypeVariables <',
+          'beginMetadataStar S',
+          'endMetadataStar 0',
+          'handleIdentifier S typeVariableDeclaration',
+          'beginTypeVariable S',
+          'handleTypeVariablesDefined > 1',
+          'handleIdentifier List typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier T typeReference',
+          'handleNoTypeArguments fieldName',
+          'handleType T fieldName',
+          'endTypeArguments 1 < >',
+          'handleType List fieldName',
+          'endTypeVariable fieldName 0 extends',
+          'endTypeVariables < >',
+        ]);
   }
 
   void test_computeTypeParam_31846() {
@@ -1447,7 +1619,7 @@
       'endMetadataStar 0',
       'handleIdentifier T typeVariableDeclaration',
       'beginTypeVariable T',
-      'handleTypeVariablesDefined >> 1',
+      'handleTypeVariablesDefined > 1',
       'handleIdentifier List typeReference',
       'beginTypeArguments <',
       'handleIdentifier List typeReference',
@@ -1494,14 +1666,7 @@
 void expectNestedInfo(expectedInfo, String source) {
   expect(source.startsWith('<'), isTrue);
   Token start = scan(source).next;
-  Token innerEndGroup = start;
-  while (!innerEndGroup.next.isEof) {
-    innerEndGroup = innerEndGroup.next;
-  }
-  if (!optional('>>', innerEndGroup)) {
-    innerEndGroup = null;
-  }
-  compute(expectedInfo, source, start, true, innerEndGroup: innerEndGroup);
+  compute(expectedInfo, source, start, true);
 }
 
 void expectNestedComplexInfo(String source) {
@@ -1509,10 +1674,9 @@
 }
 
 TypeInfo compute(expectedInfo, String source, Token start, bool required,
-    {bool inDeclaration = false, Token innerEndGroup}) {
+    {bool inDeclaration = false}) {
   int expectedGtGtAndNullEndCount = countGtGtAndNullEnd(start);
-  TypeInfo typeInfo =
-      computeType(start, required, inDeclaration, innerEndGroup);
+  TypeInfo typeInfo = computeType(start, required, inDeclaration);
   expect(typeInfo, expectedInfo, reason: source);
   expect(countGtGtAndNullEnd(start), expectedGtGtAndNullEndCount,
       reason: 'computeType should not modify the token stream');
@@ -1563,6 +1727,7 @@
 
   expect(typeVarInfo.start, start.next, reason: source);
   expectEnd(expectedAfter, typeVarInfo.skip(start));
+  validateTokens(start);
   expect(countGtGtAndNullEnd(start), expectedGtGtAndNullEndCount,
       reason: 'TypeParamOrArgInfo.skipType'
           ' should not modify the token stream');
@@ -1570,6 +1735,7 @@
   TypeInfoListener listener = new TypeInfoListener();
   Parser parser = new Parser(listener);
   Token actualEnd = typeVarInfo.parseArguments(start, parser);
+  validateTokens(start);
   expectEnd(expectedAfter, actualEnd);
 
   if (expectedCalls != null) {
@@ -1593,6 +1759,7 @@
 
   expect(typeVarInfo.start, start.next, reason: source);
   expectEnd(expectedAfter, typeVarInfo.skip(start));
+  validateTokens(start);
   expect(countGtGtAndNullEnd(start), expectedGtGtAndNullEndCount,
       reason: 'TypeParamOrArgInfo.skipType'
           ' should not modify the token stream');
@@ -1600,6 +1767,7 @@
   TypeInfoListener listener = new TypeInfoListener(metadataAllowed: true);
   Parser parser = new Parser(listener);
   Token actualEnd = typeVarInfo.parseVariables(start, parser);
+  validateTokens(start);
   expectEnd(expectedAfter, actualEnd);
 
   if (expectedCalls != null) {
@@ -1621,6 +1789,7 @@
     expectedInfo, String source, Token start, bool inDeclaration) {
   int expectedGtGtAndNullEndCount = countGtGtAndNullEnd(start);
   TypeParamOrArgInfo typeVarInfo = computeTypeParamOrArg(start, inDeclaration);
+  validateTokens(start);
   expect(typeVarInfo, expectedInfo, reason: source);
   expect(countGtGtAndNullEnd(start), expectedGtGtAndNullEndCount,
       reason: 'computeTypeParamOrArg should not modify the token stream');
@@ -1656,6 +1825,24 @@
   return count;
 }
 
+void validateTokens(Token token) {
+  int count = 0;
+  if (token.isEof && !token.next.isEof) {
+    token = token.next;
+  }
+  while (!token.isEof) {
+    Token next = token.next;
+    expect(token.charOffset, lessThanOrEqualTo(next.charOffset));
+    expect(next.previous, token);
+    if (next is SyntheticToken) {
+      expect(next.beforeSynthetic, token);
+    }
+    expect(count, lessThanOrEqualTo(10000));
+    token = next;
+    ++count;
+  }
+}
+
 class TypeInfoListener implements Listener {
   final bool metadataAllowed;
   List<String> calls = <String>[];
diff --git a/tests/language_2/language_2_analyzer.status b/tests/language_2/language_2_analyzer.status
index b496893..6d1d6c5 100644
--- a/tests/language_2/language_2_analyzer.status
+++ b/tests/language_2/language_2_analyzer.status
@@ -177,6 +177,7 @@
 library_ambiguous_test/00: Crash # Error recovery in method body (ambiguous imported name)
 library_test/01: Crash # Issue 33686 - No core library found
 list_literal_negative_test: Crash # Error recovery in method body (malformed constructor invocation)
+list_literal_syntax_test/06: Crash
 main_not_a_function_test: Crash # Issue 33686 - No core library found
 metadata_test: Crash # Poor handling of annotation inside typedef
 missing_part_of_tag_test/01: Crash # Issue #34043 - Error recovery in outline (part file missing "part of" declaration - see also #33587)