Improve type parameter and type argument recovery
This improves recovery when parsing type arguments in
* top level fields and functions
* class members
and when parsing type parameters in
* top level functions
* class declaration
* class methods
Change-Id: I7dbb520d6bb39c0d13b27698658dafe580f2b16c
Reviewed-on: https://dart-review.googlesource.com/56681
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/test/generated/parser_forest_test.dart b/pkg/analyzer/test/generated/parser_forest_test.dart
index 0e3e8de..5908df2 100644
--- a/pkg/analyzer/test/generated/parser_forest_test.dart
+++ b/pkg/analyzer/test/generated/parser_forest_test.dart
@@ -891,6 +891,16 @@
}
@failingTest
+ void test_constructorPartial2() {
+ super.test_constructorPartial();
+ }
+
+ @failingTest
+ void test_constructorPartial3() {
+ super.test_constructorPartial();
+ }
+
+ @failingTest
void test_constructorWithReturnType() {
super.test_constructorWithReturnType();
}
@@ -4279,6 +4289,11 @@
}
@failingTest
+ void test_incompleteTypeParameters3() {
+ super.test_incompleteTypeParameters3();
+ }
+
+ @failingTest
void test_invalidFunctionBodyModifier() {
super.test_invalidFunctionBodyModifier();
}
diff --git a/pkg/analyzer/test/generated/parser_test.dart b/pkg/analyzer/test/generated/parser_test.dart
index 5e92a04..f324c87 100644
--- a/pkg/analyzer/test/generated/parser_test.dart
+++ b/pkg/analyzer/test/generated/parser_test.dart
@@ -2715,6 +2715,42 @@
]);
}
+ void test_constructorPartial2() {
+ createParser('class C { C<@Foo }');
+ parser.parseCompilationUnit2();
+ listener.assertErrors(usingFastaParser
+ ? [
+ 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)
+ ]
+ : [
+ expectedError(ParserErrorCode.EXPECTED_TYPE_NAME, 12, 1),
+ expectedError(ParserErrorCode.EXPECTED_TOKEN, 12, 1),
+ expectedError(ParserErrorCode.EXPECTED_CLASS_MEMBER, 12, 1),
+ expectedError(ParserErrorCode.EXPECTED_CLASS_MEMBER, 17, 1)
+ ]);
+ }
+
+ void test_constructorPartial3() {
+ createParser('class C { C<@Foo @Bar() }');
+ parser.parseCompilationUnit2();
+ listener.assertErrors(usingFastaParser
+ ? [
+ 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)
+ ]
+ : [
+ expectedError(ParserErrorCode.EXPECTED_TYPE_NAME, 12, 1),
+ expectedError(ParserErrorCode.EXPECTED_TOKEN, 12, 1),
+ expectedError(ParserErrorCode.EXPECTED_CLASS_MEMBER, 12, 1),
+ expectedError(ParserErrorCode.EXPECTED_CLASS_MEMBER, 24, 1)
+ ]);
+ }
+
void test_constructorWithReturnType() {
createParser('C C() {}');
ClassMember member = parser.parseClassMember('C');
@@ -4432,12 +4468,15 @@
// 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!>() {}');
+ createParser('void m<E, hello!>() {}', expectedEndOffset: 6);
ClassMember member = parser.parseClassMember('C');
expectNotNullIfNoErrors(member);
listener.assertErrors(usingFastaParser
? [
- expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 15, 1) /*!*/
+ // TODO(danrubel): Improve recovery
+ expectedError(
+ ParserErrorCode.MISSING_METHOD_PARAMETERS, 5, 1) /*<*/,
+ expectedError(ParserErrorCode.MISSING_FUNCTION_BODY, 6, 1) /*E*/
]
: [
expectedError(ParserErrorCode.EXPECTED_TOKEN, 0, 0) /*>*/,
@@ -4448,8 +4487,10 @@
]);
expect(member, new isInstanceOf<MethodDeclaration>());
MethodDeclaration method = member;
- expect(method.typeParameters.toString(), '<E, hello>',
- reason: 'parser recovers what it can');
+ if (!usingFastaParser) {
+ expect(method.typeParameters.toString(), '<E, hello>',
+ reason: 'parser recovers what it can');
+ }
}
void test_missingAssignableSelector_identifiersAssigned() {
@@ -11486,7 +11527,7 @@
CompilationUnit unit = parseCompilationUnit(r'''
class C {
final List<int f;
-}''', codes: [ParserErrorCode.EXPECTED_TOKEN]);
+}''', errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 27, 1)]);
// one class
List<CompilationUnitMember> declarations = unit.declarations;
expect(declarations, hasLength(1));
@@ -11544,6 +11585,26 @@
expect(token.isSynthetic, isTrue);
}
+ 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)
+ ]);
+ // one class
+ List<CompilationUnitMember> declarations = unit.declarations;
+ expect(declarations, hasLength(1));
+ ClassDeclaration classDecl = declarations[0] as ClassDeclaration;
+ // validate the type parameters
+ TypeParameterList typeParameters = classDecl.typeParameters;
+ expect(typeParameters.typeParameters, hasLength(1));
+ // synthetic '>'
+ Token token = typeParameters.endToken;
+ expect(token.type, TokenType.GT);
+ expect(token.isSynthetic, isTrue);
+ }
+
void test_invalidFunctionBodyModifier() {
parseCompilationUnit("f() sync {}",
codes: [ParserErrorCode.MISSING_STAR_AFTER_SYNC]);
@@ -11555,14 +11616,7 @@
G<int double> g;
}''',
errors: usingFastaParser
- ? [
- // TODO(danrubel): Improve missing comma recovery.
- expectedError(ParserErrorCode.EXPECTED_TOKEN, 18, 6),
- expectedError(ParserErrorCode.MISSING_METHOD_PARAMETERS, 12, 1),
- expectedError(ParserErrorCode.MISSING_FUNCTION_BODY, 26, 1),
- expectedError(
- ParserErrorCode.MISSING_CONST_FINAL_VAR_OR_TYPE, 26, 1),
- ]
+ ? [expectedError(ParserErrorCode.EXPECTED_TOKEN, 18, 6)]
: [
expectedError(ParserErrorCode.EXPECTED_TOKEN, 18, 6),
expectedError(ParserErrorCode.EXPECTED_TOKEN, 18, 6),
@@ -11576,12 +11630,12 @@
expect(declarations, hasLength(1));
// validate members
if (usingFastaParser) {
-// ClassDeclaration classDecl = declarations[0] as ClassDeclaration;
-// expect(classDecl.members, hasLength(1));
-// FieldDeclaration fields = classDecl.members.first;
-// expect(fields.fields.variables, hasLength(1));
-// VariableDeclaration field = fields.fields.variables.first;
-// expect(field.name.name, 'g');
+ ClassDeclaration classDecl = declarations[0] as ClassDeclaration;
+ expect(classDecl.members, hasLength(1));
+ FieldDeclaration fields = classDecl.members.first;
+ expect(fields.fields.variables, hasLength(1));
+ VariableDeclaration field = fields.fields.variables.first;
+ expect(field.name.name, 'g');
}
}
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 447b10a..b54a731 100644
--- a/pkg/analyzer/test/src/fasta/recovery/paired_tokens_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/paired_tokens_test.dart
@@ -84,7 +84,6 @@
@failingTest
void test_typeArguments_inner_last() {
- // Parser crashes
testRecovery('''
List<List<int>
''', [ScannerErrorCode.EXPECTED_TOKEN], '''
@@ -94,7 +93,6 @@
@failingTest
void test_typeArguments_inner_notLast() {
- // Parser crashes
testRecovery('''
Map<List<int, List<String>>
''', [ScannerErrorCode.EXPECTED_TOKEN], '''
@@ -102,9 +100,17 @@
''');
}
+ void test_typeArguments_inner_notLast2() {
+ // TODO(danrubel): Investigate better recovery.
+ testRecovery('''
+Map<List<int, List<String>> f;
+''', [ParserErrorCode.EXPECTED_TOKEN], '''
+Map<List<int, List<String>>> f;
+''');
+ }
+
@failingTest
void test_typeArguments_outer_last() {
- // Parser crashes
testRecovery('''
List<int
''', [ScannerErrorCode.EXPECTED_TOKEN], '''
@@ -112,6 +118,14 @@
''');
}
+ void test_typeArguments_missing_comma() {
+ testRecovery('''
+List<int double> f;
+''', [ParserErrorCode.EXPECTED_TOKEN], '''
+List<int, double> f;
+''');
+ }
+
void test_typeParameters_last() {
testRecovery('''
f<T() => null;
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 429c359..05f512f 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -90,7 +90,6 @@
TypeParamOrArgInfo,
computeMethodTypeArguments,
computeType,
- computeTypeParam,
computeTypeParamOrArg,
isGeneralizedFunctionType,
isValidTypeReference,
@@ -1673,7 +1672,7 @@
assert(optional('class', token));
Token name =
ensureIdentifier(token, IdentifierContext.classOrNamedMixinDeclaration);
- token = computeTypeParam(name).parseVariables(name, this);
+ token = computeTypeParamOrArg(name, true).parseVariables(name, this);
if (optional('=', token.next)) {
listener.beginNamedMixinApplication(begin, abstractToken, name);
return parseNamedMixinApplication(token, begin, classKeyword);
@@ -2309,7 +2308,7 @@
}
Token beforeType = token;
- TypeInfo typeInfo = computeType(token, false);
+ TypeInfo typeInfo = computeType(token, false, true);
token = typeInfo.skipType(token);
next = token.next;
@@ -2473,7 +2472,7 @@
Token token;
bool isGetter = false;
if (getOrSet == null) {
- token = computeTypeParam(name).parseVariables(name, this);
+ token = computeTypeParamOrArg(name, true).parseVariables(name, this);
} else {
isGetter = optional("get", getOrSet);
token = name;
@@ -2965,7 +2964,7 @@
listener.beginMember();
Token beforeType = token;
- TypeInfo typeInfo = computeType(token, false);
+ TypeInfo typeInfo = computeType(token, false, true);
token = typeInfo.skipType(token);
next = token.next;
@@ -3146,7 +3145,7 @@
bool isGetter = false;
if (getOrSet == null) {
- token = computeTypeParam(token).parseVariables(token, this);
+ token = computeTypeParamOrArg(token, true).parseVariables(token, this);
} else {
isGetter = optional("get", getOrSet);
listener.handleNoTypeVariables(token.next);
@@ -5877,7 +5876,7 @@
return reportAndSkipEnumInClass(next);
} else if (identical(value, 'typedef')) {
return reportAndSkipTypedefInClass(next);
- } else if (next.isOperator) {
+ } else if (next.isOperator && next.endGroup == null) {
return parseInvalidOperatorDeclaration(beforeStart, externalToken,
staticToken, covariantToken, varFinalOrConst, beforeType);
}
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 b8ab28e..529c38e 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
@@ -2,8 +2,6 @@
// 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 'package:front_end/src/fasta/util/link.dart';
-
import '../../scanner/token.dart'
show
BeginToken,
@@ -41,49 +39,6 @@
/// Initialize a newly created re-writer.
TokenStreamRewriter();
- /// For every `<` in [unbalancedLt] append a synthetic `>`.
- /// Return the first `<`.
- ///
- /// [unbalancedLt] is a collection of `<` without closing `>` in the reverse
- /// order from which they were encountered in the token stream.
- Token balanceLt(final Token beforeLt, Token end, Link<Token> unbalancedLt) {
- assert(optional('<', beforeLt.next));
- assert(unbalancedLt.isNotEmpty);
-
- BeginToken lt;
- while (unbalancedLt.isNotEmpty) {
- lt = unbalancedLt.head;
- unbalancedLt = unbalancedLt.tail;
- Token next = end.next;
- Token gt;
-
- if (optional('>', next)) {
- gt = next;
- } else if (optional('>>', next)) {
- gt = new SimpleToken(TokenType.GT, next.charOffset)
- ..setNext(new SimpleToken(TokenType.GT, next.charOffset + 1)
- ..setNext(next.next));
- } else if (optional('>=', next)) {
- gt = new SimpleToken(TokenType.GT, next.charOffset)
- ..setNext(new SimpleToken(TokenType.EQ, next.charOffset + 1)
- ..setNext(next.next));
- } else if (optional('>>=', next)) {
- gt = new SimpleToken(TokenType.GT, next.charOffset)
- ..setNext(new SimpleToken(TokenType.GT, next.charOffset + 1)
- ..setNext(new SimpleToken(TokenType.EQ, next.charOffset + 2)
- ..setNext(next.next)));
- } else {
- gt = new SyntheticToken(TokenType.GT, next.charOffset)..setNext(next);
- }
-
- lt.endGroup = gt;
- end.setNext(gt);
- end = gt;
- }
- assert(beforeLt.next == lt);
- return lt;
- }
-
/// Insert a synthetic identifier after [token] and return the new identifier.
Token insertSyntheticIdentifier(Token token) {
Token identifier = new SyntheticStringToken(
@@ -136,31 +91,48 @@
return replacementToken;
}
- /// Split a `>>` token into two separate `>` tokens and return the first `>`.
- /// This sets [start].endGroup to the second `>` and updates the token stream,
- /// but does not set the inner group's endGroup.
- Token splitGtGt(BeginToken start) {
- Token gtgt = start.endGroup;
- assert(gtgt != null);
- assert(optional('>>', gtgt));
+ /// 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);
- // A no-op rewriter could simply return `>>` here.
-
- Token gt1 =
- new SimpleToken(TokenType.GT, gtgt.charOffset, gtgt.precedingComments);
- Token gt2 = gt1.setNext(new SimpleToken(TokenType.GT, gt1.charOffset + 1));
- gt2.setNext(gtgt.next);
+ Token gt;
+ if (optional('>>', end)) {
+ gt = new SimpleToken(TokenType.GT, end.charOffset, end.precedingComments)
+ ..setNext(new SimpleToken(TokenType.GT, end.charOffset + 1)
+ ..setNext(end.next));
+ } else if (optional('>=', end)) {
+ gt = new SimpleToken(TokenType.GT, end.charOffset, end.precedingComments)
+ ..setNext(new SimpleToken(TokenType.EQ, end.charOffset + 1)
+ ..setNext(end.next));
+ } else if (optional('>>=', end)) {
+ gt = new SimpleToken(TokenType.GT, end.charOffset, end.precedingComments)
+ ..setNext(new SimpleToken(TokenType.GT, end.charOffset + 1)
+ ..setNext(new SimpleToken(TokenType.EQ, end.charOffset + 2)
+ ..setNext(end.next)));
+ } else {
+ gt = new SyntheticToken(TokenType.GT, end.charOffset)..setNext(end);
+ }
Token token = start;
Token next = token.next;
- while (!identical(next, gtgt)) {
+ while (!identical(next, end)) {
token = next;
next = token.next;
}
- token.setNext(gt1);
+ token.setNext(gt);
- start.endGroup = gt2;
- return gt1;
+ 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
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 c37c160..c94e6f4 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info.dart
@@ -130,15 +130,19 @@
/// Called by the parser to obtain information about a possible type reference
/// that follows [token]. This does not modify the token stream.
///
+/// 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, [Token innerEndGroup]) {
+TypeInfo computeType(final Token token, bool required,
+ [bool inDeclaration = false, Token innerEndGroup]) {
Token next = token.next;
if (!isValidTypeReference(next)) {
if (next.type.isBuiltIn) {
TypeParamOrArgInfo typeParamOrArg =
- computeTypeParamOrArg(next, innerEndGroup);
+ computeTypeParamOrArg(next, inDeclaration, innerEndGroup);
if (typeParamOrArg != noTypeParamOrArg) {
// Recovery: built-in `<` ... `>`
if (required || looksLikeName(typeParamOrArg.skip(next).next)) {
@@ -159,7 +163,7 @@
} else if (required && optional('.', next)) {
// Recovery: looks like prefixed type missing the prefix
return new ComplexTypeInfo(
- token, computeTypeParamOrArg(next, innerEndGroup))
+ token, computeTypeParamOrArg(next, inDeclaration, innerEndGroup))
.computePrefixedType(required);
}
return noType;
@@ -185,7 +189,7 @@
// We've seen an identifier.
TypeParamOrArgInfo typeParamOrArg =
- computeTypeParamOrArg(next, innerEndGroup);
+ computeTypeParamOrArg(next, inDeclaration, innerEndGroup);
if (typeParamOrArg != noTypeParamOrArg) {
if (typeParamOrArg == simpleTypeArgument1) {
// We've seen identifier `<` identifier `>`
@@ -215,7 +219,8 @@
next = next.next;
if (isValidTypeReference(next)) {
// We've seen identifier `.` identifier
- typeParamOrArg = computeTypeParamOrArg(next, innerEndGroup);
+ typeParamOrArg =
+ computeTypeParamOrArg(next, inDeclaration, innerEndGroup);
next = next.next;
if (typeParamOrArg == noTypeParamOrArg &&
!isGeneralizedFunctionType(next)) {
@@ -233,7 +238,8 @@
}
// identifier `.` non-identifier
if (required) {
- typeParamOrArg = computeTypeParamOrArg(token.next.next, innerEndGroup);
+ typeParamOrArg =
+ computeTypeParamOrArg(token.next.next, inDeclaration, innerEndGroup);
return new ComplexTypeInfo(token, typeParamOrArg)
.computePrefixedType(required);
}
@@ -255,47 +261,30 @@
}
/// Called by the parser to obtain information about a possible group of type
-/// parameters that follow [token] in a top level or class member declaration.
-/// It assumes that a leading `<` cannot be part of an expression, and thus
-/// tries to more aggressively recover given an unmatched '<'
-TypeParamOrArgInfo computeTypeParam(Token token) {
- if (!optional('<', token.next)) {
- return noTypeParamOrArg;
- }
- TypeParamOrArgInfo typeParam = computeTypeParamOrArgImpl(token);
- if (typeParam != noTypeParamOrArg) {
- return typeParam;
- }
-
- // Recovery
- if (token.next.endGroup == null) {
- // Attempt to find where the missing `>` should be inserted.
- return new ComplexTypeParamOrArgInfo(token).computeUnbalanced();
- } else {
- // The `<` `>` are balanced but there's still a problem.
- return new ComplexTypeParamOrArgInfo(token).computeBalancedButInvalid();
- }
-}
-
-/// Called by the parser to obtain information about a possible group of type
/// parameters or type arguments that follow [token].
/// This does not modify the token stream.
///
+/// 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, [Token innerEndGroup]) {
- return optional('<', token.next)
- ? computeTypeParamOrArgImpl(token, innerEndGroup)
- : noTypeParamOrArg;
-}
-
-TypeParamOrArgInfo computeTypeParamOrArgImpl(Token token,
- [Token innerEndGroup]) {
+TypeParamOrArgInfo computeTypeParamOrArg(Token token,
+ [bool inDeclaration = false, Token innerEndGroup]) {
Token next = token.next;
- assert(optional('<', next));
+ if (!optional('<', next)) {
+ return noTypeParamOrArg;
+ }
Token endGroup = next.endGroup ?? innerEndGroup;
if (endGroup == null) {
+ if (inDeclaration) {
+ // Recovery
+ // Since the leading `<` cannot be part of an expression,
+ // try to more aggressively recover given an unbalanced '<'.
+ return new ComplexTypeParamOrArgInfo(token, inDeclaration)
+ .compute(innerEndGroup);
+ }
return noTypeParamOrArg;
}
Token identifier = next.next;
@@ -306,7 +295,8 @@
return simpleTypeArgument1;
}
// TODO(danrubel): Consider adding additional const for common situations.
- return new ComplexTypeParamOrArgInfo(token).compute(innerEndGroup);
+ return new ComplexTypeParamOrArgInfo(token, inDeclaration)
+ .compute(innerEndGroup);
}
/// 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 89247f6..c0774ee 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
@@ -23,7 +23,7 @@
import 'type_info.dart';
-import 'util.dart' show isOneOf, optional, skipMetadata;
+import 'util.dart' show optional, skipMetadata;
/// See documentation on the [noType] const.
class NoType implements TypeInfo {
@@ -225,6 +225,21 @@
// `typedef` is probably a separate declaration.
(!optional('typedef', token) || !token.next.isIdentifier));
+/// When missing a comma, determine if the given token looks like it should
+/// be part of a collection of type parameters or arguments.
+bool looksLikeTypeParamOrArg(bool inDeclaration, Token token) {
+ if (inDeclaration && token.kind == IDENTIFIER_TOKEN) {
+ Token next = token.next;
+ if (next.kind == IDENTIFIER_TOKEN ||
+ optional(',', next) ||
+ optional('>', next) ||
+ optional('>>', next)) {
+ return true;
+ }
+ }
+ return false;
+}
+
Token skipTypeVariables(Token token) {
assert(optional('<', token));
Token endGroup = token.endGroup;
@@ -359,9 +374,9 @@
assert(identical(token, end) || optional('>', token));
// During recovery, [token] may be a synthetic that was inserted in the
- // middle of the type reference. In this situation, return [end] so that it
- // matches [skipType], and so that the next token to be parsed is correct.
- return token.isSynthetic ? end : token;
+ // middle of the type reference.
+ end = token;
+ return token;
}
@override
@@ -548,10 +563,12 @@
@override
Token skip(Token token) {
token = token.next;
+ assert(optional('<', token));
assert(token.endGroup != null ||
(optional('>', token.next.next) || optional('>>', token.next.next)));
- return token.endGroup ??
- (optional('>>', token.next.next) ? token.next : token.next.next);
+ return (optional('>>', token.endGroup ?? token.next.next)
+ ? token.next
+ : token.next.next);
}
}
@@ -559,17 +576,18 @@
/// The first token in the type var.
final BeginToken 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;
- /// A collection of `<` without closing `>` in the reverse order from which
- /// they were encountered in the token stream.
- Link<Token> unbalancedLt = const Link<Token>();
-
- ComplexTypeParamOrArgInfo(Token token)
+ ComplexTypeParamOrArgInfo(Token token, this.inDeclaration)
: assert(optional('<', token.next)),
+ assert(inDeclaration != null),
start = token.next;
/// Parse the tokens and return the receiver or [noTypeParamOrArg] if there
@@ -587,12 +605,12 @@
Token token;
Token next = start;
- do {
- TypeInfo typeInfo = computeType(next, true, innerEndGroup);
+ while (true) {
+ TypeInfo typeInfo = computeType(next, true, inDeclaration, innerEndGroup);
if (typeInfo == noType) {
while (typeInfo == noType && optional('@', next.next)) {
next = skipMetadata(next);
- typeInfo = computeType(next, true, innerEndGroup);
+ typeInfo = computeType(next, true, inDeclaration, innerEndGroup);
}
if (typeInfo == noType && !optional(',', next.next)) {
token = next;
@@ -605,10 +623,17 @@
token = typeInfo.skipType(next);
next = token.next;
if (optional('extends', next) || optional('super', next)) {
- token = computeType(next, true, innerEndGroup).skipType(next);
+ token = computeType(next, true, inDeclaration, innerEndGroup)
+ .skipType(next);
next = token.next;
}
- } while (optional(',', next));
+ if (!optional(',', next)) {
+ if (!looksLikeTypeParamOrArg(inDeclaration, next)) {
+ break;
+ }
+ // Recovery: Missing comma. Continue looping
+ }
+ }
if (next == start.endGroup) {
end = next;
@@ -617,95 +642,30 @@
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 {
return noTypeParamOrArg;
}
return this;
}
- /// Parse the tokens and return the receiver or [noTypeParamOrArg] if there
- /// are no type parameters or arguments. This does not modify the token
- /// stream.
- ///
- /// This is called when parsing type parameters in a top level
- /// or class member declaration. It assumes that a leading `<` cannot be part
- /// of an expression, and thus tries to more aggressively recover.
- ///
- TypeParamOrArgInfo computeBalancedButInvalid() {
- assert(start.endGroup != null);
- Token next = start.endGroup.next;
- if (next.isIdentifier || optional('(', next)) {
- end = start.endGroup;
- return this;
- } else {
- return noTypeParamOrArg;
- }
- }
-
- /// Parse the tokens and return the receiver or [noTypeParamOrArg] if there
- /// are no type parameters or arguments. This does not modify the token
- /// stream.
- ///
- /// This is called when parsing type parameters in a top level
- /// or class member declaration. It assumes that a leading `<` cannot be part
- /// of an expression, and thus tries to more aggressively recover
- /// given an unmatched '<'.
- ///
- TypeParamOrArgInfo computeUnbalanced() {
- assert(start.endGroup == null);
- unbalancedLt = unbalancedLt.prepend(start);
- Token token = start;
- Token next = token.next;
- while (!next.isEof) {
- if (optional('Function', next)) {
- next = next.next;
- if (optional('<', next)) {
- next = skipTypeVariables(next);
- if (next == null) {
- break;
- }
- next = next.next;
- }
- if (optional('(', next)) {
- next = next.endGroup;
- } else {
- break;
- }
- } else if (optional('<', next)) {
- Token endGroup = skipTypeVariables(next);
- if (endGroup != null) {
- next = endGroup;
- } else {
- unbalancedLt = unbalancedLt.prepend(next);
- }
- } else if (!isValidTypeReference(next) &&
- !isOneOf(next, const ['.', ',', 'extends', 'super'])) {
- break;
- }
- token = next;
- next = token.next;
- }
-
- end = token;
- return this;
- }
-
@override
Token parseArguments(Token token, Parser parser) {
- Token begin = balanceLt(token, parser);
- Token next = begin;
- Token innerEndGroup = processBeginGroup(begin, parser);
- parser.listener.beginTypeArguments(begin);
+ Token next = start;
+ Token innerEndGroup = processBeginGroup(start, parser);
+ parser.listener.beginTypeArguments(start);
int count = 0;
while (true) {
- TypeInfo typeInfo = computeType(next, true, innerEndGroup);
+ TypeInfo typeInfo = computeType(next, true, inDeclaration, innerEndGroup);
if (typeInfo == noType) {
// Recovery
while (typeInfo == noType && optional('@', next.next)) {
parser.reportRecoverableErrorWithToken(
next.next, fasta.templateUnexpectedToken);
next = skipMetadata(next);
- typeInfo = computeType(next, true, innerEndGroup);
+ typeInfo = computeType(next, true, inDeclaration, innerEndGroup);
}
// Fall through to process type (if any) and consume `,`
}
@@ -713,7 +673,7 @@
next = token.next;
++count;
if (!optional(',', next)) {
- if (IDENTIFIER_TOKEN != next.kind) {
+ if (!looksLikeTypeParamOrArg(inDeclaration, next)) {
break;
}
@@ -726,17 +686,16 @@
.next;
}
}
- end = processEndGroup(token, begin, parser);
- parser.listener.endTypeArguments(count, begin, end);
+ end = processEndGroup(token, start, parser);
+ parser.listener.endTypeArguments(count, start, end);
return end;
}
@override
Token parseVariables(Token token, Parser parser) {
- Token begin = balanceLt(token, parser);
- Token next = begin;
- Token innerEndGroup = processBeginGroup(begin, parser);
- parser.listener.beginTypeVariables(begin);
+ Token next = start;
+ Token innerEndGroup = processBeginGroup(start, parser);
+ parser.listener.beginTypeVariables(start);
int count = 0;
while (true) {
parser.listener.beginTypeVariable(next.next);
@@ -747,7 +706,7 @@
next = token.next;
if (optional('extends', next) || optional('super', next)) {
extendsOrSuper = next;
- token = computeType(next, true, innerEndGroup)
+ token = computeType(next, true, inDeclaration, innerEndGroup)
.ensureTypeOrVoid(next, parser);
next = token.next;
} else {
@@ -756,7 +715,7 @@
parser.listener.endTypeVariable(next, extendsOrSuper);
++count;
if (!optional(',', next)) {
- if (IDENTIFIER_TOKEN != next.kind) {
+ if (!looksLikeTypeParamOrArg(inDeclaration, next)) {
break;
}
@@ -769,71 +728,54 @@
.next;
}
}
- end = processEndGroup(token, begin, parser);
- parser.listener.endTypeVariables(count, begin, end);
+ end = processEndGroup(token, start, parser);
+ parser.listener.endTypeVariables(count, start, end);
return end;
}
@override
Token skip(Token token) => end;
-
- /// For every unbalanced `<` append a synthetic `>`. Return the first `<`
- Token balanceLt(Token token, Parser parser) {
- assert(identical(start, token.next));
- if (unbalancedLt.isEmpty) {
- return start;
- }
- Token begin = parser.rewriter.balanceLt(token, end, unbalancedLt);
- assert(begin.endGroup != null);
- if (begin.endGroup.isSynthetic) {
- parser.reportRecoverableError(
- begin.endGroup.next, fasta.templateExpectedButGot.withArguments('>'));
- }
- return begin;
- }
}
Token processBeginGroup(BeginToken start, Parser parser) {
- Token innerEndGroup;
if (start.endGroup != null && optional('>>', start.endGroup)) {
- innerEndGroup = parser.rewriter.splitGtGt(start);
+ return parser.rewriter.splitEndGroup(start);
}
- return innerEndGroup;
+ return null;
}
Token processEndGroup(Token token, BeginToken start, Parser parser) {
Token next = token.next;
if (next == start.endGroup) {
return next;
- } else if (optional('>', 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;
- } else if (optional('>>', next)) {
- // In this case, the last consumed token is the token before `>>`.
- return token;
- } else {
- // Recovery
- parser.reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
- if (start.endGroup != null) {
- return start.endGroup;
- }
- while (true) {
- if (optional('>', next)) {
- start.endGroup = next;
- return next;
- }
- if (optional('>>', next)) {
- // In this case, the last consumed token is the token before `>>`.
- return token;
- }
- if (next.isEof) {
- // Sanity check.
- return next;
- }
- token = next;
- next = token.next;
- }
}
+
+ // Recovery
+ if (start.endGroup != null) {
+ // Extraneous tokens between `<` and `>`.
+ parser.reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
+ return start.endGroup;
+ } else if (optional('>>', next) ||
+ optional('>=', next) ||
+ optional('>>=', next)) {
+ 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;
}
diff --git a/pkg/front_end/lib/src/fasta/parser/util.dart b/pkg/front_end/lib/src/fasta/parser/util.dart
index f158cc3..96495e7 100644
--- a/pkg/front_end/lib/src/fasta/parser/util.dart
+++ b/pkg/front_end/lib/src/fasta/parser/util.dart
@@ -99,7 +99,7 @@
next = token.next;
}
}
- if (optional('(', next)) {
+ if (optional('(', next) && !next.endGroup.isSynthetic) {
token = next.endGroup;
next = token.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 856aa40..9fed8bf 100644
--- a/pkg/front_end/test/fasta/parser/type_info_test.dart
+++ b/pkg/front_end/test/fasta/parser/type_info_test.dart
@@ -890,7 +890,7 @@
Token gtgt = start.next.next.next;
expect(gtgt.lexeme, '>>');
- TypeParamOrArgInfo typeVarInfo = computeTypeParamOrArg(start, gtgt);
+ TypeParamOrArgInfo typeVarInfo = computeTypeParamOrArg(start, false, gtgt);
expect(typeVarInfo, simpleTypeArgument1, reason: source);
}
@@ -1260,9 +1260,10 @@
}
TypeInfo compute(expectedInfo, String source, Token start, bool required,
- {Token innerEndGroup}) {
+ {bool inDeclaration = false, Token innerEndGroup}) {
int expectedGtGtAndNullEndCount = countGtGtAndNullEnd(start);
- TypeInfo typeInfo = computeType(start, required, innerEndGroup);
+ TypeInfo typeInfo =
+ computeType(start, required, inDeclaration, innerEndGroup);
expect(typeInfo, expectedInfo, reason: source);
expect(countGtGtAndNullEnd(start), expectedGtGtAndNullEndCount,
reason: 'computeType should not modify the token stream');