mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:09:49 +00:00
_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:
parent
999c9b5e19
commit
882941caf4
|
@ -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!;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:', () {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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('''
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
void f(x) {
|
||||
switch (x) {
|
||||
case {...var y}:
|
||||
break;
|
||||
}
|
||||
}
|
|
@ -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, )
|
|
@ -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, )
|
|
@ -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]
|
|
@ -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]
|
Loading…
Reference in a new issue