Skip to content

Commit 817882c

Browse files
jensjohaCommit Queue
authored andcommitted
[parser] Recover missing identifier in new style typedef
Before, when parsing a new style typedef without an identifier (e.g. `typedef = whatnot`) it concluded that the token after the equal-sign wasn't an equal sign an that it thus had to be an old style typedef, followed by recovery there. This CL makes the recovery insert an identifier after `typedef` (what the recovery of the old style actually did too), and then parse it as a new style typedef. This causes the previous many errors to just be `Expected an identifier, but got '='.` It is furthermore verified that the parsing of such a case is ~the same as when having an indentifier. Fixes #56912 Change-Id: I5cde1f29839555b1d6027a7d040dc6f60ac614a5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392560 Reviewed-by: Johnni Winther <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Jens Johansen <[email protected]>
1 parent fb173b8 commit 817882c

26 files changed

+332
-77
lines changed

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

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,19 @@ class Parser {
721721
context.parseExtensionModifiers(modifierStart, keyword);
722722
return parseExtension(beginToken, context.augmentToken, keyword);
723723
}
724+
} else if (identical(value, 'typedef')) {
725+
// Having a method called typedef is ok, but we might also want to
726+
// recover.
727+
if (keyword.next!.endGroup?.next?.isA(TokenType.EQ) ?? false) {
728+
// Recovery:
729+
// `typedef` `<` [...] `>` `=`
730+
// This isn't a legal method name. Assume we're missing the name of
731+
// the typedef.
732+
ModifierContext context = new ModifierContext(this);
733+
context.parseTypedefModifiers(modifierStart, keyword);
734+
directiveState?.checkDeclaration();
735+
return parseTypedef(context.augmentToken, keyword);
736+
}
724737
}
725738
directiveState?.checkDeclaration();
726739
return parseTopLevelMemberImpl(modifierStart);
@@ -1385,14 +1398,44 @@ class Parser {
13851398
Token? equals;
13861399
TypeParamOrArgInfo typeParam =
13871400
computeTypeParamOrArg(next, /* inDeclaration = */ true);
1388-
if (typeInfo == noType && typeParam.skip(next).next!.isA(TokenType.EQ)) {
1401+
bool newStyle = false;
1402+
bool newStyleParseAsRecovered = false;
1403+
if (typeInfo == noType) {
1404+
Token skip = typeParam.skip(next);
1405+
if (skip.next!.isA(TokenType.EQ)) {
1406+
newStyle = true;
1407+
1408+
// Parse as recovered here to 'force' using it as an identifier as we've
1409+
// already established that the next token is the equal sign we're
1410+
// looking for.
1411+
newStyleParseAsRecovered = true;
1412+
} else if (skip.isA(TokenType.EQ)) {
1413+
// Recovery: `typedef =` insert missing identifier and parse as new
1414+
// style.
1415+
newStyle = true;
1416+
newStyleParseAsRecovered = false;
1417+
} else if (skip.isA(TokenType.LT)) {
1418+
if (skip.endGroup?.next?.isA(TokenType.EQ) ?? false) {
1419+
TypeParamOrArgInfo newTypeParam =
1420+
computeTypeParamOrArg(token, /* inDeclaration = */ true);
1421+
skip = newTypeParam.skip(token);
1422+
// This if shouldn't be necessary, but let's do it anyway.
1423+
if (skip.next!.isA(TokenType.EQ)) {
1424+
// Recovery: `typedef <whatever> =` insert missing identifier and
1425+
// parse as new style.
1426+
typeParam = newTypeParam;
1427+
newStyle = true;
1428+
newStyleParseAsRecovered = false;
1429+
}
1430+
}
1431+
}
1432+
}
1433+
if (newStyle) {
13891434
// New style typedef, e.g. typedef foo = void Function();".
1390-
1391-
// Parse as recovered here to 'force' using it as an identifier as we've
1392-
// already established that the next token is the equal sign we're looking
1393-
// for.
1394-
token = ensureIdentifierPotentiallyRecovered(token,
1395-
IdentifierContext.typedefDeclaration, /* isRecovered = */ true);
1435+
token = ensureIdentifierPotentiallyRecovered(
1436+
token,
1437+
IdentifierContext.typedefDeclaration,
1438+
/* isRecovered = */ newStyleParseAsRecovered);
13961439

13971440
token = typeParam.parseVariables(token, this);
13981441
next = token.next!;

pkg/analyzer/test/src/dart/parser/type_alias_test.dart

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,30 +61,19 @@ GenericTypeAlias
6161
typedef = int;
6262
''');
6363
parseResult.assertErrors([
64-
error(ParserErrorCode.EXPECTED_TOKEN, 0, 7),
6564
error(ParserErrorCode.MISSING_IDENTIFIER, 8, 1),
66-
error(ParserErrorCode.MISSING_TYPEDEF_PARAMETERS, 8, 1),
67-
error(ParserErrorCode.EXPECTED_EXECUTABLE, 8, 1),
68-
error(ParserErrorCode.MISSING_CONST_FINAL_VAR_OR_TYPE, 10, 3),
6965
]);
7066

7167
var node = parseResult.findNode.unit;
7268
assertParsedNodeText(node, r'''
7369
CompilationUnit
7470
declarations
75-
FunctionTypeAlias
71+
GenericTypeAlias
7672
typedefKeyword: typedef
7773
name: <empty> <synthetic>
78-
parameters: FormalParameterList
79-
leftParenthesis: ( <synthetic>
80-
rightParenthesis: ) <synthetic>
81-
semicolon: ; <synthetic>
82-
TopLevelVariableDeclaration
83-
variables: VariableDeclarationList
84-
variables
85-
VariableDeclaration
86-
name: int
87-
semicolon: ;
74+
equals: =
75+
type: NamedType
76+
name: int
8877
''');
8978
}
9079
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class TypedefTest extends PartialCodeTest {
4343
ParserErrorCode.EXPECTED_TOKEN
4444
],
4545
"typedef _s_ = _s_;",
46-
allFailing: true),
46+
failing: ['functionVoid', 'functionNonVoid', 'getter', 'mixin']),
4747
TestDescriptor(
4848
'equals',
4949
'typedef T =',

pkg/analyzer/test/src/summary/elements/type_alias_test.dart

Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6284,28 +6284,9 @@ library
62846284
<testLibraryFragment>
62856285
enclosingElement3: <null>
62866286
typeAliases
6287-
functionTypeAliasBased @8
6287+
@8
62886288
reference: <testLibraryFragment>::@typeAlias::0
6289-
aliasedType: dynamic Function()
6290-
aliasedElement: GenericFunctionTypeElement
6291-
returnType: dynamic
6292-
topLevelVariables
6293-
static int @10
6294-
reference: <testLibraryFragment>::@topLevelVariable::int
6295-
enclosingElement3: <testLibraryFragment>
6296-
type: dynamic
6297-
accessors
6298-
synthetic static get int @-1
6299-
reference: <testLibraryFragment>::@getter::int
6300-
enclosingElement3: <testLibraryFragment>
6301-
returnType: dynamic
6302-
synthetic static set int= @-1
6303-
reference: <testLibraryFragment>::@setter::int
6304-
enclosingElement3: <testLibraryFragment>
6305-
parameters
6306-
requiredPositional _int @-1
6307-
type: dynamic
6308-
returnType: void
6289+
aliasedType: int
63096290
----------------------------------------
63106291
library
63116292
reference: <testLibrary>
@@ -6316,42 +6297,10 @@ library
63166297
<null-name>
63176298
reference: <testLibraryFragment>::@typeAlias::0
63186299
element: <testLibraryFragment>::@typeAlias::0#element
6319-
topLevelVariables
6320-
int @10
6321-
reference: <testLibraryFragment>::@topLevelVariable::int
6322-
element: <testLibraryFragment>::@topLevelVariable::int#element
6323-
getter2: <testLibraryFragment>::@getter::int
6324-
setter2: <testLibraryFragment>::@setter::int
6325-
getters
6326-
get <null-name>
6327-
reference: <testLibraryFragment>::@getter::int
6328-
element: <testLibraryFragment>::@getter::int#element
6329-
setters
6330-
set <null-name>
6331-
reference: <testLibraryFragment>::@setter::int
6332-
element: <testLibraryFragment>::@setter::int#element
6333-
formalParameters
6334-
<null-name>
6335-
element: <testLibraryFragment>::@setter::int::@parameter::_int#element
63366300
typeAliases
63376301
<null-name>
63386302
firstFragment: <testLibraryFragment>::@typeAlias::0
6339-
aliasedType: dynamic Function()
6340-
topLevelVariables
6341-
int
6342-
firstFragment: <testLibraryFragment>::@topLevelVariable::int
6343-
type: dynamic
6344-
getter: <testLibraryFragment>::@getter::int#element
6345-
setter: <testLibraryFragment>::@setter::int#element
6346-
getters
6347-
synthetic static get int
6348-
firstFragment: <testLibraryFragment>::@getter::int
6349-
setters
6350-
synthetic static set int=
6351-
firstFragment: <testLibraryFragment>::@setter::int
6352-
formalParameters
6353-
requiredPositional _int
6354-
type: dynamic
6303+
aliasedType: int
63556304
''');
63566305
}
63576306

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
typedef = int;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Problems reported:
2+
3+
parser/error_recovery/issue_56912:1:9: Expected an identifier, but got '='.
4+
typedef = int;
5+
^
6+
7+
beginCompilationUnit(typedef)
8+
beginMetadataStar(typedef)
9+
endMetadataStar(0)
10+
beginUncategorizedTopLevelDeclaration(typedef)
11+
beginTypedef(typedef)
12+
handleRecoverableError(Message[ExpectedIdentifier, Expected an identifier, but got '='., Try inserting an identifier before '='., {lexeme: =}], =, =)
13+
handleIdentifier(, typedefDeclaration)
14+
handleNoTypeVariables(=)
15+
handleIdentifier(int, typeReference)
16+
handleNoTypeArguments(;)
17+
handleType(int, null)
18+
endTypedef(null, typedef, =, ;)
19+
endTopLevelDeclaration(;)
20+
endCompilationUnit(1, )
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
parseUnit(typedef)
2+
skipErrorTokens(typedef)
3+
listener: beginCompilationUnit(typedef)
4+
syntheticPreviousToken(typedef)
5+
parseTopLevelDeclarationImpl(, DirectiveContext(DirectiveState.Unknown))
6+
parseMetadataStar()
7+
listener: beginMetadataStar(typedef)
8+
listener: endMetadataStar(0)
9+
parseTopLevelKeywordDeclaration(typedef, , typedef, null, null, null, null, DirectiveContext(DirectiveState.Unknown))
10+
parseTypedef(null, typedef)
11+
listener: beginUncategorizedTopLevelDeclaration(typedef)
12+
listener: beginTypedef(typedef)
13+
ensureIdentifierPotentiallyRecovered(typedef, typedefDeclaration, false)
14+
insertSyntheticIdentifier(typedef, typedefDeclaration, message: Message[ExpectedIdentifier, Expected an identifier, but got '='., Try inserting an identifier before '='., {lexeme: =}], messageOnToken: null)
15+
reportRecoverableError(=, Message[ExpectedIdentifier, Expected an identifier, but got '='., Try inserting an identifier before '='., {lexeme: =}])
16+
listener: handleRecoverableError(Message[ExpectedIdentifier, Expected an identifier, but got '='., Try inserting an identifier before '='., {lexeme: =}], =, =)
17+
rewriter()
18+
listener: handleIdentifier(, typedefDeclaration)
19+
listener: handleNoTypeVariables(=)
20+
listener: handleIdentifier(int, typeReference)
21+
listener: handleNoTypeArguments(;)
22+
listener: handleType(int, null)
23+
ensureSemicolon(int)
24+
listener: endTypedef(null, typedef, =, ;)
25+
listener: endTopLevelDeclaration(;)
26+
reportAllErrorTokens(typedef)
27+
listener: endCompilationUnit(1, )
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
NOTICE: Stream was rewritten by parser!
2+
3+
typedef *synthetic*= int;
4+
5+
typedef[KeywordToken] [SyntheticStringToken]=[SimpleToken] int[StringToken];[SimpleToken][SimpleToken]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
typedef = int;
2+
3+
typedef[KeywordToken] =[SimpleToken] int[StringToken];[SimpleToken][SimpleToken]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
files:
2+
- issue_56912.dart
3+
- issue_56912_prime.dart
4+
filters:
5+
- ignoreListenerArguments
6+
ignored:
7+
- handleRecoverableError

0 commit comments

Comments
 (0)