Improve top level const/final/var name recovery
Change-Id: Ie07ca020a5f5a6c3abbe2ee48bd83c4e3ea4075c
Reviewed-on: https://dart-review.googlesource.com/54600
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/test/generated/parser_test.dart b/pkg/analyzer/test/generated/parser_test.dart
index 1f39754e..5e1068a 100644
--- a/pkg/analyzer/test/generated/parser_test.dart
+++ b/pkg/analyzer/test/generated/parser_test.dart
@@ -2633,7 +2633,14 @@
void test_constEnum() {
parseCompilationUnit("const enum E {ONE}",
- errors: [expectedError(ParserErrorCode.CONST_ENUM, 0, 5)]);
+ errors: usingFastaParser
+ ? [
+ // Fasta interprets the `const` as a malformed top level const
+ // and `enum` as the start of an enum declaration.
+ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 6, 4),
+ expectedError(ParserErrorCode.EXPECTED_TOKEN, 6, 4),
+ ]
+ : [expectedError(ParserErrorCode.CONST_ENUM, 0, 5)]);
}
void test_constFactory() {
@@ -2687,7 +2694,14 @@
void test_constTypedef() {
parseCompilationUnit("const typedef F();",
- errors: [expectedError(ParserErrorCode.CONST_TYPEDEF, 0, 5)]);
+ errors: usingFastaParser
+ ? [
+ // Fasta interprets the `const` as a malformed top level const
+ // and `typedef` as the start of an typedef declaration.
+ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 6, 7),
+ expectedError(ParserErrorCode.EXPECTED_TOKEN, 6, 7),
+ ]
+ : [expectedError(ParserErrorCode.CONST_TYPEDEF, 0, 5)]);
}
void test_continueOutsideOfLoop_continueInDoStatement() {
@@ -3616,7 +3630,14 @@
void test_finalEnum() {
parseCompilationUnit("final enum E {ONE}",
- errors: [expectedError(ParserErrorCode.FINAL_ENUM, 0, 5)]);
+ errors: usingFastaParser
+ ? [
+ // Fasta interprets the `final` as a malformed top level final
+ // and `enum` as the start of a enum declaration.
+ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 6, 4),
+ expectedError(ParserErrorCode.EXPECTED_TOKEN, 6, 4),
+ ]
+ : [expectedError(ParserErrorCode.FINAL_ENUM, 0, 5)]);
}
void test_finalMethod() {
@@ -3633,7 +3654,14 @@
void test_finalTypedef() {
parseCompilationUnit("final typedef F();",
- errors: [expectedError(ParserErrorCode.FINAL_TYPEDEF, 0, 5)]);
+ errors: usingFastaParser
+ ? [
+ // Fasta interprets the `final` as a malformed top level final
+ // and `typedef` as the start of an typedef declaration.
+ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 6, 7),
+ expectedError(ParserErrorCode.EXPECTED_TOKEN, 6, 7),
+ ]
+ : [expectedError(ParserErrorCode.FINAL_TYPEDEF, 0, 5)]);
}
void test_functionTypedField_invalidType_abstract() {
@@ -5702,14 +5730,24 @@
void test_varClass() {
parseCompilationUnit("var class C {}",
errors: usingFastaParser
- ? [expectedError(ParserErrorCode.EXTRANEOUS_MODIFIER, 0, 3)]
+ ? [
+ // Fasta interprets the `var` as a malformed top level var
+ // and `class` as the start of a class declaration.
+ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 4, 5),
+ expectedError(ParserErrorCode.EXPECTED_TOKEN, 4, 5),
+ ]
: [expectedError(ParserErrorCode.VAR_CLASS, 0, 3)]);
}
void test_varEnum() {
parseCompilationUnit("var enum E {ONE}",
errors: usingFastaParser
- ? [expectedError(ParserErrorCode.EXTRANEOUS_MODIFIER, 0, 3)]
+ ? [
+ // Fasta interprets the `var` as a malformed top level var
+ // and `enum` as the start of an enum declaration.
+ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 4, 4),
+ expectedError(ParserErrorCode.EXPECTED_TOKEN, 4, 4),
+ ]
: [expectedError(ParserErrorCode.VAR_ENUM, 0, 3)]);
}
@@ -5724,7 +5762,12 @@
void test_varTypedef() {
parseCompilationUnit("var typedef F();",
errors: usingFastaParser
- ? [expectedError(ParserErrorCode.EXTRANEOUS_MODIFIER, 0, 3)]
+ ? [
+ // Fasta interprets the `var` as a malformed top level var
+ // and `typedef` as the start of an typedef declaration.
+ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 4, 7),
+ expectedError(ParserErrorCode.EXPECTED_TOKEN, 4, 7),
+ ]
: [expectedError(ParserErrorCode.VAR_TYPEDEF, 0, 3)]);
}
@@ -11056,17 +11099,10 @@
}
void test_incomplete_topLevelVariable_const() {
- CompilationUnit unit = parseCompilationUnit("const ",
- codes: usingFastaParser
- ? [
- ParserErrorCode.MISSING_IDENTIFIER,
- ParserErrorCode.EXPECTED_TOKEN,
- CompileTimeErrorCode.CONST_NOT_INITIALIZED
- ]
- : [
- ParserErrorCode.MISSING_IDENTIFIER,
- ParserErrorCode.EXPECTED_TOKEN
- ]);
+ CompilationUnit unit = parseCompilationUnit("const ", codes: [
+ ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.EXPECTED_TOKEN
+ ]);
NodeList<CompilationUnitMember> declarations = unit.declarations;
expect(declarations, hasLength(1));
CompilationUnitMember member = declarations[0];
@@ -11080,17 +11116,10 @@
}
void test_incomplete_topLevelVariable_final() {
- CompilationUnit unit = parseCompilationUnit("final ",
- codes: usingFastaParser
- ? [
- ParserErrorCode.MISSING_IDENTIFIER,
- ParserErrorCode.EXPECTED_TOKEN,
- StaticWarningCode.FINAL_NOT_INITIALIZED
- ]
- : [
- ParserErrorCode.MISSING_IDENTIFIER,
- ParserErrorCode.EXPECTED_TOKEN
- ]);
+ CompilationUnit unit = parseCompilationUnit("final ", codes: [
+ ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.EXPECTED_TOKEN
+ ]);
NodeList<CompilationUnitMember> declarations = unit.declarations;
expect(declarations, hasLength(1));
CompilationUnitMember member = declarations[0];
@@ -11124,17 +11153,10 @@
CompilationUnit unit = parseCompilationUnit(r'''
class C {
const
-}''',
- codes: usingFastaParser
- ? [
- ParserErrorCode.MISSING_IDENTIFIER,
- ParserErrorCode.EXPECTED_TOKEN,
- CompileTimeErrorCode.CONST_NOT_INITIALIZED
- ]
- : [
- ParserErrorCode.MISSING_IDENTIFIER,
- ParserErrorCode.EXPECTED_TOKEN
- ]);
+}''', codes: [
+ ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.EXPECTED_TOKEN
+ ]);
NodeList<CompilationUnitMember> declarations = unit.declarations;
expect(declarations, hasLength(1));
CompilationUnitMember unitMember = declarations[0];
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/field_declaration_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/field_declaration_test.dart
index d3a1af6..a4ddf6b 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/field_declaration_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/field_declaration_test.dart
@@ -31,11 +31,7 @@
new TestDescriptor(
'const_noName',
'const',
- [
- ParserErrorCode.MISSING_IDENTIFIER,
- ParserErrorCode.EXPECTED_TOKEN,
- CompileTimeErrorCode.CONST_NOT_INITIALIZED
- ],
+ [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
'const _s_;',
failing: allExceptEof,
expectedErrorsInValidCode: [
@@ -166,11 +162,7 @@
new TestDescriptor(
'static_const_noName',
'static const',
- [
- ParserErrorCode.MISSING_IDENTIFIER,
- ParserErrorCode.EXPECTED_TOKEN,
- CompileTimeErrorCode.CONST_NOT_INITIALIZED
- ],
+ [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
'static const _s_;',
failing: allExceptEof,
expectedErrorsInValidCode: [
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/top_level_variable_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/top_level_variable_test.dart
index 1d27bed..bb3264c 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/top_level_variable_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/top_level_variable_test.dart
@@ -33,10 +33,15 @@
[
ParserErrorCode.MISSING_IDENTIFIER,
ParserErrorCode.EXPECTED_TOKEN,
- CompileTimeErrorCode.CONST_NOT_INITIALIZED
],
"const _s_;",
- failing: allExceptEof,
+ failing: [
+ 'class',
+ 'functionVoid',
+ 'functionNonVoid',
+ 'getter',
+ 'setter'
+ ],
expectedErrorsInValidCode: [
CompileTimeErrorCode.CONST_NOT_INITIALIZED
],
@@ -49,7 +54,7 @@
CompileTimeErrorCode.CONST_NOT_INITIALIZED
],
"const a;",
- failing: ['typedef', 'functionNonVoid', 'getter', 'setter'],
+ failing: ['functionNonVoid', 'getter', 'setter'],
expectedErrorsInValidCode: [
CompileTimeErrorCode.CONST_NOT_INITIALIZED
],
@@ -72,11 +77,10 @@
[
ParserErrorCode.MISSING_IDENTIFIER,
ParserErrorCode.EXPECTED_TOKEN,
- CompileTimeErrorCode.CONST_NOT_INITIALIZED,
CompileTimeErrorCode.CONST_NOT_INITIALIZED
],
"const a, _s_;",
- failing: ['typedef', 'functionNonVoid', 'getter', 'setter'],
+ failing: ['functionNonVoid', 'getter'],
expectedErrorsInValidCode: [
CompileTimeErrorCode.CONST_NOT_INITIALIZED,
CompileTimeErrorCode.CONST_NOT_INITIALIZED
@@ -88,11 +92,10 @@
[
ParserErrorCode.MISSING_IDENTIFIER,
ParserErrorCode.EXPECTED_TOKEN,
- CompileTimeErrorCode.CONST_NOT_INITIALIZED,
CompileTimeErrorCode.CONST_NOT_INITIALIZED
],
"const int a, _s_;",
- failing: ['typedef', 'functionNonVoid', 'getter', 'setter'],
+ failing: ['functionNonVoid', 'getter'],
expectedErrorsInValidCode: [
CompileTimeErrorCode.CONST_NOT_INITIALIZED,
CompileTimeErrorCode.CONST_NOT_INITIALIZED
@@ -132,10 +135,15 @@
[
ParserErrorCode.MISSING_IDENTIFIER,
ParserErrorCode.EXPECTED_TOKEN,
- StaticWarningCode.FINAL_NOT_INITIALIZED
],
"final _s_;",
- failing: allExceptEof,
+ failing: [
+ 'class',
+ 'functionVoid',
+ 'functionNonVoid',
+ 'getter',
+ 'setter'
+ ],
expectedErrorsInValidCode: [
StaticWarningCode.FINAL_NOT_INITIALIZED
],
@@ -148,7 +156,7 @@
StaticWarningCode.FINAL_NOT_INITIALIZED
],
"final a;",
- failing: ['typedef', 'functionNonVoid', 'getter', 'setter'],
+ failing: ['functionNonVoid', 'getter', 'setter'],
expectedErrorsInValidCode: [
StaticWarningCode.FINAL_NOT_INITIALIZED
],
@@ -189,14 +197,14 @@
ParserErrorCode.EXPECTED_TOKEN
],
"var _s_;",
- failing: allExceptEof,
+ failing: ['functionVoid', 'functionNonVoid', 'getter', 'setter'],
),
new TestDescriptor(
'varName',
'var a',
[ParserErrorCode.EXPECTED_TOKEN],
"var a;",
- failing: ['typedef', 'functionNonVoid', 'getter', 'setter'],
+ failing: ['functionNonVoid', 'getter', 'setter'],
),
new TestDescriptor(
'varNameEquals',
diff --git a/pkg/front_end/lib/src/fasta/parser/identifier_context.dart b/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
index ba67f33..b9bbf76 100644
--- a/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
+++ b/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
@@ -120,9 +120,8 @@
const TypeReferenceIdentifierContext.continuation();
/// Identifier is a name being declared by a top level variable declaration.
- static const topLevelVariableDeclaration = const IdentifierContext(
- 'topLevelVariableDeclaration',
- inDeclaration: true);
+ static const topLevelVariableDeclaration =
+ const TopLevelVariableIdentifierContext();
/// Identifier is a name being declared by a field declaration.
static const fieldDeclaration = const FieldDeclarationIdentifierContext();
diff --git a/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart b/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart
index 37aedf8..36baa41 100644
--- a/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart
+++ b/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart
@@ -311,10 +311,12 @@
Token ensureIdentifier(Token token, Parser parser) {
Token identifier = token.next;
assert(identifier.kind != IDENTIFIER_TOKEN);
+ const followingValues = const ['.', ';'];
+
if (identifier.isIdentifier) {
Token next = identifier.next;
- if (isOneOfOrEof(next, const ['.', ';']) ||
- !looksLikeStartOfNextTopLevelDeclaration(identifier)) {
+ if (!looksLikeStartOfNextTopLevelDeclaration(identifier) ||
+ isOneOfOrEof(next, followingValues)) {
return identifier;
}
// Although this is a valid library name, the library declaration
@@ -323,7 +325,7 @@
}
// Recovery
- if (isOneOfOrEof(identifier, const ['.', ';']) ||
+ if (isOneOfOrEof(identifier, followingValues) ||
looksLikeStartOfNextTopLevelDeclaration(identifier)) {
identifier = parser.insertSyntheticIdentifier(token, this,
message: fasta.templateExpectedIdentifier.withArguments(identifier));
@@ -411,6 +413,49 @@
}
}
+/// See [IdentifierContext.topLevelVariableDeclaration].
+class TopLevelVariableIdentifierContext extends IdentifierContext {
+ const TopLevelVariableIdentifierContext()
+ : super('topLevelVariableDeclaration', inDeclaration: true);
+
+ @override
+ Token ensureIdentifier(Token token, Parser parser) {
+ Token identifier = token.next;
+ assert(identifier.kind != IDENTIFIER_TOKEN);
+ const followingValues = const [';', '=', ','];
+
+ if (identifier.isIdentifier) {
+ Token next = identifier.next;
+ if (!looksLikeStartOfNextTopLevelDeclaration(identifier) ||
+ isOneOfOrEof(next, followingValues)) {
+ return identifier;
+ }
+ // Although this is a valid top level var name, the var declaration
+ // is invalid and this looks like the start of the next declaration.
+ // In this situation, fall through to insert a synthetic var name.
+ }
+
+ // Recovery
+ if (looksLikeStartOfNextTopLevelDeclaration(identifier) ||
+ isOneOfOrEof(identifier, followingValues)) {
+ identifier = parser.insertSyntheticIdentifier(token, this,
+ message: fasta.templateExpectedIdentifier.withArguments(identifier));
+ } else if (identifier.type.isBuiltIn) {
+ parser.reportRecoverableErrorWithToken(
+ identifier, fasta.templateBuiltInIdentifierInDeclaration);
+ } else {
+ parser.reportRecoverableErrorWithToken(
+ identifier, fasta.templateExpectedIdentifier);
+ if (!identifier.isKeywordOrIdentifier) {
+ // When in doubt, consume the token to ensure we make progress
+ // but insert a synthetic identifier to satisfy listeners.
+ identifier = insertSyntheticIdentifierAfter(identifier, parser);
+ }
+ }
+ return identifier;
+ }
+}
+
/// See [IdentifierContext].typedefDeclaration
class TypedefDeclarationIdentifierContext extends IdentifierContext {
const TypedefDeclarationIdentifierContext()
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 72adcc5..cbeb2c5 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -332,9 +332,12 @@
listener.endTopLevelDeclaration(token.next);
count++;
if (start == token.next) {
+ // Recovery:
// If progress has not been made reaching the end of the token stream,
// then report an error and skip the current token.
token = token.next;
+ listener.beginMetadataStar(token);
+ listener.endMetadataStar(0);
reportRecoverableErrorWithToken(
token, fasta.templateExpectedDeclaration);
listener.handleInvalidTopLevelDeclaration(token);
@@ -443,15 +446,26 @@
return parseScript(token);
}
token = parseMetadataStar(token);
- if (token.next.isTopLevelKeyword) {
+ Token next = token.next;
+ if (next.isTopLevelKeyword) {
return parseTopLevelKeywordDeclaration(token, null, directiveState);
}
Token start = token;
// Skip modifiers to find a top level keyword or identifier
- while (token.next.isModifier) {
- token = token.next;
+ if (next.isModifier) {
+ if (optional('var', next) ||
+ ((optional('const', next) || optional('final', next)) &&
+ // Ignore `const class` and `final class` so that it is reported
+ // below as an invalid modifier on a class.
+ !optional('class', next.next))) {
+ directiveState?.checkDeclaration();
+ return parseTopLevelMemberImpl(token);
+ }
+ while (token.next.isModifier) {
+ token = token.next;
+ }
}
- Token next = token.next;
+ next = token.next;
if (next.isTopLevelKeyword) {
Token beforeAbstractToken;
Token beforeModifier = start;
@@ -1934,14 +1948,7 @@
token = next;
} else {
reportRecoverableErrorWithToken(next, context.recoveryTemplate);
- if (context == IdentifierContext.topLevelVariableDeclaration) {
- // Since the token is not a keyword or identifier, consume it to
- // ensure forward progress in parseField.
- token = next.next;
- // Supply a non-empty method name so that it does not accidentally
- // match the default constructor.
- token = insertSyntheticIdentifier(next, context);
- } else if (context == IdentifierContext.constructorReference) {
+ if (context == IdentifierContext.constructorReference) {
token = insertSyntheticIdentifier(token, context);
} else {
token = next;
@@ -2027,8 +2034,6 @@
followingValues = ['.', '(', '{', '=>'];
} else if (context == IdentifierContext.topLevelFunctionDeclaration) {
followingValues = ['(', '{', '=>'];
- } else if (context == IdentifierContext.topLevelVariableDeclaration) {
- followingValues = [';', '=', ','];
} else if (context == IdentifierContext.typeVariableDeclaration) {
followingValues = ['<', '>', ';', '}'];
} else {
@@ -2099,8 +2104,6 @@
initialKeywords = statementKeywords();
} else if (context == IdentifierContext.topLevelFunctionDeclaration) {
initialKeywords = topLevelKeywords();
- } else if (context == IdentifierContext.topLevelVariableDeclaration) {
- initialKeywords = topLevelKeywords();
} else if (context == IdentifierContext.typeVariableDeclaration) {
initialKeywords = topLevelKeywords()
..addAll(classMemberKeywords())
@@ -2521,14 +2524,23 @@
next = token.next;
}
if (isModifier(next)) {
- ModifierRecoveryContext context = new ModifierRecoveryContext(this);
- token = context.parseTopLevelModifiers(token,
- externalToken: externalToken, varFinalOrConst: varFinalOrConst);
- next = token.next;
+ // Recovery
+ if (varFinalOrConst != null &&
+ (optional('final', next) ||
+ optional('var', next) ||
+ optional('const', next))) {
+ // If another `var`, `final`, or `const` then fall through
+ // to parse that as part of the next top level declaration.
+ } else {
+ ModifierRecoveryContext context = new ModifierRecoveryContext(this);
+ token = context.parseTopLevelModifiers(token,
+ externalToken: externalToken, varFinalOrConst: varFinalOrConst);
+ next = token.next;
- externalToken = context.externalToken;
- varFinalOrConst = context.varFinalOrConst;
- context = null;
+ externalToken = context.externalToken;
+ varFinalOrConst = context.varFinalOrConst;
+ context = null;
+ }
}
}
}
@@ -2745,7 +2757,7 @@
token = parseExpression(next);
listener.endFieldInitializer(assignment, token.next);
} else {
- if (varFinalOrConst != null) {
+ if (varFinalOrConst != null && !name.isSynthetic) {
if (optional("const", varFinalOrConst)) {
reportRecoverableError(
name,
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 4120e05..376d485 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
@@ -8,6 +8,8 @@
import '../fasta_codes.dart' as fasta;
+import '../scanner/token_constants.dart' show IDENTIFIER_TOKEN;
+
import '../util/link.dart' show Link;
import 'identifier_context.dart' show IdentifierContext;
@@ -214,7 +216,13 @@
}
bool looksLikeName(Token token) =>
- token.isIdentifier || optional('this', token);
+ token.kind == IDENTIFIER_TOKEN ||
+ optional('this', token) ||
+ (token.isIdentifier &&
+ // Although `typedef` is a legal identifier,
+ // type `typedef` identifier is not legal and in this situation
+ // `typedef` is probably a separate declaration.
+ (!optional('typedef', token) || !token.next.isIdentifier));
Token skipTypeVariables(Token token) {
assert(optional('<', token));
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index a4feebd..f768b5d 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -579,13 +579,9 @@
- "external foo; main(){}"
- "final class C {}"
- "abstract enum foo {bar}"
- - "const enum foo {bar}"
- - "final enum foo {bar}"
- "abstract void foo() {}"
- "static void foo() {}"
- "abstract typedef foo();"
- - "const typedef foo();"
- - "final typedef foo();"
- "static typedef foo();"
FinalAndCovariant: