Skip to content

Commit 3ab2dfa

Browse files
chloestefantsovaCommit Queue
authored and
Commit Queue
committed
[cfe] Add support for c-style pattern-for elements in lists
Part of #49749 Change-Id: Iafc0239535dd89d0ff4bfa99bd10cdb3978b42ea Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/284224 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent 3424cc8 commit 3ab2dfa

13 files changed

+505
-6
lines changed

pkg/front_end/lib/src/fasta/kernel/body_builder.dart

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4118,12 +4118,33 @@ class BodyBuilder extends StackListenerImpl
41184118

41194119
@override
41204120
void endForControlFlow(Token token) {
4121+
assert(checkState(token, <ValueKind>[
4122+
/* entry = */ unionOfKinds(<ValueKind>[
4123+
ValueKinds.Generator,
4124+
ValueKinds.ExpressionOrNull,
4125+
ValueKinds.Statement,
4126+
ValueKinds.ParserRecovery,
4127+
ValueKinds.MapLiteralEntry,
4128+
]),
4129+
/* update expression count = */ ValueKinds.Integer,
4130+
/* left separator = */ ValueKinds.Token,
4131+
/* left parenthesis = */ ValueKinds.Token,
4132+
/* for keyword = */ ValueKinds.Token,
4133+
]));
41214134
debugEvent("ForControlFlow");
41224135
Object? entry = pop();
41234136
int updateExpressionCount = pop() as int;
41244137
pop(); // left separator
41254138
pop(); // left parenthesis
41264139
Token forToken = pop() as Token;
4140+
4141+
assert(checkState(token, <ValueKind>[
4142+
/* updates = */ ...repeatedKind(
4143+
unionOfKinds(
4144+
<ValueKind>[ValueKinds.Expression, ValueKinds.Generator]),
4145+
updateExpressionCount),
4146+
/* condition = */ ValueKinds.Statement,
4147+
]));
41274148
List<Expression> updates = popListForEffect(updateExpressionCount);
41284149
Statement conditionStatement = popStatement(); // condition
41294150

@@ -4142,6 +4163,7 @@ class BodyBuilder extends StackListenerImpl
41424163

41434164
// This is matched by the call to [beginNode] in
41444165
// [handleForInitializerEmptyStatement],
4166+
// [handleForInitializerPatternVariableAssignment],
41454167
// [handleForInitializerExpressionStatement], and
41464168
// [handleForInitializerLocalVariableDeclaration].
41474169
AssignedVariablesNodeInfo assignedVariablesNodeInfo =
@@ -4169,9 +4191,18 @@ class BodyBuilder extends StackListenerImpl
41694191
typeInferrer.assignedVariables.endNode(result);
41704192
push(result);
41714193
} else {
4172-
ForElement result = forest.createForElement(offsetForToken(forToken),
4173-
variables, condition, updates, toValue(entry));
4174-
typeInferrer.assignedVariables.endNode(result);
4194+
TreeNode result;
4195+
ForElement forElement = result = forest.createForElement(
4196+
offsetForToken(forToken),
4197+
variables,
4198+
condition,
4199+
updates,
4200+
toValue(entry));
4201+
if (variableOrExpression is PatternVariableDeclaration) {
4202+
result = forest.createPatternForElement(
4203+
offsetForToken(forToken), variableOrExpression, forElement);
4204+
}
4205+
typeInferrer.assignedVariables.endNode(forElement);
41754206
push(result);
41764207
}
41774208
}

pkg/front_end/lib/src/fasta/kernel/forest.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,16 @@ class Forest {
310310
..fileOffset = fileOffset;
311311
}
312312

313+
PatternForElement createPatternForElement(
314+
int fileOffset,
315+
PatternVariableDeclaration patternVariableDeclaration,
316+
ForElement forElement) {
317+
// ignore: unnecessary_null_comparison
318+
assert(fileOffset != null);
319+
return new PatternForElement(patternVariableDeclaration, forElement)
320+
..fileOffset = fileOffset;
321+
}
322+
313323
ForMapEntry createForMapEntry(
314324
int fileOffset,
315325
List<VariableDeclaration> variables,

pkg/front_end/lib/src/fasta/kernel/internal_ast.dart

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4201,9 +4201,9 @@ class WildcardPattern extends Pattern {
42014201
}
42024202

42034203
class PatternVariableDeclaration extends InternalStatement {
4204-
final Pattern pattern;
4205-
final Expression initializer;
4206-
final bool isFinal;
4204+
Pattern pattern;
4205+
Expression initializer;
4206+
bool isFinal;
42074207

42084208
PatternVariableDeclaration(this.pattern, this.initializer,
42094209
{required int fileOffset, required this.isFinal}) {
@@ -4914,3 +4914,48 @@ class IfCaseMapEntry extends TreeNode
49144914
return "IfCaseMapEntry(${toStringInternal()})";
49154915
}
49164916
}
4917+
4918+
class PatternForElement extends InternalExpression with ControlFlowElement {
4919+
PatternVariableDeclaration patternVariableDeclaration;
4920+
ForElement forElement;
4921+
late List<Statement> replacement;
4922+
4923+
PatternForElement(this.patternVariableDeclaration, this.forElement);
4924+
4925+
@override
4926+
ExpressionInferenceResult acceptInference(
4927+
InferenceVisitorImpl visitor, DartType typeContext) {
4928+
throw new UnsupportedError("PatternForElement.acceptInference");
4929+
}
4930+
4931+
@override
4932+
void toTextInternal(AstPrinter printer) {
4933+
printer.write('for (');
4934+
for (int index = 0; index < forElement.variables.length; index++) {
4935+
if (index > 0) {
4936+
printer.write(', ');
4937+
}
4938+
printer.writeVariableDeclaration(forElement.variables[index],
4939+
includeModifiersAndType: index == 0);
4940+
}
4941+
printer.write('; ');
4942+
if (forElement.condition != null) {
4943+
printer.writeExpression(forElement.condition!);
4944+
}
4945+
printer.write('; ');
4946+
printer.writeExpressions(forElement.updates);
4947+
printer.write(') ');
4948+
printer.writeExpression(forElement.body);
4949+
}
4950+
4951+
@override
4952+
MapLiteralEntry? toMapLiteralEntry(
4953+
void Function(TreeNode from, TreeNode to) onConvertElement) {
4954+
throw new UnimplementedError("toMapLiteralEntry");
4955+
}
4956+
4957+
@override
4958+
String toString() {
4959+
return "PatternForElement(${toStringInternal()})";
4960+
}
4961+
}

pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2348,6 +2348,87 @@ class InferenceVisitorImpl extends InferenceVisitorBase
23482348
}
23492349
flowAnalysis.for_end();
23502350
return new ExpressionInferenceResult(bodyResult.inferredType, element);
2351+
} else if (element is PatternForElement) {
2352+
int? stackBase;
2353+
assert(checkStackBase(element, stackBase = stackHeight));
2354+
2355+
PatternVariableDeclaration patternVariableDeclaration =
2356+
element.patternVariableDeclaration;
2357+
analyzePatternVariableDeclaration(
2358+
patternVariableDeclaration,
2359+
patternVariableDeclaration.pattern,
2360+
patternVariableDeclaration.initializer,
2361+
isFinal: patternVariableDeclaration.isFinal,
2362+
isLate: false);
2363+
2364+
assert(checkStack(element, stackBase, [
2365+
/* pattern = */ ValueKinds.Pattern,
2366+
/* initializer = */ ValueKinds.Expression,
2367+
]));
2368+
2369+
Object? rewrite = popRewrite(NullValues.Expression);
2370+
if (!identical(patternVariableDeclaration.pattern, rewrite)) {
2371+
patternVariableDeclaration.pattern = (rewrite as Pattern)
2372+
..parent = patternVariableDeclaration;
2373+
}
2374+
2375+
rewrite = popRewrite();
2376+
if (!identical(patternVariableDeclaration.initializer, rewrite)) {
2377+
patternVariableDeclaration.initializer = (rewrite as Expression)
2378+
..parent = patternVariableDeclaration;
2379+
}
2380+
2381+
MatchingCache matchingCache = createMatchingCache();
2382+
MatchingExpressionVisitor matchingExpressionVisitor =
2383+
new MatchingExpressionVisitor(matchingCache);
2384+
// TODO(cstefantsova): Do we need a more precise type for the variable?
2385+
DartType matchedType = const DynamicType();
2386+
CacheableExpression matchedExpression =
2387+
matchingCache.createRootExpression(
2388+
patternVariableDeclaration.initializer, matchedType);
2389+
2390+
DelayedExpression matchingExpression = matchingExpressionVisitor
2391+
.visitPattern(patternVariableDeclaration.pattern, matchedExpression);
2392+
2393+
matchingExpression.registerUse();
2394+
2395+
Expression readMatchingExpression =
2396+
matchingExpression.createExpression(typeSchemaEnvironment);
2397+
2398+
List<Statement> replacementStatements = [
2399+
...matchingCache.declarations,
2400+
// TODO(cstefantsova): Figure out the right exception to throw.
2401+
createIfStatement(
2402+
createNot(readMatchingExpression),
2403+
createExpressionStatement(createThrow(createConstructorInvocation(
2404+
coreTypes.reachabilityErrorConstructor,
2405+
createArguments([], fileOffset: element.fileOffset),
2406+
fileOffset: element.fileOffset))),
2407+
fileOffset: element.fileOffset),
2408+
];
2409+
if (replacementStatements.length > 1) {
2410+
// If we need local declarations, create a new block to avoid naming
2411+
// collision with declarations in the same parent block.
2412+
replacementStatements = [
2413+
createBlock(replacementStatements, fileOffset: element.fileOffset)
2414+
];
2415+
}
2416+
replacementStatements = [
2417+
...patternVariableDeclaration.pattern.declaredVariables,
2418+
...replacementStatements,
2419+
];
2420+
element.replacement = replacementStatements;
2421+
2422+
ExpressionInferenceResult inferenceResult = inferElement(
2423+
element.forElement,
2424+
inferredTypeArgument,
2425+
inferredSpreadTypes,
2426+
inferredConditionTypes);
2427+
element.forElement = (inferenceResult.expression as ForElement)
2428+
..parent = element;
2429+
2430+
return new ExpressionInferenceResult(
2431+
inferenceResult.inferredType, element);
23512432
} else if (element is ForInElement) {
23522433
ForInResult result;
23532434
if (element.variable.name == null) {
@@ -2661,6 +2742,10 @@ class InferenceVisitorImpl extends InferenceVisitorBase
26612742
} else if (element is ForElement) {
26622743
_translateForElement(element, receiverType, elementType, result, body,
26632744
isSet: isSet);
2745+
} else if (element is PatternForElement) {
2746+
_translatePatternForElement(
2747+
element, receiverType, elementType, result, body,
2748+
isSet: isSet);
26642749
} else if (element is ForInElement) {
26652750
_translateForInElement(element, receiverType, elementType, result, body,
26662751
isSet: isSet);
@@ -2753,6 +2838,30 @@ class InferenceVisitorImpl extends InferenceVisitorBase
27532838
body.add(loop);
27542839
}
27552840

2841+
void _translatePatternForElement(
2842+
PatternForElement element,
2843+
InterfaceType receiverType,
2844+
DartType elementType,
2845+
VariableDeclaration result,
2846+
List<Statement> body,
2847+
{required bool isSet}) {
2848+
List<Statement> statements = <Statement>[];
2849+
_translateElement(
2850+
element.forElement.body, receiverType, elementType, result, statements,
2851+
isSet: isSet);
2852+
Statement loopBody =
2853+
statements.length == 1 ? statements.first : _createBlock(statements);
2854+
ForStatement loop = _createForStatement(
2855+
element.fileOffset,
2856+
element.forElement.variables,
2857+
element.forElement.condition,
2858+
element.forElement.updates,
2859+
loopBody);
2860+
libraryBuilder.loader.dataForTesting?.registerAlias(element, loop);
2861+
body.addAll(element.replacement);
2862+
body.add(loop);
2863+
}
2864+
27562865
void _translateForInElement(ForInElement element, InterfaceType receiverType,
27572866
DartType elementType, VariableDeclaration result, List<Statement> body,
27582867
{required bool isSet}) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
test1(dynamic x) => [for (var [int i, int n] = x; i < n; i++) i];
6+
7+
main() {
8+
expectEquals(
9+
listToString(test1([0, 3])),
10+
listToString([0, 1, 2]),
11+
);
12+
expectEquals(
13+
listToString(test1([0, 0])),
14+
listToString([]),
15+
);
16+
expectThrows(() => test1({}));
17+
}
18+
19+
expectEquals(x, y) {
20+
if (x != y) {
21+
throw "Expected '${x}' to be equal to '${y}'.";
22+
}
23+
}
24+
25+
listToString(List list) => "[${list.map((e) => '${e}').join(',')}]";
26+
27+
expectThrows(void Function() f) {
28+
bool hasThrown = true;
29+
try {
30+
f();
31+
hasThrown = false;
32+
} catch (e) {}
33+
if (!hasThrown) {
34+
throw "Expected the function to throw.";
35+
}
36+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
library /*isNonNullableByDefault*/;
2+
import self as self;
3+
import "dart:core" as core;
4+
import "dart:_internal" as _in;
5+
6+
static method test1(dynamic x) → dynamic
7+
return block {
8+
final core::List<core::int> #t1 = <core::int>[];
9+
core::int i;
10+
core::int n;
11+
{
12+
final dynamic #0#0 = x;
13+
late final dynamic #0#6 = #0#0{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic};
14+
late final dynamic #0#7 = #0#0{core::List<dynamic>}.{core::List::[]}(1){(core::int) → dynamic};
15+
if(!(#0#0 is{ForNonNullableByDefault} core::List<dynamic> && #0#0{core::List<dynamic>}.{core::List::length}{core::int} =={core::num::==}{(core::Object) → core::bool} #C1 && (#0#6 is{ForNonNullableByDefault} core::int && (let final dynamic #t2 = i = #0#6{core::int} in true)) && (#0#7 is{ForNonNullableByDefault} core::int && (let final dynamic #t3 = n = #0#7{core::int} in true))))
16+
throw new _in::ReachabilityError::•();
17+
}
18+
for (core::int i = i, core::int n = n; i.{core::num::<}(n){(core::num) → core::bool}; i = i.{core::num::+}(1){(core::num) → core::int})
19+
#t1.{core::List::add}{Invariant}(i){(core::int) → void};
20+
} =>#t1;
21+
static method main() → dynamic {
22+
self::expectEquals(self::listToString(self::test1(<core::int>[0, 3]) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[0, 1, 2]));
23+
self::expectEquals(self::listToString(self::test1(<core::int>[0, 0]) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[]));
24+
self::expectThrows(() → void => self::test1(<dynamic, dynamic>{}));
25+
}
26+
static method expectEquals(dynamic x, dynamic y) → dynamic {
27+
if(!(x =={core::Object::==}{(core::Object) → core::bool} y)) {
28+
throw "Expected '${x}' to be equal to '${y}'.";
29+
}
30+
}
31+
static method listToString(core::List<dynamic> list) → dynamic
32+
return "[${list.{core::Iterable::map}<core::String>((dynamic e) → core::String => "${e}"){((dynamic) → core::String) → core::Iterable<core::String>}.{core::Iterable::join}(","){([core::String]) → core::String}}]";
33+
static method expectThrows(() → void f) → dynamic {
34+
core::bool hasThrown = true;
35+
try {
36+
f(){() → void};
37+
hasThrown = false;
38+
}
39+
on core::Object catch(final core::Object e) {
40+
}
41+
if(!hasThrown) {
42+
throw "Expected the function to throw.";
43+
}
44+
}
45+
46+
constants {
47+
#C1 = 2
48+
}

0 commit comments

Comments
 (0)