Improve formal parameter identifier recovery
Change-Id: I7bc6012295836a4455388c287da3cf15eed9ee5f
Reviewed-on: https://dart-review.googlesource.com/56300
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 56f90ba..7b529d3 100644
--- a/pkg/analyzer/test/generated/parser_test.dart
+++ b/pkg/analyzer/test/generated/parser_test.dart
@@ -4495,12 +4495,12 @@
// being reported in code that cannot actually be reached), but that hasn't
// been proven yet.
createParser('(int a, int b ;',
- expectedEndOffset: 0 /* ErrorToken at end of token stream */);
+ expectedEndOffset: 14 /* ErrorToken at end of token stream */);
FormalParameterList list = parser.parseFormalParameterList();
expectNotNullIfNoErrors(list);
if (usingFastaParser) {
- listener
- .assertErrors([expectedError(ParserErrorCode.EXPECTED_TOKEN, 14, 1)]);
+ // Fasta scanner reports missing `)` error
+ listener.assertNoErrors();
} else if (fe.Scanner.useFasta) {
listener.errors
.contains(expectedError(ParserErrorCode.EXPECTED_TOKEN, 14, 1));
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/method_declaration_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/method_declaration_test.dart
index a2c8624..aacff92 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/method_declaration_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/method_declaration_test.dart
@@ -12,6 +12,15 @@
class MethodTest extends PartialCodeTest {
buildAll() {
+ const allExceptEof = const [
+ 'field',
+ 'fieldConst',
+ 'fieldFinal',
+ 'methodNonVoid',
+ 'methodVoid',
+ 'getter',
+ 'setter'
+ ];
buildTests(
'method_declaration',
[
@@ -21,44 +30,52 @@
new TestDescriptor(
'noType_leftParen',
'm(',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'm();',
- allFailing: true,
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'm() {}',
+ failing: allExceptEof,
),
new TestDescriptor(
'noType_paramName',
'm(B',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'm(B);',
- allFailing: true,
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'm(B) {}',
+ failing: ['methodNonVoid', 'getter', 'setter'],
),
new TestDescriptor(
- 'noType_paramTypeAndName',
- 'm(B b',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'm(B b);',
- allFailing: true,
- ),
+ 'noType_paramTypeAndName',
+ 'm(B b',
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'm(B b) {}'),
new TestDescriptor(
'noType_paramAndComma',
'm(B b,',
- [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
- 'm(B b, _s_);',
- allFailing: true,
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'm(B b) {}',
+ failing: allExceptEof,
),
new TestDescriptor(
'noType_noParams',
'm()',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'm();',
- allFailing: true,
+ [ParserErrorCode.MISSING_FUNCTION_BODY],
+ 'm() {}',
),
new TestDescriptor(
'noType_params',
'm(b, c)',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'm(b, c);',
- allFailing: true,
+ [ParserErrorCode.MISSING_FUNCTION_BODY],
+ 'm(b, c) {}',
),
new TestDescriptor(
'noType_emptyOptional',
@@ -84,44 +101,53 @@
new TestDescriptor(
'type_leftParen',
'A m(',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'A m();',
- allFailing: true,
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'A m() {}',
+ failing: allExceptEof,
),
new TestDescriptor(
'type_paramName',
'A m(B',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'A m(B);',
- allFailing: true,
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'A m(B) {}',
+ failing: ['methodNonVoid', 'getter', 'setter'],
),
new TestDescriptor(
'type_paramTypeAndName',
'A m(B b',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'A m(B b);',
- allFailing: true,
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'A m(B b) {}',
),
new TestDescriptor(
'type_paramAndComma',
'A m(B b,',
- [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
- 'A m(B b, _s_);',
- allFailing: true,
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'A m(B b) {}',
+ failing: allExceptEof,
),
new TestDescriptor(
'type_noParams',
'A m()',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'A m();',
- allFailing: true,
+ [ParserErrorCode.MISSING_FUNCTION_BODY],
+ 'A m() {}',
),
new TestDescriptor(
'type_params',
'A m(b, c)',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'A m(b, c);',
- allFailing: true,
+ [ParserErrorCode.MISSING_FUNCTION_BODY],
+ 'A m(b, c) {}',
),
new TestDescriptor(
'type_emptyOptional',
@@ -147,50 +173,53 @@
new TestDescriptor(
'static_noType_leftParen',
'static m(',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'static m();',
- allFailing: true,
- expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'static m() {}',
+ failing: allExceptEof,
),
new TestDescriptor(
'static_noType_paramName',
'static m(B',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'static m(B);',
- allFailing: true,
- expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'static m(B) {}',
+ failing: ['methodNonVoid', 'getter', 'setter'],
),
new TestDescriptor(
'static_noType_paramTypeAndName',
'static m(B b',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'static m(B b);',
- allFailing: true,
- expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'static m(B b) {}',
),
new TestDescriptor(
'static_noType_paramAndComma',
'static m(B b,',
- [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
- 'static m(B b, _s_);',
- allFailing: true,
- expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'static m(B b) {}',
+ failing: allExceptEof,
),
new TestDescriptor(
'static_noType_noParams',
'static m()',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'static m();',
- allFailing: true,
- expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+ [ParserErrorCode.MISSING_FUNCTION_BODY],
+ 'static m() {}',
),
new TestDescriptor(
'static_noType_params',
'static m(b, c)',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'static m(b, c);',
- allFailing: true,
- expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+ [ParserErrorCode.MISSING_FUNCTION_BODY],
+ 'static m(b, c) {}',
),
new TestDescriptor(
'static_noType_emptyOptional',
@@ -216,50 +245,53 @@
new TestDescriptor(
'static_type_leftParen',
'static A m(',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'static A m();',
- allFailing: true,
- expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'static A m() {}',
+ failing: allExceptEof,
),
new TestDescriptor(
'static_type_paramName',
'static A m(B',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'static A m(B);',
- allFailing: true,
- expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'static A m(B) {}',
+ failing: ['methodNonVoid', 'getter', 'setter'],
),
new TestDescriptor(
'static_type_paramTypeAndName',
'static A m(B b',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'static A m(B b);',
- allFailing: true,
- expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'static A m(B b) {}',
),
new TestDescriptor(
'static_type_paramAndComma',
'static A m(B b,',
- [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
- 'static A m(B b, _s_);',
- allFailing: true,
- expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_FUNCTION_BODY
+ ],
+ 'static A m(B b) {}',
+ failing: allExceptEof,
),
new TestDescriptor(
'static_type_noParams',
'static A m()',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'static A m();',
- allFailing: true,
- expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+ [ParserErrorCode.MISSING_FUNCTION_BODY],
+ 'static A m() {}',
),
new TestDescriptor(
'static_type_params',
'static A m(b, c)',
- [ParserErrorCode.EXPECTED_TOKEN],
- 'static A m(b, c);',
- allFailing: true,
- expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+ [ParserErrorCode.MISSING_FUNCTION_BODY],
+ 'static A m(b, c) {}',
),
new TestDescriptor(
'static_type_emptyOptional',
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 a4075e1..e0fdaec 100644
--- a/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
+++ b/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
@@ -60,9 +60,8 @@
/// Identifier is a formal parameter being declared as part of a function,
/// method, or typedef declaration.
- static const formalParameterDeclaration = const IdentifierContext(
- 'formalParameterDeclaration',
- inDeclaration: true);
+ static const formalParameterDeclaration =
+ const FormalParameterDeclarationIdentifierContext();
/// Identifier is the start of a library name (e.g. `foo` in the directive
/// 'library foo;`).
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 2beb97c..18dd095 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
@@ -298,6 +298,39 @@
}
}
+/// See [IdentifierContext.formalParameterDeclaration].
+class FormalParameterDeclarationIdentifierContext extends IdentifierContext {
+ const FormalParameterDeclarationIdentifierContext()
+ : super('formalParameterDeclaration', inDeclaration: true);
+
+ @override
+ Token ensureIdentifier(Token token, Parser parser) {
+ Token identifier = token.next;
+ assert(identifier.kind != IDENTIFIER_TOKEN);
+ if (identifier.isIdentifier) {
+ return identifier;
+ }
+
+ // Recovery
+ const followingValues = const [':', '=', ',', '(', ')', '[', ']', '{', '}'];
+ if (looksLikeStartOfNextClassMember(identifier) ||
+ looksLikeStartOfNextStatement(identifier) ||
+ isOneOfOrEof(identifier, followingValues)) {
+ identifier = parser.insertSyntheticIdentifier(token, this,
+ message: fasta.templateExpectedIdentifier.withArguments(identifier));
+ } 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 = parser.rewriter.insertSyntheticIdentifier(identifier);
+ }
+ }
+ return identifier;
+ }
+}
+
/// See [IdentifierContext.importPrefixDeclaration].
class ImportPrefixIdentifierContext extends IdentifierContext {
const ImportPrefixIdentifierContext()
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index fc82a91..0264e50 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -1195,12 +1195,25 @@
}
token = parseFormalParameter(token, FormalParameterKind.mandatory, kind);
next = token.next;
- if (optional(',', next)) {
- token = next;
- continue;
+ if (!optional(',', next)) {
+ Token next = token.next;
+ if (optional(')', next)) {
+ token = next;
+ } else {
+ // Recovery
+ if (begin.endGroup.isSynthetic) {
+ // Scanner has already reported a missing `)` error,
+ // but placed the `)` in the wrong location, so move it.
+ token = rewriter.moveSynthetic(token, begin.endGroup);
+ } else {
+ reportRecoverableError(
+ next, fasta.templateExpectedButGot.withArguments(')'));
+ token = begin.endGroup;
+ }
+ }
+ break;
}
- token = ensureCloseParen(token, begin);
- break;
+ token = next;
}
assert(optional(')', token));
listener.endFormalParameters(parameterCount, begin, token, kind);
@@ -2013,8 +2026,6 @@
followingValues = [';'];
} else if (context == IdentifierContext.constructorReferenceContinuation) {
followingValues = ['.', ',', '(', ')', '[', ']', '}', ';'];
- } else if (context == IdentifierContext.formalParameterDeclaration) {
- followingValues = [':', '=', ',', '(', ')', '[', ']', '{', '}'];
} else if (context == IdentifierContext.labelDeclaration) {
followingValues = [':'];
} else if (context == IdentifierContext.literalSymbol ||
@@ -2041,8 +2052,6 @@
return false;
}
- List<String> classMemberKeywords() =>
- <String>['const', 'final', 'var', 'void'];
List<String> statementKeywords() => <String>[
'const',
'do',
@@ -2054,19 +2063,6 @@
'void',
'while'
];
- List<String> topLevelKeywords() => <String>[
- 'class',
- 'const',
- 'enum',
- 'export',
- 'final',
- 'import',
- 'library',
- 'part',
- 'typedef',
- 'var',
- 'void'
- ];
// TODO(brianwilkerson): At the moment, this test is entirely based on data
// that can be represented declaratively. If that proves to be sufficient,
@@ -2074,12 +2070,7 @@
// could create a method to test whether a given token matches one of the
// patterns.
List<String> initialKeywords;
- if (context == IdentifierContext.formalParameterDeclaration) {
- initialKeywords = topLevelKeywords()
- ..addAll(classMemberKeywords())
- ..addAll(statementKeywords())
- ..add('covariant');
- } else if (context == IdentifierContext.labelDeclaration) {
+ if (context == IdentifierContext.labelDeclaration) {
initialKeywords = statementKeywords();
} else if (context == IdentifierContext.localAccessorDeclaration) {
initialKeywords = statementKeywords();