Skip to content

Commit 452dcdf

Browse files
stereotype441Commit Queue
authored and
Commit Queue
committed
Fix parsing of PATTERN as T? when
The logic for parsing types has special disambiguation rules for deciding whether a trailing `?` should be included in the type, based on what token(s) follow the `?`. In the case where the token that follows the `?` is `when`, we need to look further ahead to disambiguate, to distinguish `PATTERN as T? when guard` from something like `EXPRESSION is T ? when : otherwise`. (Note: an alternative implementation would be to disambiguate based on whether we're parsing a pattern or an expression. But in the future I want to move toward an architecture where expression parsing and pattern parsing are combined, so that if the parser makes the wrong decision about whether it's looking at a pattern or an expression, error recovery will do a better job. So I'm disambiguating based solely on what follows the `?`.) Bug: #50035 Change-Id: Idbc780b7b54fecc7fd01cae868c34771564dd804 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/292282 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Jens Johansen <[email protected]>
1 parent e6d352d commit 452dcdf

13 files changed

+564
-4
lines changed

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7578,11 +7578,15 @@ class Parser {
75787578
// rather than the start of a conditional expression.
75797579
return typeInfo;
75807580
}
7581-
if (optional('{', next)) {
7581+
if (optional('{', next) || optional('when', next)) {
75827582
// <expression> is/as <type> ? {
7583-
// This could be either a nullable type (e.g. last initializer in a
7584-
// constructor with a body), or a non-nullable type and a conditional.
7585-
// As with "?[" we check and have it as a conditional if it can be.
7583+
// This could be either a nullable type (e.g. last initializer in a
7584+
// constructor with a body), or a non-nullable type and a conditional.
7585+
// <expression> is/as <type> ? when
7586+
// This could be either a nullable type (e.g. a cast pattern followed
7587+
// by a guard), or a non-nullable type and a conditional (where the
7588+
// first token of the "then" expression is the identifier `when`).
7589+
// If it can be successfully parsed as a conditional, we do so.
75867590
bool isConditional = canParseAsConditional(skipToken);
75877591
if (!isConditional) {
75887592
return typeInfo;

pkg/analyzer/test/generated/patterns_parser_test.dart

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8296,6 +8296,73 @@ SwitchExpression
82968296
''');
82978297
}
82988298

8299+
test_typeQuestionBeforeWhen_conditional() {
8300+
// The logic for parsing types has special disambiguation rules for deciding
8301+
// whether a trailing `?` should be included in the type; these rules are
8302+
// based primarily on what token(s) follow the `?`. Make sure that these
8303+
// rules do the right thing if the token that follows the `?` is `when`, but
8304+
// the `when` is an ordinary identifier.
8305+
_parse('''
8306+
void f(condition, when, otherwise) => condition as bool ? when : otherwise;
8307+
''');
8308+
var node = findNode.functionDeclaration('=>').functionExpression.body;
8309+
assertParsedNodeText(node, r'''
8310+
ExpressionFunctionBody
8311+
functionDefinition: =>
8312+
expression: ConditionalExpression
8313+
condition: AsExpression
8314+
expression: SimpleIdentifier
8315+
token: condition
8316+
asOperator: as
8317+
type: NamedType
8318+
name: SimpleIdentifier
8319+
token: bool
8320+
question: ?
8321+
thenExpression: SimpleIdentifier
8322+
token: when
8323+
colon: :
8324+
elseExpression: SimpleIdentifier
8325+
token: otherwise
8326+
semicolon: ;
8327+
''');
8328+
}
8329+
8330+
test_typeQuestionBeforeWhen_guard() {
8331+
// The logic for parsing types has special disambiguation rules for deciding
8332+
// whether a trailing `?` should be included in the type; these rules are
8333+
// based primarily on what token(s) follow the `?`. Make sure that these
8334+
// rules do the right thing if the token that follows the `?` is the `when`
8335+
// of a pattern guard.
8336+
_parse('''
8337+
void f(x) {
8338+
switch (x) {
8339+
case _ as int? when x == null:
8340+
break;
8341+
}
8342+
}
8343+
''');
8344+
var node = findNode.singleGuardedPattern;
8345+
assertParsedNodeText(node, r'''
8346+
GuardedPattern
8347+
pattern: CastPattern
8348+
pattern: WildcardPattern
8349+
name: _
8350+
asToken: as
8351+
type: NamedType
8352+
name: SimpleIdentifier
8353+
token: int
8354+
question: ?
8355+
whenClause: WhenClause
8356+
whenKeyword: when
8357+
expression: BinaryExpression
8358+
leftOperand: SimpleIdentifier
8359+
token: x
8360+
operator: ==
8361+
rightOperand: NullLiteral
8362+
literal: null
8363+
''');
8364+
}
8365+
82998366
test_variable_bare_insideCast() {
83008367
_parse('''
83018368
void f(x) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
void f(condition, when, otherwise) => condition as bool ? when : otherwise;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
beginCompilationUnit(void)
2+
beginMetadataStar(void)
3+
endMetadataStar(0)
4+
beginTopLevelMember(void)
5+
beginTopLevelMethod(, null, null)
6+
handleVoidKeyword(void)
7+
handleIdentifier(f, topLevelFunctionDeclaration)
8+
handleNoTypeVariables(()
9+
beginFormalParameters((, MemberKind.TopLevelMethod)
10+
beginMetadataStar(condition)
11+
endMetadataStar(0)
12+
beginFormalParameter(condition, MemberKind.TopLevelMethod, null, null, null)
13+
handleNoType(()
14+
handleIdentifier(condition, formalParameterDeclaration)
15+
handleFormalParameterWithoutValue(,)
16+
endFormalParameter(null, null, null, condition, null, null, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
17+
beginMetadataStar(when)
18+
endMetadataStar(0)
19+
beginFormalParameter(when, MemberKind.TopLevelMethod, null, null, null)
20+
handleNoType(,)
21+
handleIdentifier(when, formalParameterDeclaration)
22+
handleFormalParameterWithoutValue(,)
23+
endFormalParameter(null, null, null, when, null, null, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
24+
beginMetadataStar(otherwise)
25+
endMetadataStar(0)
26+
beginFormalParameter(otherwise, MemberKind.TopLevelMethod, null, null, null)
27+
handleNoType(,)
28+
handleIdentifier(otherwise, formalParameterDeclaration)
29+
handleFormalParameterWithoutValue())
30+
endFormalParameter(null, null, null, otherwise, null, null, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
31+
endFormalParameters(3, (, ), MemberKind.TopLevelMethod)
32+
handleAsyncModifier(null, null)
33+
handleIdentifier(condition, expression)
34+
handleNoTypeArguments(as)
35+
handleNoArguments(as)
36+
handleSend(condition, as)
37+
beginAsOperatorType(as)
38+
handleIdentifier(bool, typeReference)
39+
handleNoTypeArguments(?)
40+
handleType(bool, null)
41+
endAsOperatorType(as)
42+
handleAsOperator(as)
43+
beginConditionalExpression(?)
44+
handleIdentifier(when, expression)
45+
handleNoTypeArguments(:)
46+
handleNoArguments(:)
47+
handleSend(when, :)
48+
handleConditionalExpressionColon()
49+
handleIdentifier(otherwise, expression)
50+
handleNoTypeArguments(;)
51+
handleNoArguments(;)
52+
handleSend(otherwise, ;)
53+
endConditionalExpression(?, :)
54+
handleExpressionFunctionBody(=>, ;)
55+
endTopLevelMethod(void, null, ;)
56+
endTopLevelDeclaration()
57+
endCompilationUnit(1, )
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
parseUnit(void)
2+
skipErrorTokens(void)
3+
listener: beginCompilationUnit(void)
4+
syntheticPreviousToken(void)
5+
parseTopLevelDeclarationImpl(, Instance of 'DirectiveContext')
6+
parseMetadataStar()
7+
listener: beginMetadataStar(void)
8+
listener: endMetadataStar(0)
9+
parseTopLevelMemberImpl()
10+
listener: beginTopLevelMember(void)
11+
parseTopLevelMethod(, null, null, , Instance of 'VoidType', null, f, false)
12+
listener: beginTopLevelMethod(, null, null)
13+
listener: handleVoidKeyword(void)
14+
ensureIdentifierPotentiallyRecovered(void, topLevelFunctionDeclaration, false)
15+
listener: handleIdentifier(f, topLevelFunctionDeclaration)
16+
parseMethodTypeVar(f)
17+
listener: handleNoTypeVariables(()
18+
parseGetterOrFormalParameters(f, f, false, MemberKind.TopLevelMethod)
19+
parseFormalParameters(f, MemberKind.TopLevelMethod)
20+
parseFormalParametersRest((, MemberKind.TopLevelMethod)
21+
listener: beginFormalParameters((, MemberKind.TopLevelMethod)
22+
parseFormalParameter((, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
23+
parseMetadataStar(()
24+
listener: beginMetadataStar(condition)
25+
listener: endMetadataStar(0)
26+
listener: beginFormalParameter(condition, MemberKind.TopLevelMethod, null, null, null)
27+
listener: handleNoType(()
28+
ensureIdentifier((, formalParameterDeclaration)
29+
listener: handleIdentifier(condition, formalParameterDeclaration)
30+
listener: handleFormalParameterWithoutValue(,)
31+
listener: endFormalParameter(null, null, null, condition, null, null, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
32+
parseFormalParameter(,, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
33+
parseMetadataStar(,)
34+
listener: beginMetadataStar(when)
35+
listener: endMetadataStar(0)
36+
listener: beginFormalParameter(when, MemberKind.TopLevelMethod, null, null, null)
37+
listener: handleNoType(,)
38+
ensureIdentifier(,, formalParameterDeclaration)
39+
inPlainSync()
40+
listener: handleIdentifier(when, formalParameterDeclaration)
41+
listener: handleFormalParameterWithoutValue(,)
42+
listener: endFormalParameter(null, null, null, when, null, null, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
43+
parseFormalParameter(,, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
44+
parseMetadataStar(,)
45+
listener: beginMetadataStar(otherwise)
46+
listener: endMetadataStar(0)
47+
listener: beginFormalParameter(otherwise, MemberKind.TopLevelMethod, null, null, null)
48+
listener: handleNoType(,)
49+
ensureIdentifier(,, formalParameterDeclaration)
50+
listener: handleIdentifier(otherwise, formalParameterDeclaration)
51+
listener: handleFormalParameterWithoutValue())
52+
listener: endFormalParameter(null, null, null, otherwise, null, null, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
53+
listener: endFormalParameters(3, (, ), MemberKind.TopLevelMethod)
54+
parseAsyncModifierOpt())
55+
listener: handleAsyncModifier(null, null)
56+
inPlainSync()
57+
parseFunctionBody(), false, false)
58+
parseExpressionFunctionBody(=>, false)
59+
parseExpression(=>)
60+
looksLikeOuterPatternEquals(=>)
61+
skipOuterPattern(=>)
62+
skipObjectPatternRest(condition)
63+
parsePrecedenceExpression(=>, 1, true, ConstantPatternContext.none)
64+
parseUnaryExpression(=>, true, ConstantPatternContext.none)
65+
parsePrimary(=>, expression, ConstantPatternContext.none)
66+
parseSendOrFunctionLiteral(=>, expression, ConstantPatternContext.none)
67+
parseSend(=>, expression, ConstantPatternContext.none)
68+
isNextIdentifier(=>)
69+
ensureIdentifier(=>, expression)
70+
listener: handleIdentifier(condition, expression)
71+
listener: handleNoTypeArguments(as)
72+
parseArgumentsOpt(condition)
73+
listener: handleNoArguments(as)
74+
listener: handleSend(condition, as)
75+
parseAsOperatorRest(condition)
76+
listener: beginAsOperatorType(as)
77+
computeTypeAfterIsOrAs(as)
78+
canParseAsConditional(?)
79+
parseExpressionWithoutCascade(?)
80+
parsePrecedenceExpression(?, 1, false, ConstantPatternContext.none)
81+
parseUnaryExpression(?, false, ConstantPatternContext.none)
82+
parsePrimary(?, expression, ConstantPatternContext.none)
83+
inPlainSync()
84+
parseSendOrFunctionLiteral(?, expression, ConstantPatternContext.none)
85+
parseSend(?, expression, ConstantPatternContext.none)
86+
isNextIdentifier(?)
87+
ensureIdentifier(?, expression)
88+
inPlainSync()
89+
parseArgumentsOpt(when)
90+
parseExpressionWithoutCascade(:)
91+
parsePrecedenceExpression(:, 1, false, ConstantPatternContext.none)
92+
parseUnaryExpression(:, false, ConstantPatternContext.none)
93+
parsePrimary(:, expression, ConstantPatternContext.none)
94+
parseSendOrFunctionLiteral(:, expression, ConstantPatternContext.none)
95+
parseSend(:, expression, ConstantPatternContext.none)
96+
isNextIdentifier(:)
97+
ensureIdentifier(:, expression)
98+
parseArgumentsOpt(otherwise)
99+
listener: handleIdentifier(bool, typeReference)
100+
listener: handleNoTypeArguments(?)
101+
listener: handleType(bool, null)
102+
listener: endAsOperatorType(as)
103+
listener: handleAsOperator(as)
104+
skipChainedAsIsOperators(bool)
105+
parseConditionalExpressionRest(bool)
106+
listener: beginConditionalExpression(?)
107+
parseExpressionWithoutCascade(?)
108+
parsePrecedenceExpression(?, 1, false, ConstantPatternContext.none)
109+
parseUnaryExpression(?, false, ConstantPatternContext.none)
110+
parsePrimary(?, expression, ConstantPatternContext.none)
111+
inPlainSync()
112+
parseSendOrFunctionLiteral(?, expression, ConstantPatternContext.none)
113+
parseSend(?, expression, ConstantPatternContext.none)
114+
isNextIdentifier(?)
115+
ensureIdentifier(?, expression)
116+
inPlainSync()
117+
listener: handleIdentifier(when, expression)
118+
listener: handleNoTypeArguments(:)
119+
parseArgumentsOpt(when)
120+
listener: handleNoArguments(:)
121+
listener: handleSend(when, :)
122+
ensureColon(when)
123+
listener: handleConditionalExpressionColon()
124+
parseExpressionWithoutCascade(:)
125+
parsePrecedenceExpression(:, 1, false, ConstantPatternContext.none)
126+
parseUnaryExpression(:, false, ConstantPatternContext.none)
127+
parsePrimary(:, expression, ConstantPatternContext.none)
128+
parseSendOrFunctionLiteral(:, expression, ConstantPatternContext.none)
129+
parseSend(:, expression, ConstantPatternContext.none)
130+
isNextIdentifier(:)
131+
ensureIdentifier(:, expression)
132+
listener: handleIdentifier(otherwise, expression)
133+
listener: handleNoTypeArguments(;)
134+
parseArgumentsOpt(otherwise)
135+
listener: handleNoArguments(;)
136+
listener: handleSend(otherwise, ;)
137+
listener: endConditionalExpression(?, :)
138+
ensureSemicolon(otherwise)
139+
listener: handleExpressionFunctionBody(=>, ;)
140+
inGenerator()
141+
listener: endTopLevelMethod(void, null, ;)
142+
listener: endTopLevelDeclaration()
143+
reportAllErrorTokens(void)
144+
listener: endCompilationUnit(1, )
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
void f(condition, when, otherwise) => condition as bool ? when : otherwise;
2+
3+
4+
void[KeywordToken] f[StringToken]([BeginToken]condition[StringToken],[SimpleToken] when[KeywordToken],[SimpleToken] otherwise[StringToken])[SimpleToken] =>[SimpleToken] condition[StringToken] as[KeywordToken] bool[StringToken] ?[SimpleToken] when[KeywordToken] :[SimpleToken] otherwise[StringToken];[SimpleToken]
5+
[SimpleToken]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
void f(condition, when, otherwise) => condition as bool ? when : otherwise;
2+
3+
4+
void[KeywordToken] f[StringToken]([BeginToken]condition[StringToken],[SimpleToken] when[KeywordToken],[SimpleToken] otherwise[StringToken])[SimpleToken] =>[SimpleToken] condition[StringToken] as[KeywordToken] bool[StringToken] ?[SimpleToken] when[KeywordToken] :[SimpleToken] otherwise[StringToken];[SimpleToken]
5+
[SimpleToken]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
void f(x) {
2+
switch (x) {
3+
case _ as int? when x == null:
4+
break;
5+
}
6+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
beginCompilationUnit(void)
2+
beginMetadataStar(void)
3+
endMetadataStar(0)
4+
beginTopLevelMember(void)
5+
beginTopLevelMethod(, null, null)
6+
handleVoidKeyword(void)
7+
handleIdentifier(f, topLevelFunctionDeclaration)
8+
handleNoTypeVariables(()
9+
beginFormalParameters((, MemberKind.TopLevelMethod)
10+
beginMetadataStar(x)
11+
endMetadataStar(0)
12+
beginFormalParameter(x, MemberKind.TopLevelMethod, null, null, null)
13+
handleNoType(()
14+
handleIdentifier(x, formalParameterDeclaration)
15+
handleFormalParameterWithoutValue())
16+
endFormalParameter(null, null, null, x, null, null, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
17+
endFormalParameters(1, (, ), MemberKind.TopLevelMethod)
18+
handleAsyncModifier(null, null)
19+
beginBlockFunctionBody({)
20+
beginSwitchStatement(switch)
21+
handleIdentifier(x, expression)
22+
handleNoTypeArguments())
23+
handleNoArguments())
24+
handleSend(x, ))
25+
handleParenthesizedCondition((, null, null)
26+
beginSwitchBlock({)
27+
beginCaseExpression(case)
28+
handleNoType(_)
29+
handleWildcardPattern(null, _)
30+
beginAsOperatorType(as)
31+
handleIdentifier(int, typeReference)
32+
handleNoTypeArguments(?)
33+
handleType(int, ?)
34+
endAsOperatorType(as)
35+
handleCastPattern(as)
36+
beginSwitchCaseWhenClause(when)
37+
handleIdentifier(x, expression)
38+
handleNoTypeArguments(==)
39+
handleNoArguments(==)
40+
handleSend(x, ==)
41+
beginBinaryExpression(==)
42+
handleLiteralNull(null)
43+
endBinaryExpression(==)
44+
endSwitchCaseWhenClause(null)
45+
endCaseExpression(case, when, :)
46+
beginSwitchCase(0, 1, case)
47+
handleBreakStatement(false, break, ;)
48+
endSwitchCase(0, 1, null, null, 1, case, })
49+
endSwitchBlock(1, {, })
50+
endSwitchStatement(switch, })
51+
endBlockFunctionBody(1, {, })
52+
endTopLevelMethod(void, null, })
53+
endTopLevelDeclaration()
54+
endCompilationUnit(1, )

0 commit comments

Comments
 (0)