_fe_analyzer_shared: add support for rest patterns with subpatterns in maps.

The parser now handles maps that contain rest patterns with
subpatterns (e.g. `{'foo': _, ...var rest}`).  Even though this is
invalid Dart, it still makes sense for the parser to handle it so that
we can generate higher quality error messages.

The shared type analyzer now generates an error,
`restPatternWithSubPatternInMap`, if it encounters this error
condition.

Change-Id: I7f447bde28e646593aa432e3e5ad1f5e415bdd30
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/273283
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Jens Johansen <jensj@google.com>
This commit is contained in:
Paul Berry 2022-12-02 13:50:38 +00:00 committed by Commit Queue
parent 999c9b5e19
commit 882941caf4
12 changed files with 255 additions and 5 deletions

View file

@ -9596,11 +9596,12 @@ class Parser {
if (optional('...', next)) {
Token dots = next;
token = next;
// TODO(paulberry): users might assume that `...pattern` is valid inside
// a map (because it's valid inside a list). Consider accepting this
// and reporting the error at a later stage of the compilation pipeline
// so that the error is more useful.
listener.handleRestPattern(dots, hasSubPattern: false);
next = token.next!;
bool hasSubPattern = looksLikePatternStart(next);
if (hasSubPattern) {
token = parsePattern(token, isRefutableContext: isRefutableContext);
}
listener.handleRestPattern(dots, hasSubPattern: hasSubPattern);
} else {
token = parseExpression(token);
Token colon = token.next!;

View file

@ -698,6 +698,11 @@ mixin TypeAnalyzer<
handleMapPatternEntry(node, element);
} else {
assert(isRestPatternElement(element));
Pattern? subPattern = getRestPatternElementPattern(element);
if (subPattern != null) {
errors?.restPatternWithSubPatternInMap(node, element);
dispatchPattern(dynamicType, context, subPattern);
}
handleMapPatternRestElement(node, element);
}
}
@ -1827,6 +1832,11 @@ abstract class TypeAnalyzerErrors<
required Type returnType,
});
/// Called if a rest pattern inside a map pattern has a subpattern.
///
/// [node] is the map pattern. [element] is the rest pattern.
void restPatternWithSubPatternInMap(Pattern node, Node element);
/// Called if one of the case bodies of a switch statement completes normally
/// (other than the last case body), and the "patterns" feature is not
/// enabled.

View file

@ -2978,6 +2978,12 @@ class _MiniAstErrors
);
}
@override
void restPatternWithSubPatternInMap(Pattern node, Node element) {
_recordError(
'restPatternWithSubPatternInMap(${node.errorId}, ${element.errorId})');
}
@override
void switchCaseCompletesNormally(
covariant _SwitchStatement node, int caseIndex, int numHeads) {

View file

@ -1660,6 +1660,24 @@ main() {
});
});
});
group('Errors:', () {
test('Rest pattern with subpattern', () {
var x = Var('x');
h.run([
match(
mapPattern([
mapPatternEntry(expr('bool'), x.pattern(type: 'int')),
mapPatternRestElement(wildcard())..errorId = 'REST_ELEMENT',
])
..errorId = 'MAP_PATTERN',
expr('dynamic'),
),
], expectedErrors: {
'restPatternWithSubPatternInMap(MAP_PATTERN, REST_ELEMENT)'
});
});
});
});
group('List:', () {

View file

@ -144,6 +144,11 @@ class SharedTypeAnalyzerErrors
);
}
@override
void restPatternWithSubPatternInMap(DartPattern node, AstNode element) {
throw UnimplementedError('TODO(paulberry)');
}
@override
void switchCaseCompletesNormally(
covariant SwitchStatement node, int caseIndex, int numHeads) {

View file

@ -7431,6 +7431,32 @@ ListPattern
''');
}
test_rest_withSubpattern_insideMap() {
// The parser accepts this syntax even though it's not legal dart, because
// we suspect it's a mistake a user is likely to make, and we want to ensure
// that we give a helpful error message.
_parse('''
void f(x) {
switch (x) {
case {...var y}:
break;
}
}
''');
var node = findNode.singleGuardedPattern.pattern;
assertParsedNodeText(node, r'''
MapPattern
leftBracket: {
elements
RestPatternElement
operator: ...
pattern: VariablePattern
keyword: var
name: y
rightBracket: }
''');
}
test_skipOuterPattern_eof() {
// See https://github.com/dart-lang/sdk/issues/50563
_parse('''

View file

@ -107,6 +107,11 @@ class SharedTypeAnalyzerErrors
throw new UnimplementedError('TODO(scheglov)');
}
@override
void restPatternWithSubPatternInMap(Pattern node, Node element) {
throw new UnimplementedError('TODO(paulberry)');
}
@override
void switchCaseCompletesNormally(
covariant SwitchStatement node, int caseIndex, int numMergedCases) {

View file

@ -0,0 +1,6 @@
void f(x) {
switch (x) {
case {...var y}:
break;
}
}

View file

@ -0,0 +1,42 @@
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)
handleNoTypeArguments({)
handleNoType(var)
handleVariablePattern(var, y)
handleRestPattern(..., true)
handleMapPattern(1, {, })
endCaseExpression(case, null, :)
beginSwitchCase(0, 1, case)
handleBreakStatement(false, break, ;)
endSwitchCase(0, 1, null, null, 1, case, })
endSwitchBlock(1, {, })
endSwitchStatement(switch, })
endBlockFunctionBody(1, {, })
endTopLevelMethod(void, null, })
endTopLevelDeclaration()
endCompilationUnit(1, )

View file

@ -0,0 +1,101 @@
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)
parseUnaryExpression((, true)
parsePrimary((, expression)
parseSendOrFunctionLiteral((, expression)
parseSend((, expression)
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, precedence: 1, isRefutableContext: true)
parsePrimaryPattern(case, isRefutableContext: true)
listener: handleNoTypeArguments({)
parseMapPatternSuffix(case, isRefutableContext: true)
parsePattern(..., precedence: 1, isRefutableContext: true)
parsePrimaryPattern(..., isRefutableContext: true)
parseVariablePattern(..., typeInfo: Instance of 'NoType')
listener: handleNoType(var)
listener: handleVariablePattern(var, y)
listener: handleRestPattern(..., true)
listener: handleMapPattern(1, {, })
ensureColon(})
listener: endCaseExpression(case, null, :)
peekPastLabels(break)
parseStatementsInSwitchCase(:, break, case, 0, 1, null, null)
listener: beginSwitchCase(0, 1, case)
parseStatement(:)
parseStatementX(:)
parseBreakStatement(:)
isBreakAllowed()
ensureSemicolon(break)
listener: handleBreakStatement(false, break, ;)
peekPastLabels(})
listener: endSwitchCase(0, 1, null, null, 1, 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 @@
void f(x) {
switch (x) {
case {...var y}:
break;
}
}
void[KeywordToken] f[StringToken]([BeginToken]x[StringToken])[SimpleToken] {[BeginToken]
switch[KeywordToken] ([BeginToken]x[StringToken])[SimpleToken] {[BeginToken]
case[KeywordToken] {[BeginToken]...[SimpleToken]var[KeywordToken] y[StringToken]}[SimpleToken]:[SimpleToken]
break[KeywordToken];[SimpleToken]
}[SimpleToken]
}[SimpleToken]
[SimpleToken]

View file

@ -0,0 +1,15 @@
void f(x) {
switch (x) {
case {...var y}:
break;
}
}
void[KeywordToken] f[StringToken]([BeginToken]x[StringToken])[SimpleToken] {[BeginToken]
switch[KeywordToken] ([BeginToken]x[StringToken])[SimpleToken] {[BeginToken]
case[KeywordToken] {[BeginToken]...[SimpleToken]var[KeywordToken] y[StringToken]}[SimpleToken]:[SimpleToken]
break[KeywordToken];[SimpleToken]
}[SimpleToken]
}[SimpleToken]
[SimpleToken]