Skip to content

Commit ff18485

Browse files
stereotype441Commit Queue
authored and
Commit Queue
committed
Add parser support for ... in list patterns.
Bug: #50035 Change-Id: I31c7003fd40a6e074f2561561d2fba49955cc098 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/271565 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent e7b0d4f commit ff18485

27 files changed

+1489
-11
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,6 +1864,11 @@ class ForwardingListener implements Listener {
18641864
listener?.handleSpreadExpression(spreadToken);
18651865
}
18661866

1867+
@override
1868+
void handleRestPattern(Token dots, {required bool hasSubPattern}) {
1869+
listener?.handleRestPattern(dots, hasSubPattern: hasSubPattern);
1870+
}
1871+
18671872
@override
18681873
void handleStringJuxtaposition(Token startToken, int literalCount) {
18691874
listener?.handleStringJuxtaposition(startToken, literalCount);

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,36 @@ bool looksLikeExpressionStart(Token next) =>
370370
optional('++', next) ||
371371
optional('--', next);
372372

373+
/// Returns `true` if the given [token] should be treated like the start of a
374+
/// pattern for the purposes of recovery.
375+
///
376+
/// Note: since the syntax for patterns is very similar to that for expressions,
377+
/// we mostly re-use [looksLikeExpressionStart].
378+
bool looksLikePatternStart(Token next) =>
379+
next.isIdentifier ||
380+
next.type == TokenType.DOUBLE ||
381+
next.type == TokenType.HASH ||
382+
next.type == TokenType.HEXADECIMAL ||
383+
next.type == TokenType.IDENTIFIER ||
384+
next.type == TokenType.INT ||
385+
next.type == TokenType.STRING ||
386+
optional('null', next) ||
387+
optional('false', next) ||
388+
optional('true', next) ||
389+
optional('{', next) ||
390+
optional('(', next) ||
391+
optional('[', next) ||
392+
optional('[]', next) ||
393+
optional('<', next) ||
394+
optional('<=', next) ||
395+
optional('>', next) ||
396+
optional('>=', next) ||
397+
optional('!=', next) ||
398+
optional('==', next) ||
399+
optional('var', next) ||
400+
optional('final', next) ||
401+
optional('const', next);
402+
373403
/// Return `true` if the given [token] should be treated like the start of
374404
/// a new statement for the purposes of recovery.
375405
bool looksLikeStatementStart(Token token) => isOneOfOrEof(token, const [

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,13 @@ class Listener implements UnescapeErrorListener {
16731673
logEvent("SpreadExpression");
16741674
}
16751675

1676+
/// Called after parsing an element of a list or map pattern that starts with
1677+
/// `...`. Substructures:
1678+
/// - pattern (if hasSubPattern is `true`)
1679+
void handleRestPattern(Token dots, {required bool hasSubPattern}) {
1680+
logEvent('RestPattern');
1681+
}
1682+
16761683
/// Handle the start of a function typed formal parameter. Substructures:
16771684
/// - type variables
16781685
void beginFunctionTypedFormalParameter(Token token) {}

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import 'identifier_context.dart'
7575
show
7676
IdentifierContext,
7777
looksLikeExpressionStart,
78+
looksLikePatternStart,
7879
okNextValueInFormalParameter;
7980

8081
import 'identifier_context_impl.dart'
@@ -9493,7 +9494,18 @@ class Parser {
94939494
token = next;
94949495
break;
94959496
}
9496-
token = parsePattern(token, isRefutableContext: isRefutableContext);
9497+
if (optional('...', next)) {
9498+
Token dots = next;
9499+
token = next;
9500+
next = token.next!;
9501+
bool hasSubPattern = looksLikePatternStart(next);
9502+
if (hasSubPattern) {
9503+
token = parsePattern(token, isRefutableContext: isRefutableContext);
9504+
}
9505+
listener.handleRestPattern(dots, hasSubPattern: hasSubPattern);
9506+
} else {
9507+
token = parsePattern(token, isRefutableContext: isRefutableContext);
9508+
}
94979509
next = token.next!;
94989510
++count;
94999511
if (!optional(',', next)) {

pkg/analyzer/lib/src/fasta/ast_builder.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4972,6 +4972,12 @@ class AstBuilder extends StackListener {
49724972
operator: token, operand: pop() as ExpressionImpl));
49734973
}
49744974

4975+
@override
4976+
void handleRestPattern(Token dots, {required bool hasSubPattern}) {
4977+
var subPattern = hasSubPattern ? pop() as DartPatternImpl : null;
4978+
push(RestPatternElementImpl(operator: dots, pattern: subPattern));
4979+
}
4980+
49754981
@override
49764982
void handleScript(Token token) {
49774983
assert(identical(token.type, TokenType.SCRIPT_TAG));

pkg/analyzer/test/generated/patterns_parser_test.dart

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6608,6 +6608,90 @@ RecordPattern
66086608
''');
66096609
}
66106610

6611+
test_rest_subpatternStartingTokens() {
6612+
// Test a wide variety of rest subpatterns to make sure the parser properly
6613+
// identifies each as a subpattern. (The logic for deciding if a rest
6614+
// pattern has a subpattern is based on the token that follows the `..`, so
6615+
// we test every kind of token that can legally follow `...`). Note that
6616+
// not all of these are semantically meaningful, but they should all be
6617+
// parseable.
6618+
// TODO(paulberry): if support for symbol literal patterns is added (see
6619+
// https://github.com/dart-lang/language/issues/2636), adjust this test
6620+
// accordingly.
6621+
_parse('''
6622+
void f(x) {
6623+
switch (x) {
6624+
case [...== null]:
6625+
case [...!= null]:
6626+
case [...< 0]:
6627+
case [...> 0]:
6628+
case [...<= 0]:
6629+
case [...>= 0]:
6630+
case [...0]:
6631+
case [...0.0]:
6632+
case [...0x0]:
6633+
case [...null]:
6634+
case [...false]:
6635+
case [...true]:
6636+
case [...'foo']:
6637+
case [...x]:
6638+
case [...const List()]:
6639+
case [...var x]:
6640+
case [...final x]:
6641+
case [...List x]:
6642+
case [..._]:
6643+
case [...(_)]:
6644+
case [...[_]]:
6645+
case [...[]]:
6646+
case [...<int>[]]:
6647+
case [...{}]:
6648+
case [...List()]:
6649+
break;
6650+
}
6651+
}
6652+
''');
6653+
// No assertions; it's sufficient to make sure the parse succeeds without
6654+
// errors.
6655+
}
6656+
6657+
test_rest_withoutSubpattern_insideList() {
6658+
_parse('''
6659+
void f(x) {
6660+
switch (x) {
6661+
case [...]:
6662+
break;
6663+
}
6664+
}
6665+
''');
6666+
var node = findNode.singleGuardedPattern.pattern;
6667+
assertParsedNodeText(node, r'''
6668+
ListPattern
6669+
leftBracket: [
6670+
elements
6671+
RestPatternElement
6672+
rightBracket: ]
6673+
''');
6674+
}
6675+
6676+
test_rest_withSubpattern_insideList() {
6677+
_parse('''
6678+
void f(x) {
6679+
switch (x) {
6680+
case [...var y]:
6681+
break;
6682+
}
6683+
}
6684+
''');
6685+
var node = findNode.singleGuardedPattern.pattern;
6686+
assertParsedNodeText(node, r'''
6687+
ListPattern
6688+
leftBracket: [
6689+
elements
6690+
RestPatternElement
6691+
rightBracket: ]
6692+
''');
6693+
}
6694+
66116695
test_variable_bare_insideCast() {
66126696
_parse('''
66136697
void f(x) {

pkg/analyzer/test/src/summary/resolved_ast_printer.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,14 @@ class ResolvedAstPrinter extends ThrowingAstVisitor<void> {
11791179
});
11801180
}
11811181

1182+
@override
1183+
void visitRestPatternElement(RestPatternElement node) {
1184+
_writeln('RestPatternElement');
1185+
_withIndent(() {
1186+
_writeNamedChildEntities(node);
1187+
});
1188+
}
1189+
11821190
@override
11831191
void visitReturnStatement(ReturnStatement node) {
11841192
_writeln('ReturnStatement');

pkg/front_end/lib/src/fasta/kernel/macro/annotation_parser.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2091,6 +2091,11 @@ class _MacroListener implements Listener {
20912091
_unsupported();
20922092
}
20932093

2094+
@override
2095+
void handleRestPattern(Token dots, {required bool hasSubPattern}) {
2096+
_unsupported();
2097+
}
2098+
20942099
@override
20952100
void handleSuperExpression(Token token, IdentifierContext context) {
20962101
_unsupported();

pkg/front_end/lib/src/fasta/util/parser_ast_helper.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2232,6 +2232,13 @@ abstract class AbstractParserAstListener implements Listener {
22322232
seen(data);
22332233
}
22342234

2235+
@override
2236+
void handleRestPattern(Token dots, {required bool hasSubPattern}) {
2237+
RestPatternHandle data = new RestPatternHandle(ParserAstType.HANDLE,
2238+
dots: dots, hasSubPattern: hasSubPattern);
2239+
seen(data);
2240+
}
2241+
22352242
@override
22362243
void beginFunctionTypedFormalParameter(Token token) {
22372244
FunctionTypedFormalParameterBegin data =
@@ -6823,6 +6830,21 @@ class SpreadExpressionHandle extends ParserAstNode {
68236830
};
68246831
}
68256832

6833+
class RestPatternHandle extends ParserAstNode {
6834+
final Token dots;
6835+
final bool hasSubPattern;
6836+
6837+
RestPatternHandle(ParserAstType type,
6838+
{required this.dots, required this.hasSubPattern})
6839+
: super("RestPattern", type);
6840+
6841+
@override
6842+
Map<String, Object?> get deprecatedArguments => {
6843+
"dots": dots,
6844+
"hasSubPattern": hasSubPattern,
6845+
};
6846+
}
6847+
68266848
class FunctionTypedFormalParameterBegin extends ParserAstNode {
68276849
final Token token;
68286850

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
void f(x) {
2+
switch (x) {
3+
case [...== null]:
4+
case [...!= null]:
5+
case [...< 0]:
6+
case [...> 0]:
7+
case [...<= 0]:
8+
case [...>= 0]:
9+
case [...0]:
10+
case [...0.0]:
11+
case [...0x0]:
12+
case [...null]:
13+
case [...false]:
14+
case [...true]:
15+
case [...'foo']:
16+
case [...x]:
17+
case [...const List()]:
18+
case [...var x]:
19+
case [...final x]:
20+
case [...List x]:
21+
case [..._]:
22+
case [...(_)]:
23+
case [...[_]]:
24+
case [...[]]:
25+
case [...<int>[]]:
26+
case [...{}]:
27+
case [...List()]:
28+
break;
29+
}
30+
}

0 commit comments

Comments
 (0)