Patterns parsing: fix error recovery for switch expression using :

If a user attempts to use `:` instead of `=>` in a switch expression,
we now generate an error at the location of each `:` token (and
otherwise interpret the switch expression in the way the user
intended).

Fixes #50930.

Bug: https://github.com/dart-lang/sdk/issues/50930
Change-Id: I3687fb464b6875424745e664b0d28346b3cd6375
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/292982
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry 2023-04-03 21:28:41 +00:00 committed by Commit Queue
parent 40973edbb5
commit d78fe25542
7 changed files with 261 additions and 1 deletions

View file

@ -10325,7 +10325,16 @@ class Parser {
when = token = next;
token = parseExpression(token);
}
Token arrow = token = ensureFunctionArrow(token);
Token arrow;
if (optional(':', next)) {
// User accidentally used `:` instead of `=>`
arrow = next;
reportRecoverableError(
arrow, codes.templateExpectedButGot.withArguments('=>'));
} else {
arrow = ensureFunctionArrow(token);
}
token = arrow;
mayParseFunctionExpressions = true;
token = parseExpression(token);
mayParseFunctionExpressions = false;

View file

@ -9237,6 +9237,46 @@ SwitchExpression
''');
}
test_switchExpression_recovery_colonInsteadOfArrow() {
_parse('''
f(x) => switch (x) {
1: 'one',
2: 'two'
};
''', errors: [
error(ParserErrorCode.EXPECTED_TOKEN, 24, 1),
error(ParserErrorCode.EXPECTED_TOKEN, 36, 1),
]);
var node = findNode.switchExpression('switch');
assertParsedNodeText(node, r'''
SwitchExpression
switchKeyword: switch
leftParenthesis: (
expression: SimpleIdentifier
token: x
rightParenthesis: )
leftBracket: {
cases
SwitchExpressionCase
guardedPattern: GuardedPattern
pattern: ConstantPattern
expression: IntegerLiteral
literal: 1
arrow: :
expression: SimpleStringLiteral
literal: 'one'
SwitchExpressionCase
guardedPattern: GuardedPattern
pattern: ConstantPattern
expression: IntegerLiteral
literal: 2
arrow: :
expression: SimpleStringLiteral
literal: 'two'
rightBracket: }
''');
}
test_switchExpression_recovery_missingComma() {
// If the extra tokens after a switch case look like they could be a
// pattern, the parser assumes there's a missing comma.

View file

@ -0,0 +1,4 @@
f(x) => switch (x) {
1: 'one',
2: 'two'
};

View file

@ -0,0 +1,59 @@
Problems reported:
parser/patterns/switchExpression_recovery_colonInsteadOfArrow:2:4: Expected '=>' before this.
1: 'one',
^
parser/patterns/switchExpression_recovery_colonInsteadOfArrow:3:4: Expected '=>' before this.
2: 'two'
^
beginCompilationUnit(f)
beginMetadataStar(f)
endMetadataStar(0)
beginTopLevelMember(f)
beginTopLevelMethod(, null, null)
handleNoType()
handleIdentifier(f, topLevelFunctionDeclaration)
handleNoTypeVariables(()
beginFormalParameters((, MemberKind.TopLevelMethod)
beginMetadataStar(x)
endMetadataStar(0)
beginFormalParameter(x, MemberKind.TopLevelMethod, null, null, null)
handleNoType(()
handleIdentifier(x, formalParameterDeclaration)
handleFormalParameterWithoutValue())
endFormalParameter(null, null, null, x, null, null, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
endFormalParameters(1, (, ), MemberKind.TopLevelMethod)
handleAsyncModifier(null, null)
beginSwitchExpression(switch)
handleIdentifier(x, expression)
handleNoTypeArguments())
handleNoArguments())
handleSend(x, ))
handleParenthesizedCondition((, null, null)
beginSwitchExpressionBlock({)
beginSwitchExpressionCase()
beginConstantPattern(null)
handleLiteralInt(1)
endConstantPattern(null)
handleSwitchExpressionCasePattern(1)
handleRecoverableError(Message[ExpectedButGot, Expected '=>' before this., null, {string: =>}], :, :)
beginLiteralString('one')
endLiteralString(0, ,)
endSwitchExpressionCase(null, :, 'one')
beginSwitchExpressionCase()
beginConstantPattern(null)
handleLiteralInt(2)
endConstantPattern(null)
handleSwitchExpressionCasePattern(2)
handleRecoverableError(Message[ExpectedButGot, Expected '=>' before this., null, {string: =>}], :, :)
beginLiteralString('two')
endLiteralString(0, })
endSwitchExpressionCase(null, :, 'two')
endSwitchExpressionBlock(2, {, })
endSwitchExpression(switch, })
handleExpressionFunctionBody(=>, ;)
endTopLevelMethod(f, null, ;)
endTopLevelDeclaration()
endCompilationUnit(1, )

View file

@ -0,0 +1,126 @@
parseUnit(f)
skipErrorTokens(f)
listener: beginCompilationUnit(f)
syntheticPreviousToken(f)
parseTopLevelDeclarationImpl(, Instance of 'DirectiveContext')
parseMetadataStar()
listener: beginMetadataStar(f)
listener: endMetadataStar(0)
parseTopLevelMemberImpl()
listener: beginTopLevelMember(f)
isReservedKeyword(()
parseTopLevelMethod(, null, null, , Instance of 'NoType', null, f, false)
listener: beginTopLevelMethod(, null, null)
listener: handleNoType()
ensureIdentifierPotentiallyRecovered(, topLevelFunctionDeclaration, false)
listener: handleIdentifier(f, topLevelFunctionDeclaration)
parseMethodTypeVar(f)
listener: handleNoTypeVariables(()
parseGetterOrFormalParameters(f, f, false, MemberKind.TopLevelMethod)
parseFormalParameters(f, MemberKind.TopLevelMethod)
parseFormalParametersRest((, MemberKind.TopLevelMethod)
listener: beginFormalParameters((, MemberKind.TopLevelMethod)
parseFormalParameter((, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
parseMetadataStar(()
listener: beginMetadataStar(x)
listener: endMetadataStar(0)
listener: beginFormalParameter(x, MemberKind.TopLevelMethod, null, null, null)
listener: handleNoType(()
ensureIdentifier((, formalParameterDeclaration)
listener: handleIdentifier(x, formalParameterDeclaration)
listener: handleFormalParameterWithoutValue())
listener: endFormalParameter(null, null, null, x, null, null, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
listener: endFormalParameters(1, (, ), MemberKind.TopLevelMethod)
parseAsyncModifierOpt())
listener: handleAsyncModifier(null, null)
inPlainSync()
parseFunctionBody(), false, false)
parseExpressionFunctionBody(=>, false)
parseExpression(=>)
looksLikeOuterPatternEquals(=>)
skipOuterPattern(=>)
parsePrecedenceExpression(=>, 1, true, ConstantPatternContext.none)
parseUnaryExpression(=>, true, ConstantPatternContext.none)
parsePrimary(=>, expression, ConstantPatternContext.none)
inPlainSync()
parseSwitchExpression(=>)
listener: beginSwitchExpression(switch)
ensureParenthesizedCondition(switch, allowCase: false)
parseExpressionInParenthesisRest((, allowCase: false)
parseExpression(()
looksLikeOuterPatternEquals(()
skipOuterPattern(()
skipObjectPatternRest(x)
parsePrecedenceExpression((, 1, true, ConstantPatternContext.none)
parseUnaryExpression((, true, ConstantPatternContext.none)
parsePrimary((, expression, ConstantPatternContext.none)
parseSendOrFunctionLiteral((, expression, ConstantPatternContext.none)
parseSend((, expression, ConstantPatternContext.none)
isNextIdentifier(()
ensureIdentifier((, expression)
listener: handleIdentifier(x, expression)
listener: handleNoTypeArguments())
parseArgumentsOpt(x)
listener: handleNoArguments())
listener: handleSend(x, ))
ensureCloseParen(x, ()
listener: handleParenthesizedCondition((, null, null)
ensureBlock(), null, switch expression)
listener: beginSwitchExpressionBlock({)
listener: beginSwitchExpressionCase()
parsePattern({, PatternContext.matching, precedence: 1)
parsePrimaryPattern({, PatternContext.matching)
listener: beginConstantPattern(null)
parsePrecedenceExpression({, 7, false, ConstantPatternContext.implicit)
parseUnaryExpression({, false, ConstantPatternContext.implicit)
parsePrimary({, expression, ConstantPatternContext.implicit)
parseLiteralInt({)
listener: handleLiteralInt(1)
listener: endConstantPattern(null)
listener: handleSwitchExpressionCasePattern(1)
reportRecoverableError(:, Message[ExpectedButGot, Expected '=>' before this., null, {string: =>}])
listener: handleRecoverableError(Message[ExpectedButGot, Expected '=>' before this., null, {string: =>}], :, :)
parseExpression(:)
looksLikeOuterPatternEquals(:)
skipOuterPattern(:)
parsePrecedenceExpression(:, 1, true, ConstantPatternContext.none)
parseUnaryExpression(:, true, ConstantPatternContext.none)
parsePrimary(:, expression, ConstantPatternContext.none)
parseLiteralString(:)
parseSingleLiteralString(:)
listener: beginLiteralString('one')
listener: endLiteralString(0, ,)
listener: endSwitchExpressionCase(null, :, 'one')
listener: beginSwitchExpressionCase()
parsePattern(,, PatternContext.matching, precedence: 1)
parsePrimaryPattern(,, PatternContext.matching)
listener: beginConstantPattern(null)
parsePrecedenceExpression(,, 7, false, ConstantPatternContext.implicit)
parseUnaryExpression(,, false, ConstantPatternContext.implicit)
parsePrimary(,, expression, ConstantPatternContext.implicit)
parseLiteralInt(,)
listener: handleLiteralInt(2)
listener: endConstantPattern(null)
listener: handleSwitchExpressionCasePattern(2)
reportRecoverableError(:, Message[ExpectedButGot, Expected '=>' before this., null, {string: =>}])
listener: handleRecoverableError(Message[ExpectedButGot, Expected '=>' before this., null, {string: =>}], :, :)
parseExpression(:)
looksLikeOuterPatternEquals(:)
skipOuterPattern(:)
parsePrecedenceExpression(:, 1, true, ConstantPatternContext.none)
parseUnaryExpression(:, true, ConstantPatternContext.none)
parsePrimary(:, expression, ConstantPatternContext.none)
parseLiteralString(:)
parseSingleLiteralString(:)
listener: beginLiteralString('two')
listener: endLiteralString(0, })
listener: endSwitchExpressionCase(null, :, 'two')
listener: endSwitchExpressionBlock(2, {, })
listener: endSwitchExpression(switch, })
ensureSemicolon(})
listener: handleExpressionFunctionBody(=>, ;)
inGenerator()
listener: endTopLevelMethod(f, null, ;)
listener: endTopLevelDeclaration()
reportAllErrorTokens(f)
listener: endCompilationUnit(1, )

View file

@ -0,0 +1,11 @@
f(x) => switch (x) {
1: 'one',
2: 'two'
};
f[StringToken]([BeginToken]x[StringToken])[SimpleToken] =>[SimpleToken] switch[KeywordToken] ([BeginToken]x[StringToken])[SimpleToken] {[BeginToken]
1[StringToken]:[SimpleToken] 'one'[StringToken],[SimpleToken]
2[StringToken]:[SimpleToken] 'two'[StringToken]
}[SimpleToken];[SimpleToken]
[SimpleToken]

View file

@ -0,0 +1,11 @@
f(x) => switch (x) {
1: 'one',
2: 'two'
};
f[StringToken]([BeginToken]x[StringToken])[SimpleToken] =>[SimpleToken] switch[KeywordToken] ([BeginToken]x[StringToken])[SimpleToken] {[BeginToken]
1[StringToken]:[SimpleToken] 'one'[StringToken],[SimpleToken]
2[StringToken]:[SimpleToken] 'two'[StringToken]
}[SimpleToken];[SimpleToken]
[SimpleToken]