Patterns parsing: handle incomplete identifier . in a pattern.

Bug: https://github.com/Dart-Code/Dart-Code/issues/4407
Change-Id: Ibac9822fa6cf2f15aa67b68a28f399cf4373e782
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/291043
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry 2023-03-24 16:10:09 +00:00 committed by Commit Queue
parent c927840920
commit 8f6325d650
7 changed files with 242 additions and 1 deletions

View file

@ -9785,7 +9785,8 @@ class Parser {
// `typeIdentifier | qualifiedName`, but that permits `a.b.c`,
// which doesn't make sense.
} else {
throw new UnimplementedError('TODO(paulberry)');
secondIdentifier = IdentifierContext.expressionContinuation
.ensureIdentifier(token, this);
}
}
TypeParamOrArgInfo potentialTypeArg = computeTypeParamOrArg(token);

View file

@ -21,6 +21,35 @@ main() {
class PatternsTest extends ParserDiagnosticsTest {
late FindNode findNode;
test_case_identifier_dot_incomplete() {
// Based on the repro from
// https://github.com/Dart-Code/Dart-Code/issues/4407.
_parse('''
void f(x) {
switch (x) {
case A.
}
}
''', errors: [
error(ParserErrorCode.MISSING_IDENTIFIER, 41, 1),
error(ParserErrorCode.EXPECTED_TOKEN, 41, 1),
]);
var node = findNode.switchPatternCase('case');
assertParsedNodeText(node, r'''
SwitchPatternCase
keyword: case
guardedPattern: GuardedPattern
pattern: ConstantPattern
expression: PrefixedIdentifier
prefix: SimpleIdentifier
token: A
period: .
identifier: SimpleIdentifier
token: <empty> <synthetic>
colon: : <synthetic>
''');
}
test_caseHead_withClassicPattern_guarded_insideIfElement() {
_parse('''
void f(x) {

View file

@ -0,0 +1,5 @@
void f(x) {
switch (x) {
case A.
}
}

View file

@ -0,0 +1,60 @@
Problems reported:
parser/patterns/case_identifier_dot_incomplete:4:3: Expected an identifier, but got '}'.
}
^
parser/patterns/case_identifier_dot_incomplete:4:3: Expected ':' before this.
}
^
beginCompilationUnit(void)
beginMetadataStar(void)
endMetadataStar(0)
beginTopLevelMember(void)
beginTopLevelMethod(, null, null)
handleVoidKeyword(void)
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)
beginBlockFunctionBody({)
beginSwitchStatement(switch)
handleIdentifier(x, expression)
handleNoTypeArguments())
handleNoArguments())
handleSend(x, ))
handleParenthesizedCondition((, null, null)
beginSwitchBlock({)
beginCaseExpression(case)
handleRecoverableError(Message[ExpectedIdentifier, Expected an identifier, but got '}'., Try inserting an identifier before '}'., {lexeme: }}], }, })
beginConstantPattern(null)
handleIdentifier(A, expression)
handleNoTypeArguments(.)
handleNoArguments(.)
handleSend(A, .)
handleIdentifier(, expressionContinuation)
handleNoTypeArguments(})
handleNoArguments(})
handleSend(, })
handleEndingBinaryExpression(.)
endConstantPattern(null)
handleSwitchCaseNoWhenClause()
handleRecoverableError(Message[ExpectedButGot, Expected ':' before this., null, {string: :}], }, })
endCaseExpression(case, null, :)
beginSwitchCase(0, 1, case)
endSwitchCase(0, 1, null, null, 0, case, })
endSwitchBlock(1, {, })
endSwitchStatement(switch, })
endBlockFunctionBody(1, {, })
endTopLevelMethod(void, null, })
endTopLevelDeclaration()
endCompilationUnit(1, )

View file

@ -0,0 +1,118 @@
parseUnit(void)
skipErrorTokens(void)
listener: beginCompilationUnit(void)
syntheticPreviousToken(void)
parseTopLevelDeclarationImpl(, Instance of 'DirectiveContext')
parseMetadataStar()
listener: beginMetadataStar(void)
listener: endMetadataStar(0)
parseTopLevelMemberImpl()
listener: beginTopLevelMember(void)
parseTopLevelMethod(, null, null, , Instance of 'VoidType', null, f, false)
listener: beginTopLevelMethod(, null, null)
listener: handleVoidKeyword(void)
ensureIdentifierPotentiallyRecovered(void, 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)
listener: beginBlockFunctionBody({)
notEofOrValue(}, switch)
parseStatement({)
parseStatementX({)
parseSwitchStatement({)
listener: beginSwitchStatement(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)
parseSwitchBlock())
ensureBlock(), null, switch statement)
listener: beginSwitchBlock({)
notEofOrValue(}, case)
peekPastLabels(case)
listener: beginCaseExpression(case)
parsePattern(case, PatternContext.matching, precedence: 1)
parsePrimaryPattern(case, PatternContext.matching)
reportRecoverableErrorWithToken(}, Instance of 'Template<(Token) => Message>')
listener: handleRecoverableError(Message[ExpectedIdentifier, Expected an identifier, but got '}'., Try inserting an identifier before '}'., {lexeme: }}], }, })
rewriter()
listener: beginConstantPattern(null)
parsePrecedenceExpression(case, 7, false, ConstantPatternContext.implicit)
parseUnaryExpression(case, false, ConstantPatternContext.implicit)
parsePrimary(case, expression, ConstantPatternContext.implicit)
parseSendOrFunctionLiteral(case, expression, ConstantPatternContext.implicit)
parseSend(case, expression, ConstantPatternContext.implicit)
isNextIdentifier(case)
ensureIdentifier(case, expression)
listener: handleIdentifier(A, expression)
listener: handleNoTypeArguments(.)
parseArgumentsOpt(A)
listener: handleNoArguments(.)
listener: handleSend(A, .)
parsePrimary(., expressionContinuation, ConstantPatternContext.implicit)
parseSendOrFunctionLiteral(., expressionContinuation, ConstantPatternContext.implicit)
parseSend(., expressionContinuation, ConstantPatternContext.implicit)
isNextIdentifier(.)
ensureIdentifier(., expressionContinuation)
listener: handleIdentifier(, expressionContinuation)
listener: handleNoTypeArguments(})
parseArgumentsOpt()
listener: handleNoArguments(})
listener: handleSend(, })
listener: handleEndingBinaryExpression(.)
listener: endConstantPattern(null)
listener: handleSwitchCaseNoWhenClause()
ensureColon()
rewriteAndRecover(, Message[ExpectedButGot, Expected ':' before this., null, {string: :}], :)
reportRecoverableError(}, Message[ExpectedButGot, Expected ':' before this., null, {string: :}])
listener: handleRecoverableError(Message[ExpectedButGot, Expected ':' before this., null, {string: :}], }, })
rewriter()
listener: endCaseExpression(case, null, :)
peekPastLabels(})
parseStatementsInSwitchCase(:, }, case, 0, 1, null, null)
listener: beginSwitchCase(0, 1, case)
listener: endSwitchCase(0, 1, null, null, 0, case, })
notEofOrValue(}, })
listener: endSwitchBlock(1, {, })
listener: endSwitchStatement(switch, })
notEofOrValue(}, })
listener: endBlockFunctionBody(1, {, })
listener: endTopLevelMethod(void, null, })
listener: endTopLevelDeclaration()
reportAllErrorTokens(void)
listener: endCompilationUnit(1, )

View file

@ -0,0 +1,15 @@
NOTICE: Stream was rewritten by parser!
void f(x) {
switch (x) {
case A.
*synthetic*:}
}
void[KeywordToken] f[StringToken]([BeginToken]x[StringToken])[SimpleToken] {[BeginToken]
switch[KeywordToken] ([BeginToken]x[StringToken])[SimpleToken] {[BeginToken]
case[KeywordToken] A[StringToken].[SimpleToken]
[SyntheticStringToken]:[SyntheticToken]}[SimpleToken]
}[SimpleToken]
[SimpleToken]

View file

@ -0,0 +1,13 @@
void f(x) {
switch (x) {
case A.
}
}
void[KeywordToken] f[StringToken]([BeginToken]x[StringToken])[SimpleToken] {[BeginToken]
switch[KeywordToken] ([BeginToken]x[StringToken])[SimpleToken] {[BeginToken]
case[KeywordToken] A[StringToken].[SimpleToken]
}[SimpleToken]
}[SimpleToken]
[SimpleToken]