Skip to content

Commit 514f678

Browse files
jensjohaCommit Bot
authored and
Commit Bot
committed
[parser] Better recovery for extra stuff in enum and using enum as identifier
Fixes #48371 Change-Id: I93331485c884c89c179c2fa332f3f975db9507f7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/234043 Reviewed-by: Johnni Winther <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Jens Johansen <[email protected]>
1 parent c16d5f0 commit 514f678

35 files changed

+3239
-7
lines changed

pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7476,6 +7476,37 @@ const MessageCode messageMoreThanOneSuperInitializer = const MessageCode(
74767476
analyzerCodes: <String>["MULTIPLE_SUPER_INITIALIZERS"],
74777477
problemMessage: r"""Can't have more than one 'super' initializer.""");
74787478

7479+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
7480+
const Template<
7481+
Message Function(
7482+
String string,
7483+
String
7484+
string2)> templateMultipleClauses = const Template<
7485+
Message Function(String string, String string2)>(
7486+
problemMessageTemplate:
7487+
r"""Each '#string' definition can have at most one '#string2' clause.""",
7488+
correctionMessageTemplate:
7489+
r"""Try combining all of the '#string2' clauses into a single clause.""",
7490+
withArguments: _withArgumentsMultipleClauses);
7491+
7492+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
7493+
const Code<Message Function(String string, String string2)>
7494+
codeMultipleClauses =
7495+
const Code<Message Function(String string, String string2)>(
7496+
"MultipleClauses",
7497+
index: 121);
7498+
7499+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
7500+
Message _withArgumentsMultipleClauses(String string, String string2) {
7501+
if (string.isEmpty) throw 'No string provided';
7502+
if (string2.isEmpty) throw 'No string provided';
7503+
return new Message(codeMultipleClauses,
7504+
problemMessage:
7505+
"""Each '${string}' definition can have at most one '${string2}' clause.""",
7506+
correctionMessage: """Try combining all of the '${string2}' clauses into a single clause.""",
7507+
arguments: {'string': string, 'string2': string2});
7508+
}
7509+
74797510
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
74807511
const Code<Null> codeMultipleExtends = messageMultipleExtends;
74817512

@@ -8369,6 +8400,37 @@ const MessageCode messageOperatorWithTypeParameters = const MessageCode(
83698400
r"""Types parameters aren't allowed when defining an operator.""",
83708401
correctionMessage: r"""Try removing the type parameters.""");
83718402

8403+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
8404+
const Template<
8405+
Message Function(
8406+
String string,
8407+
String
8408+
string2)> templateOutOfOrderClauses = const Template<
8409+
Message Function(String string, String string2)>(
8410+
problemMessageTemplate:
8411+
r"""The '#string' clause must come before the '#string2' clause.""",
8412+
correctionMessageTemplate:
8413+
r"""Try moving the '#string' clause before the '#string2' clause.""",
8414+
withArguments: _withArgumentsOutOfOrderClauses);
8415+
8416+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
8417+
const Code<Message Function(String string, String string2)>
8418+
codeOutOfOrderClauses =
8419+
const Code<Message Function(String string, String string2)>(
8420+
"OutOfOrderClauses",
8421+
index: 122);
8422+
8423+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
8424+
Message _withArgumentsOutOfOrderClauses(String string, String string2) {
8425+
if (string.isEmpty) throw 'No string provided';
8426+
if (string2.isEmpty) throw 'No string provided';
8427+
return new Message(codeOutOfOrderClauses,
8428+
problemMessage:
8429+
"""The '${string}' clause must come before the '${string2}' clause.""",
8430+
correctionMessage: """Try moving the '${string}' clause before the '${string2}' clause.""",
8431+
arguments: {'string': string, 'string2': string2});
8432+
}
8433+
83728434
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
83738435
const Template<Message Function(String name)> templateOverriddenMethodCause =
83748436
const Template<Message Function(String name)>(
@@ -10323,6 +10385,15 @@ Message _withArgumentsUnexpectedToken(Token token) {
1032310385
arguments: {'lexeme': token});
1032410386
}
1032510387

10388+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
10389+
const Code<Null> codeUnexpectedTokens = messageUnexpectedTokens;
10390+
10391+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
10392+
const MessageCode messageUnexpectedTokens = const MessageCode(
10393+
"UnexpectedTokens",
10394+
index: 123,
10395+
problemMessage: r"""Unexpected tokens.""");
10396+
1032610397
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
1032710398
const Template<Message Function(String string, Token token)>
1032810399
templateUnmatchedToken =

pkg/_fe_analyzer_shared/lib/src/parser/identifier_context_impl.dart

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,12 @@ class CombinatorIdentifierContext extends IdentifierContext {
124124
}
125125

126126
// Recovery
127-
if (isOneOfOrEof(identifier, followingValues) ||
128-
looksLikeStartOfNextTopLevelDeclaration(identifier)) {
127+
if (isOneOfOrEof(identifier, followingValues)) {
128+
identifier = parser.insertSyntheticIdentifier(token, this,
129+
message: codes.templateExpectedIdentifier.withArguments(identifier));
130+
} else if (looksLikeStartOfNextTopLevelDeclaration(identifier) &&
131+
(identifier.next == null ||
132+
!isOneOfOrEof(identifier.next!, followingValues))) {
129133
identifier = parser.insertSyntheticIdentifier(token, this,
130134
message: codes.templateExpectedIdentifier.withArguments(identifier));
131135
} else {
@@ -603,8 +607,12 @@ class ImportPrefixIdentifierContext extends IdentifierContext {
603607
isOneOfOrEof(identifier.next!, followingValues)) {
604608
parser.reportRecoverableErrorWithToken(
605609
identifier, codes.templateBuiltInIdentifierInDeclaration);
606-
} else if (looksLikeStartOfNextTopLevelDeclaration(identifier) ||
607-
isOneOfOrEof(identifier, followingValues)) {
610+
} else if (looksLikeStartOfNextTopLevelDeclaration(identifier) &&
611+
(identifier.next == null ||
612+
!isOneOfOrEof(identifier.next!, followingValues))) {
613+
identifier = parser.insertSyntheticIdentifier(token, this,
614+
message: codes.templateExpectedIdentifier.withArguments(identifier));
615+
} else if (isOneOfOrEof(identifier, followingValues)) {
608616
identifier = parser.insertSyntheticIdentifier(token, this,
609617
message: codes.templateExpectedIdentifier.withArguments(identifier));
610618
} else {
@@ -802,8 +810,12 @@ class LibraryIdentifierContext extends IdentifierContext {
802810
}
803811

804812
// Recovery
805-
if (isOneOfOrEof(identifier, followingValues) ||
806-
looksLikeStartOfNextTopLevelDeclaration(identifier)) {
813+
if (isOneOfOrEof(identifier, followingValues)) {
814+
identifier = parser.insertSyntheticIdentifier(token, this,
815+
message: codes.templateExpectedIdentifier.withArguments(identifier));
816+
} else if (looksLikeStartOfNextTopLevelDeclaration(identifier) &&
817+
(identifier.next == null ||
818+
!isOneOfOrEof(identifier.next!, followingValues))) {
807819
identifier = parser.insertSyntheticIdentifier(token, this,
808820
message: codes.templateExpectedIdentifier.withArguments(identifier));
809821
} else {

pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ import 'identifier_context.dart'
7676
looksLikeExpressionStart,
7777
okNextValueInFormalParameter;
7878

79+
import 'identifier_context_impl.dart'
80+
show looksLikeStartOfNextTopLevelDeclaration;
81+
7982
import 'listener.dart' show Listener;
8083

8184
import 'literal_entry_info.dart'
@@ -2005,11 +2008,140 @@ class Parser {
20052008
token = computeTypeParamOrArg(
20062009
token, /* inDeclaration = */ true, /* allowsVariance = */ true)
20072010
.parseVariables(token, this);
2011+
List<String> lookForNext = const ['{', 'with', 'implements'];
2012+
if (!isOneOf(token.next!, lookForNext)) {
2013+
// Recovery: Possible unexpected tokens before any clauses.
2014+
Token? skipToken = recoverySmallLookAheadSkipTokens(token, lookForNext);
2015+
if (skipToken != null) {
2016+
token = skipToken;
2017+
}
2018+
}
2019+
2020+
Token beforeWith = token;
20082021
token = parseEnumWithClauseOpt(token);
2022+
2023+
while (!isOneOf(token.next!, const ['{', 'implements'])) {
2024+
// Recovery: Skip unexpected tokens and more with clauses.
2025+
// Note that if we find a "with" we've seen one already (otherwise the
2026+
// parseEnumWithClauseOpt call above would have found this 'with').
2027+
Token? skipToken = recoveryEnumWith(token,
2028+
codes.templateMultipleClauses.withArguments("enum", "with")) ??
2029+
recoverySmallLookAheadSkipTokens(token, lookForNext);
2030+
2031+
if (skipToken != null) {
2032+
// Skipped tokens.
2033+
token = skipToken;
2034+
} else {
2035+
break;
2036+
}
2037+
}
2038+
20092039
token = parseClassOrMixinOrEnumImplementsOpt(token);
2040+
2041+
bool? hasWithClauses;
2042+
while (!optional('{', token.next!)) {
2043+
if (hasWithClauses == null) {
2044+
hasWithClauses = optional('with', beforeWith.next!);
2045+
}
2046+
2047+
// Recovery: Skip unexpected tokens and more with/implements clauses.
2048+
Token? skipToken = recoveryEnumWith(
2049+
token,
2050+
hasWithClauses
2051+
? codes.templateMultipleClauses.withArguments("enum", "with")
2052+
: codes.templateOutOfOrderClauses
2053+
.withArguments("with", "implements"));
2054+
if (skipToken != null) {
2055+
hasWithClauses = true;
2056+
}
2057+
if (skipToken == null) {
2058+
// Note that if we find a "implements" we've seen one already (otherwise
2059+
// the parseClassOrMixinOrEnumImplementsOpt call above would have found
2060+
// this 'implements').
2061+
skipToken = recoveryEnumImplements(token,
2062+
codes.templateMultipleClauses.withArguments("enum", "implements"));
2063+
}
2064+
if (skipToken == null) {
2065+
skipToken = recoverySmallLookAheadSkipTokens(token, lookForNext);
2066+
}
2067+
2068+
if (skipToken != null) {
2069+
// Skipped tokens.
2070+
token = skipToken;
2071+
} else {
2072+
break;
2073+
}
2074+
}
2075+
20102076
return token;
20112077
}
20122078

2079+
Token? recoveryEnumWith(Token token, codes.Message message) {
2080+
if (optional('with', token.next!)) {
2081+
reportRecoverableError(token.next!, message);
2082+
Listener originalListener = listener;
2083+
listener = new NullListener();
2084+
token = parseEnumWithClauseOpt(token);
2085+
listener = originalListener;
2086+
return token;
2087+
}
2088+
return null;
2089+
}
2090+
2091+
Token? recoveryEnumImplements(Token token, codes.Message message) {
2092+
if (optional('implements', token.next!)) {
2093+
reportRecoverableError(token.next!, message);
2094+
Listener originalListener = listener;
2095+
listener = new NullListener();
2096+
token = parseClassOrMixinOrEnumImplementsOpt(token);
2097+
listener = originalListener;
2098+
return token;
2099+
}
2100+
return null;
2101+
}
2102+
2103+
/// Allow a small lookahead (currently up to 3 tokens) trying to find any in
2104+
/// [lookFor].
2105+
///
2106+
/// If any wanted token is found an error is issued about unexpected tokens,
2107+
/// and the last skipped token is returned.
2108+
/// Otherwise null is returned.
2109+
Token? recoverySmallLookAheadSkipTokens(
2110+
final Token token, Iterable<String> lookFor) {
2111+
// Recovery: Allow a small lookahead for '{'. E.g. the user might be in
2112+
// the middle of writing 'with' or 'implements'.
2113+
Token skipToken = token.next!;
2114+
bool foundWanted = false;
2115+
2116+
if (looksLikeStartOfNextTopLevelDeclaration(skipToken)) return null;
2117+
2118+
int skipped = 0;
2119+
while (skipped < 3) {
2120+
skipped++;
2121+
if (isOneOf(skipToken.next!, lookFor)) {
2122+
foundWanted = true;
2123+
break;
2124+
}
2125+
2126+
skipToken = skipToken.next!;
2127+
if (looksLikeStartOfNextTopLevelDeclaration(skipToken)) return null;
2128+
}
2129+
2130+
if (foundWanted) {
2131+
// Give error and skip the tokens.
2132+
if (skipped == 1) {
2133+
reportRecoverableError(
2134+
skipToken, codes.templateUnexpectedToken.withArguments(skipToken));
2135+
} else {
2136+
reportRecoverableErrorWithEnd(
2137+
token.next!, skipToken, codes.messageUnexpectedTokens);
2138+
}
2139+
return skipToken;
2140+
}
2141+
2142+
return null;
2143+
}
2144+
20132145
Token parseEnumElement(Token token) {
20142146
Token beginToken = token;
20152147
token = parseMetadataStar(token);

pkg/analysis_server/test/services/completion/dart/location/enum_test.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ class EnumDeclarationTest2 extends AbstractCompletionDriverTest
2929
@override
3030
TestingCompletionProtocol get protocol => TestingCompletionProtocol.version2;
3131

32-
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/48371')
3332
Future<void> test_afterName_w() async {
3433
var response = await getTestCodeSuggestions('''
3534
enum E w^ {

pkg/analyzer/lib/error/error.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,7 @@ const List<ErrorCode> errorCodeValues = [
841841
ParserErrorCode.MIXED_PARAMETER_GROUPS,
842842
ParserErrorCode.MIXIN_DECLARES_CONSTRUCTOR,
843843
ParserErrorCode.MODIFIER_OUT_OF_ORDER,
844+
ParserErrorCode.MULTIPLE_CLAUSES,
844845
ParserErrorCode.MULTIPLE_EXTENDS_CLAUSES,
845846
ParserErrorCode.MULTIPLE_IMPLEMENTS_CLAUSES,
846847
ParserErrorCode.MULTIPLE_LIBRARY_DIRECTIVES,
@@ -864,6 +865,7 @@ const List<ErrorCode> errorCodeValues = [
864865
ParserErrorCode.NON_USER_DEFINABLE_OPERATOR,
865866
ParserErrorCode.NORMAL_BEFORE_OPTIONAL_PARAMETERS,
866867
ParserErrorCode.NULL_AWARE_CASCADE_OUT_OF_ORDER,
868+
ParserErrorCode.OUT_OF_ORDER_CLAUSES,
867869
ParserErrorCode.POSITIONAL_AFTER_NAMED_ARGUMENT,
868870
ParserErrorCode.POSITIONAL_PARAMETER_OUTSIDE_GROUP,
869871
ParserErrorCode.PREFIX_AFTER_COMBINATOR,
@@ -887,6 +889,7 @@ const List<ErrorCode> errorCodeValues = [
887889
ParserErrorCode.TYPEDEF_IN_CLASS,
888890
ParserErrorCode.UNEXPECTED_TERMINATOR_FOR_PARAMETER_GROUP,
889891
ParserErrorCode.UNEXPECTED_TOKEN,
892+
ParserErrorCode.UNEXPECTED_TOKENS,
890893
ParserErrorCode.VAR_AND_TYPE,
891894
ParserErrorCode.VAR_AS_TYPE_NAME,
892895
ParserErrorCode.VAR_CLASS,

pkg/analyzer/lib/src/dart/error/syntactic_errors.g.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ final fastaAnalyzerErrorCodes = <ErrorCode?>[
135135
ParserErrorCode.CONSTRUCTOR_WITH_TYPE_ARGUMENTS,
136136
ParserErrorCode.FUNCTION_TYPED_PARAMETER_VAR,
137137
ParserErrorCode.TYPE_PARAMETER_ON_OPERATOR,
138+
ParserErrorCode.MULTIPLE_CLAUSES,
139+
ParserErrorCode.OUT_OF_ORDER_CLAUSES,
140+
ParserErrorCode.UNEXPECTED_TOKENS,
138141
];
139142

140143
class ParserErrorCode extends ErrorCode {
@@ -1294,6 +1297,13 @@ class ParserErrorCode extends ErrorCode {
12941297
correctionMessage: "Try re-ordering the modifiers.",
12951298
);
12961299

1300+
static const ParserErrorCode MULTIPLE_CLAUSES = ParserErrorCode(
1301+
'MULTIPLE_CLAUSES',
1302+
"Each '{0}' definition can have at most one '{1}' clause.",
1303+
correctionMessage:
1304+
"Try combining all of the '{1}' clauses into a single clause.",
1305+
);
1306+
12971307
static const ParserErrorCode MULTIPLE_EXTENDS_CLAUSES = ParserErrorCode(
12981308
'MULTIPLE_EXTENDS_CLAUSES',
12991309
"Each class definition can have at most one extends clause.",
@@ -1468,6 +1478,12 @@ class ParserErrorCode extends ErrorCode {
14681478
"sequence.",
14691479
);
14701480

1481+
static const ParserErrorCode OUT_OF_ORDER_CLAUSES = ParserErrorCode(
1482+
'OUT_OF_ORDER_CLAUSES',
1483+
"The '{0}' clause must come before the '{1}' clause.",
1484+
correctionMessage: "Try moving the '{0}' clause before the '{1}' clause.",
1485+
);
1486+
14711487
static const ParserErrorCode POSITIONAL_AFTER_NAMED_ARGUMENT =
14721488
ParserErrorCode(
14731489
'POSITIONAL_AFTER_NAMED_ARGUMENT',
@@ -1636,6 +1652,11 @@ class ParserErrorCode extends ErrorCode {
16361652
correctionMessage: "Try removing the text.",
16371653
);
16381654

1655+
static const ParserErrorCode UNEXPECTED_TOKENS = ParserErrorCode(
1656+
'UNEXPECTED_TOKENS',
1657+
"Unexpected tokens.",
1658+
);
1659+
16391660
static const ParserErrorCode VAR_AND_TYPE = ParserErrorCode(
16401661
'VAR_AND_TYPE',
16411662
"Variables can't be declared using both 'var' and a type name.",

0 commit comments

Comments
 (0)