Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 5e99872

Browse files
Dan Rubelcommit-bot@chromium.org
Dan Rubel
authored andcommitted
Improve missing/invalid type reference recovery
Change-Id: I52231fba7ebce51242393a0030325198bf51dfbf Reviewed-on: https://dart-review.googlesource.com/50340 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
1 parent 899dbe4 commit 5e99872

File tree

7 files changed

+100
-48
lines changed

7 files changed

+100
-48
lines changed

pkg/analyzer/test/generated/parser_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15043,6 +15043,18 @@ abstract class StatementParserTestMixin implements AbstractParserTestCase {
1504315043
expect(variableList.type, new isInstanceOf<GenericFunctionType>());
1504415044
}
1504515045

15046+
void test_invalid_typeParamAnnotation() {
15047+
parseCompilationUnit('main() { C<@Foo T> v; }',
15048+
errors: usingFastaParser
15049+
// TODO(danrubel): Improve this error to indicate that annotations
15050+
// are not valid in this context.
15051+
? [expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 11, 1)]
15052+
: [
15053+
expectedError(ParserErrorCode.MISSING_IDENTIFIER, 11, 1),
15054+
expectedError(ParserErrorCode.EXPECTED_TOKEN, 11, 1)
15055+
]);
15056+
}
15057+
1504615058
void test_parseStatement_emptyTypeArgumentList() {
1504715059
var declaration = parseStatement('C<> c;') as VariableDeclarationStatement;
1504815060
assertErrorsWithCodes([ParserErrorCode.EXPECTED_TYPE_NAME]);

pkg/analyzer/test/src/fasta/recovery/partial_code/class_declaration_test.dart

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ class ClassDeclarationTest extends PartialCodeTest {
3636
'class _s_ {}',
3737
failing: <String>[
3838
'typedef',
39-
'functionVoid',
4039
'functionNonVoid',
4140
'getter',
4241
'setter'
@@ -53,8 +52,7 @@ class ClassDeclarationTest extends PartialCodeTest {
5352
'class A extends _s_ {}',
5453
failing: allExceptEof),
5554
new TestDescriptor('extendsBody', 'class A extends {}',
56-
[ParserErrorCode.EXPECTED_TYPE_NAME], 'class A extends _s_ {}',
57-
allFailing: true),
55+
[ParserErrorCode.EXPECTED_TYPE_NAME], 'class A extends _s_ {}'),
5856
new TestDescriptor(
5957
'extendsWithNameBody',
6058
'class A extends with B {}',
@@ -80,8 +78,7 @@ class ClassDeclarationTest extends PartialCodeTest {
8078
'extendsNameWithBody',
8179
'class A extends B with {}',
8280
[ParserErrorCode.EXPECTED_TYPE_NAME],
83-
'class A extends B with _s_ {}',
84-
allFailing: true),
81+
'class A extends B with _s_ {}'),
8582
new TestDescriptor(
8683
'extendsNameImplements',
8784
'class A extends B implements',
@@ -95,8 +92,7 @@ class ClassDeclarationTest extends PartialCodeTest {
9592
'extendsNameImplementsBody',
9693
'class A extends B implements {}',
9794
[ParserErrorCode.EXPECTED_TYPE_NAME],
98-
'class A extends B implements _s_ {}',
99-
allFailing: true),
95+
'class A extends B implements _s_ {}'),
10096
new TestDescriptor(
10197
'extendsNameWithNameImplements',
10298
'class A extends B with C implements',
@@ -110,8 +106,7 @@ class ClassDeclarationTest extends PartialCodeTest {
110106
'extendsNameWithNameImplementsBody',
111107
'class A extends B with C implements {}',
112108
[ParserErrorCode.EXPECTED_TYPE_NAME],
113-
'class A extends B with C implements _s_ {}',
114-
allFailing: true),
109+
'class A extends B with C implements _s_ {}'),
115110
new TestDescriptor(
116111
'implements',
117112
'class A implements',
@@ -121,9 +116,11 @@ class ClassDeclarationTest extends PartialCodeTest {
121116
],
122117
'class A implements _s_ {}',
123118
failing: allExceptEof),
124-
new TestDescriptor('implementsBody', 'class A implements {}',
125-
[ParserErrorCode.EXPECTED_TYPE_NAME], 'class A implements _s_ {}',
126-
allFailing: true),
119+
new TestDescriptor(
120+
'implementsBody',
121+
'class A implements {}',
122+
[ParserErrorCode.EXPECTED_TYPE_NAME],
123+
'class A implements _s_ {}'),
127124
new TestDescriptor(
128125
'implementsNameComma',
129126
'class A implements B,',
@@ -137,8 +134,7 @@ class ClassDeclarationTest extends PartialCodeTest {
137134
'implementsNameCommaBody',
138135
'class A implements B, {}',
139136
[ParserErrorCode.EXPECTED_TYPE_NAME],
140-
'class A implements B, _s_ {}',
141-
allFailing: true),
137+
'class A implements B, _s_ {}'),
142138
],
143139
PartialCodeTest.declarationSuffixes);
144140
}

pkg/analyzer/test/src/fasta/recovery/partial_code/local_variable_test.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ class LocalVariableTest extends PartialCodeTest {
6666
'for',
6767
'labeled',
6868
'localFunctionNonVoid',
69-
'localFunctionVoid',
7069
'return',
7170
]),
7271
new TestDescriptor(
@@ -85,7 +84,6 @@ class LocalVariableTest extends PartialCodeTest {
8584
'for',
8685
'labeled',
8786
'localFunctionNonVoid',
88-
'localFunctionVoid',
8987
'return',
9088
]),
9189
new TestDescriptor('constNameCommaName', 'const a, b',

pkg/analyzer/test/src/fasta/recovery/partial_code/top_level_variable_test.dart

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,7 @@ class TopLevelVariableTest extends PartialCodeTest {
7676
CompileTimeErrorCode.CONST_NOT_INITIALIZED
7777
],
7878
"const a, _s_;",
79-
failing: [
80-
'typedef',
81-
'functionVoid',
82-
'functionNonVoid',
83-
'getter',
84-
'setter'
85-
],
79+
failing: ['typedef', 'functionNonVoid', 'getter', 'setter'],
8680
expectedErrorsInValidCode: [
8781
CompileTimeErrorCode.CONST_NOT_INITIALIZED,
8882
CompileTimeErrorCode.CONST_NOT_INITIALIZED
@@ -98,13 +92,7 @@ class TopLevelVariableTest extends PartialCodeTest {
9892
CompileTimeErrorCode.CONST_NOT_INITIALIZED
9993
],
10094
"const int a, _s_;",
101-
failing: [
102-
'typedef',
103-
'functionVoid',
104-
'functionNonVoid',
105-
'getter',
106-
'setter'
107-
],
95+
failing: ['typedef', 'functionNonVoid', 'getter', 'setter'],
10896
expectedErrorsInValidCode: [
10997
CompileTimeErrorCode.CONST_NOT_INITIALIZED,
11098
CompileTimeErrorCode.CONST_NOT_INITIALIZED

pkg/front_end/lib/src/fasta/parser/identifier_context.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,7 @@ class IdentifierContext {
126126
recoveryTemplate: templateExpectedType);
127127

128128
/// Identifier is the start of a reference to a type declared elsewhere.
129-
static const typeReference = const IdentifierContext('typeReference',
130-
isScopeReference: true,
131-
isBuiltInIdentifierAllowed: false,
132-
recoveryTemplate: templateExpectedType);
129+
static const typeReference = const TypeReferenceIdentifierContext();
133130

134131
/// Identifier is part of a reference to a type declared elsewhere, but it's
135132
/// not the first identifier of the reference.

pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import 'identifier_context.dart';
1212

1313
import 'parser.dart' show Parser;
1414

15-
import 'type_info.dart' show insertSyntheticIdentifierAfter;
15+
import 'type_info.dart'
16+
show insertSyntheticIdentifierAfter, isValidTypeReference;
1617

1718
import 'util.dart' show optional;
1819

@@ -66,3 +67,72 @@ class LibraryIdentifierContext extends IdentifierContext {
6667
optional('var', token) ||
6768
optional('void', token);
6869
}
70+
71+
class TypeReferenceIdentifierContext extends IdentifierContext {
72+
const TypeReferenceIdentifierContext()
73+
: super('typeReference',
74+
isScopeReference: true,
75+
isBuiltInIdentifierAllowed: false,
76+
recoveryTemplate: fasta.templateExpectedType);
77+
78+
@override
79+
Token ensureIdentifier(Token token, Parser parser) {
80+
Token next = token.next;
81+
assert(next.kind != IDENTIFIER_TOKEN);
82+
if (isValidTypeReference(next)) {
83+
return next;
84+
}
85+
86+
// Recovery: skip over any annotations
87+
while (optional('@', next)) {
88+
// TODO(danrubel): Improve this error message to indicate that an
89+
// annotation is not allowed before type arguments.
90+
parser.reportRecoverableErrorWithToken(
91+
next, fasta.templateUnexpectedToken);
92+
93+
Token annotation = next.next;
94+
if (annotation.isIdentifier) {
95+
if (optional('(', annotation.next)) {
96+
if (annotation.next.endGroup.next.isIdentifier) {
97+
token = annotation.next.endGroup;
98+
next = token.next;
99+
}
100+
} else if (annotation.next.isIdentifier) {
101+
token = annotation;
102+
next = token.next;
103+
}
104+
}
105+
}
106+
if (isValidTypeReference(next)) {
107+
return next;
108+
} else if (next.isKeywordOrIdentifier) {
109+
if (optional("void", next)) {
110+
parser.reportRecoverableError(next, fasta.messageInvalidVoid);
111+
} else if (next.type.isBuiltIn) {
112+
parser.reportRecoverableErrorWithToken(
113+
next, fasta.templateBuiltInIdentifierAsType);
114+
} else {
115+
parser.reportRecoverableErrorWithToken(
116+
next, fasta.templateExpectedType);
117+
}
118+
return next;
119+
}
120+
parser.reportRecoverableErrorWithToken(next, fasta.templateExpectedType);
121+
if (!isOneOfOrEof(next, ['>', ')', ']', '{', '}', ',', ';'])) {
122+
// When in doubt, consume the token to ensure we make progress
123+
token = next;
124+
next = token.next;
125+
}
126+
// Insert a synthetic identifier to satisfy listeners.
127+
return insertSyntheticIdentifierAfter(token, parser);
128+
}
129+
}
130+
131+
bool isOneOfOrEof(Token token, Iterable<String> followingValues) {
132+
for (String tokenValue in followingValues) {
133+
if (optional(tokenValue, token)) {
134+
return true;
135+
}
136+
}
137+
return token.isEof;
138+
}

pkg/front_end/lib/src/fasta/parser/parser.dart

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1843,10 +1843,7 @@ class Parser {
18431843
// ensureIdentifier methods in the various IdentifierContext subclasses.
18441844

18451845
if (!next.isIdentifier) {
1846-
if (optional("void", next)) {
1847-
reportRecoverableError(next, fasta.messageInvalidVoid);
1848-
token = next;
1849-
} else if (next is ErrorToken) {
1846+
if (next is ErrorToken) {
18501847
// TODO(brianwilkerson): This preserves the current semantics, but the
18511848
// listener should not be recovering from this case, so this needs to be
18521849
// reworked to recover in this method (probably inside the outermost
@@ -1898,13 +1895,8 @@ class Parser {
18981895
reportRecoverableErrorWithToken(
18991896
next, fasta.templateBuiltInIdentifierInDeclaration);
19001897
} else if (!optional("dynamic", next)) {
1901-
if (context == IdentifierContext.typeReference &&
1902-
optional('.', next.next)) {
1903-
// Built in identifiers may be used as a prefix
1904-
} else {
1905-
reportRecoverableErrorWithToken(
1906-
next, fasta.templateBuiltInIdentifierAsType);
1907-
}
1898+
reportRecoverableErrorWithToken(
1899+
next, fasta.templateBuiltInIdentifierAsType);
19081900
}
19091901
token = next;
19101902
} else if (!inPlainSync && next.type.isPseudo) {
@@ -2016,8 +2008,7 @@ class Parser {
20162008
followingValues = [';', '=', ','];
20172009
} else if (context == IdentifierContext.typedefDeclaration) {
20182010
followingValues = ['(', '<', ';'];
2019-
} else if (context == IdentifierContext.typeReference ||
2020-
context == IdentifierContext.typeReferenceContinuation) {
2011+
} else if (context == IdentifierContext.typeReferenceContinuation) {
20212012
followingValues = ['>', ')', ']', '}', ',', ';'];
20222013
} else if (context == IdentifierContext.typeVariableDeclaration) {
20232014
followingValues = ['<', '>', ';', '}'];

0 commit comments

Comments
 (0)