Skip to content

Commit 9383820

Browse files
chloestefantsovaCommit Queue
authored and
Commit Queue
committed
[cfe] Desugar pattern variable assignments
Part of #49749 Change-Id: Ibae19d0e64f023aea047b007d73f3cee9910d259 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/277683 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent 8e07d49 commit 9383820

14 files changed

+649
-7
lines changed

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8483,8 +8483,17 @@ class BodyBuilder extends StackListenerImpl
84838483
Pattern pattern;
84848484
if (variable.lexeme == "_") {
84858485
pattern = new WildcardPattern(patternType, variable.charOffset);
8486+
} else if (inAssignmentPattern) {
8487+
Expression variableUse =
8488+
toValue(scopeLookup(scope, variable.lexeme, variable));
8489+
if (variableUse is VariableGet) {
8490+
pattern = new AssignedVariablePattern(variableUse.variable,
8491+
offset: variable.charOffset);
8492+
} else {
8493+
// Recover by using [WildcardPattern] instead.
8494+
pattern = new WildcardPattern(patternType, variable.charOffset);
8495+
}
84868496
} else {
8487-
// TODO(paulberry): use inAssignmentPattern.
84888497
pattern = new VariablePattern(
84898498
patternType,
84908499
variable.lexeme,
@@ -8552,7 +8561,24 @@ class BodyBuilder extends StackListenerImpl
85528561
// TODO(johnniwinther,cstefantsova): Handle metadata.
85538562
pop(NullValue.Metadata) as List<Expression>?;
85548563
push(new PatternVariableDeclaration(pattern, initializer,
8555-
offset: keyword.charOffset, isFinal: isFinal));
8564+
fileOffset: keyword.charOffset, isFinal: isFinal));
8565+
}
8566+
8567+
@override
8568+
void handlePatternAssignment(Token equals) {
8569+
debugEvent("PatternAssignment");
8570+
assert(checkState(equals, [
8571+
unionOfKinds([
8572+
ValueKinds.Expression,
8573+
ValueKinds.Generator,
8574+
ValueKinds.ProblemBuilder,
8575+
]),
8576+
ValueKinds.Pattern
8577+
]));
8578+
Expression expression = popForValue();
8579+
Pattern pattern = toPattern(pop());
8580+
push(new PatternAssignment(pattern, expression,
8581+
fileOffset: equals.charOffset));
85568582
}
85578583
}
85588584

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,14 @@ class Forest {
386386
..fileEndOffset = fileEndOffset;
387387
}
388388

389+
/// Return a representation of a block expression.
390+
BlockExpression createBlockExpression(
391+
int fileOffset, Block body, Expression value) {
392+
// ignore: unnecessary_null_comparison
393+
assert(fileOffset != null);
394+
return new BlockExpression(body, value)..fileOffset = fileOffset;
395+
}
396+
389397
/// Return a representation of a break statement.
390398
Statement createBreakStatement(int fileOffset, Object? label) {
391399
// ignore: unnecessary_null_comparison

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

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5020,8 +5020,8 @@ class PatternVariableDeclaration extends InternalStatement {
50205020
final bool isFinal;
50215021

50225022
PatternVariableDeclaration(this.pattern, this.initializer,
5023-
{required int offset, required this.isFinal}) {
5024-
fileOffset = offset;
5023+
{required int fileOffset, required this.isFinal}) {
5024+
super.fileOffset = fileOffset;
50255025
}
50265026

50275027
@override
@@ -5048,6 +5048,72 @@ class PatternVariableDeclaration extends InternalStatement {
50485048
}
50495049
}
50505050

5051+
class PatternAssignment extends InternalExpression {
5052+
final Pattern pattern;
5053+
final Expression expression;
5054+
5055+
PatternAssignment(this.pattern, this.expression, {required int fileOffset}) {
5056+
super.fileOffset = fileOffset;
5057+
}
5058+
5059+
@override
5060+
ExpressionInferenceResult acceptInference(
5061+
InferenceVisitorImpl visitor, DartType typeContext) {
5062+
return visitor.visitPatternAssignment(this, typeContext);
5063+
}
5064+
5065+
@override
5066+
String toString() {
5067+
return "PatternAssignment(${toStringInternal()})";
5068+
}
5069+
}
5070+
5071+
class AssignedVariablePattern extends Pattern {
5072+
final VariableDeclaration variable;
5073+
5074+
AssignedVariablePattern(this.variable, {required int offset}) : super(offset);
5075+
5076+
@override
5077+
PatternInferenceResult acceptInference(InferenceVisitorImpl visitor,
5078+
{required SharedMatchContext context}) {
5079+
return visitor.visitAssignedVariablePattern(this, context: context);
5080+
}
5081+
5082+
@override
5083+
List<VariableDeclaration> get declaredVariables => [variable];
5084+
5085+
@override
5086+
void toTextInternal(AstPrinter printer) {
5087+
printer.write(variable.name!);
5088+
}
5089+
5090+
@override
5091+
String toString() {
5092+
return "AssignedVariablePattern(${toStringInternal()})";
5093+
}
5094+
5095+
@override
5096+
PatternTransformationResult transform(
5097+
Expression matchedExpression,
5098+
DartType matchedType,
5099+
Expression variableInitializingContext,
5100+
InferenceVisitorBase inferenceVisitor) {
5101+
// condition: let _ = `variable` = `matchedExpression` in true
5102+
return new PatternTransformationResult([
5103+
new PatternTransformationElement(
5104+
kind: PatternTransformationElementKind.regular,
5105+
condition: inferenceVisitor.engine.forest.createLet(
5106+
inferenceVisitor.engine.forest.createVariableDeclarationForValue(
5107+
inferenceVisitor.engine.forest.createVariableSet(
5108+
fileOffset, variable, matchedExpression)),
5109+
inferenceVisitor.engine.forest
5110+
.createBoolLiteral(fileOffset, true))
5111+
..fileOffset = fileOffset,
5112+
variableInitializers: [])
5113+
]);
5114+
}
5115+
}
5116+
50515117
final Pattern dummyPattern = new ExpressionPattern(dummyExpression);
50525118

50535119
/// Internal statement for a if-case statements:
@@ -5617,7 +5683,7 @@ class VariablePattern extends Pattern {
56175683

56185684
@override
56195685
String toString() {
5620-
return "VariableBinder(${toStringInternal()})";
5686+
return "VariablePattern(${toStringInternal()})";
56215687
}
56225688
}
56235689

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

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9304,6 +9304,82 @@ class InferenceVisitorImpl extends InferenceVisitorBase
93049304
return const PatternInferenceResult();
93059305
}
93069306

9307+
ExpressionInferenceResult visitPatternAssignment(
9308+
PatternAssignment node, DartType typeContext) {
9309+
Expression expression = node.expression;
9310+
ExpressionTypeAnalysisResult<DartType> analysisResult =
9311+
analyzePatternAssignment(node, node.pattern, expression);
9312+
Node? rewrite = popRewrite();
9313+
if (!identical(expression, rewrite)) {
9314+
expression = rewrite as Expression;
9315+
}
9316+
9317+
// TODO(cstefantsova): Do we need a more precise type for the variable?
9318+
VariableDeclaration matchedExpressionVariable = engine.forest
9319+
.createVariableDeclarationForValue(expression,
9320+
type: const DynamicType());
9321+
VariableDeclaration isPatternMatchingFailed = engine.forest
9322+
.createVariableDeclarationForValue(
9323+
engine.forest.createBoolLiteral(node.fileOffset, true),
9324+
type: coreTypes.boolNonNullableRawType);
9325+
9326+
// patternMatchedSet: `isPatternMatchingFailed` = false;
9327+
// ==> VAR = true;
9328+
Statement patternMatchedSet = engine.forest.createExpressionStatement(
9329+
node.fileOffset,
9330+
engine.forest.createVariableSet(
9331+
node.fileOffset,
9332+
isPatternMatchingFailed,
9333+
engine.forest.createBoolLiteral(node.fileOffset, false)));
9334+
9335+
PatternTransformationResult transformationResult = node.pattern.transform(
9336+
engine.forest
9337+
.createVariableGet(node.fileOffset, matchedExpressionVariable),
9338+
const DynamicType(),
9339+
engine.forest
9340+
.createVariableGet(node.fileOffset, matchedExpressionVariable),
9341+
this);
9342+
9343+
List<Statement> replacementStatements = _transformationResultToStatements(
9344+
node.fileOffset, transformationResult, [patternMatchedSet]);
9345+
9346+
replacementStatements = [
9347+
matchedExpressionVariable,
9348+
isPatternMatchingFailed,
9349+
...replacementStatements,
9350+
// TODO(cstefantsova): Figure out the right exception to throw.
9351+
engine.forest.createIfStatement(
9352+
node.fileOffset,
9353+
engine.forest
9354+
.createVariableGet(node.fileOffset, isPatternMatchingFailed),
9355+
engine.forest.createExpressionStatement(
9356+
node.fileOffset,
9357+
new Throw(
9358+
new ConstructorInvocation(
9359+
coreTypes.reachabilityErrorConstructor,
9360+
engine.forest.createArguments(node.fileOffset, []))
9361+
..fileOffset = node.fileOffset)
9362+
..fileOffset = node.fileOffset),
9363+
null),
9364+
];
9365+
9366+
Expression replacement = engine.forest.createBlockExpression(
9367+
node.fileOffset,
9368+
engine.forest.createBlock(
9369+
node.fileOffset, node.fileOffset, replacementStatements),
9370+
engine.forest
9371+
.createVariableGet(node.fileOffset, matchedExpressionVariable));
9372+
return new ExpressionInferenceResult(
9373+
analysisResult.resolveShorting(), replacement);
9374+
}
9375+
9376+
PatternInferenceResult visitAssignedVariablePattern(
9377+
AssignedVariablePattern node,
9378+
{required SharedMatchContext context}) {
9379+
analyzeAssignedVariablePattern(context, node, node.variable);
9380+
return const PatternInferenceResult();
9381+
}
9382+
93079383
@override
93089384
shared.RecordType<DartType>? asRecordType(DartType type) {
93099385
if (type is RecordType) {

pkg/front_end/test/text_representation/internal_ast_text_representation_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,14 +1318,14 @@ void _testPatternVariableDeclaration() {
13181318
testStatement(
13191319
new PatternVariableDeclaration(
13201320
new ExpressionPattern(new IntLiteral(0)), new IntLiteral(1),
1321-
isFinal: false, offset: TreeNode.noOffset),
1321+
isFinal: false, fileOffset: TreeNode.noOffset),
13221322
'''
13231323
var 0 = 1;''');
13241324

13251325
testStatement(
13261326
new PatternVariableDeclaration(
13271327
new ExpressionPattern(new IntLiteral(0)), new IntLiteral(1),
1328-
isFinal: true, offset: TreeNode.noOffset),
1328+
isFinal: true, fileOffset: TreeNode.noOffset),
13291329
'''
13301330
final 0 = 1;''');
13311331
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) 2022, 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) {
6+
int a;
7+
[a] = x;
8+
return a;
9+
}
10+
11+
test2(dynamic x) {
12+
int a, b, c;
13+
[c, ...] = [a && b, ...] = x;
14+
return a + b + c;
15+
}
16+
17+
main() {
18+
expectEquals(test1([1]), 1);
19+
expectThrows(() => test1([]));
20+
expectThrows(() => test1([1, 2, 3]));
21+
expectThrows(() => test1("foo"));
22+
expectThrows(() => test1(null));
23+
24+
expectEquals(test2([1]), 3);
25+
expectThrows(() => test2(1));
26+
}
27+
28+
expectEquals(x, y) {
29+
if (x != y) {
30+
throw "Expected ${x} to be equal to ${y}.";
31+
}
32+
}
33+
34+
expectThrows(void Function() f) {
35+
bool hasThrown = true;
36+
try {
37+
f();
38+
hasThrown = false;
39+
} catch (e) {}
40+
if (!hasThrown) {
41+
throw "Expected the function to throw.";
42+
}
43+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
core::int a;
8+
block {
9+
final dynamic #t1 = x;
10+
final core::bool #t2 = true;
11+
final dynamic #t3 = #t1;
12+
if(#t3 is core::List<dynamic> && #t3{core::List<dynamic>}.{core::List::length}{core::int}.{core::num::==}(1){(core::Object) → core::bool}) {
13+
final dynamic #t4 = #t3{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic};
14+
if(let final dynamic #t5 = a = #t4 in true) {
15+
#t2 = false;
16+
}
17+
}
18+
if(#t2)
19+
throw new _in::ReachabilityError::•();
20+
} =>#t1;
21+
return a;
22+
}
23+
static method test2(dynamic x) → dynamic {
24+
core::int a;
25+
core::int b;
26+
core::int c;
27+
block {
28+
final dynamic #t6 = block {
29+
final dynamic #t7 = x;
30+
final core::bool #t8 = true;
31+
final dynamic #t9 = #t7;
32+
if(#t9 is core::List<dynamic> && #t9{core::List<dynamic>}.{core::List::length}{core::int}.{core::num::>=}(1){(core::num) → core::bool}) {
33+
final dynamic #t10 = #t9{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic};
34+
final dynamic #t11 = #t10;
35+
if((let final dynamic #t12 = a = #t11 in true) && (let final dynamic #t13 = b = #t11 in true)) {
36+
#t8 = false;
37+
}
38+
}
39+
if(#t8)
40+
throw new _in::ReachabilityError::•();
41+
} =>#t7;
42+
final core::bool #t14 = true;
43+
final dynamic #t15 = #t6;
44+
if(#t15 is core::List<dynamic> && #t15{core::List<dynamic>}.{core::List::length}{core::int}.{core::num::>=}(1){(core::num) → core::bool}) {
45+
final dynamic #t16 = #t15{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic};
46+
if(let final dynamic #t17 = c = #t16 in true) {
47+
#t14 = false;
48+
}
49+
}
50+
if(#t14)
51+
throw new _in::ReachabilityError::•();
52+
} =>#t6;
53+
return a.{core::num::+}(b){(core::num) → core::int}.{core::num::+}(c){(core::num) → core::int};
54+
}
55+
static method main() → dynamic {
56+
self::expectEquals(self::test1(<core::int>[1]), 1);
57+
self::expectThrows(() → void => self::test1(<dynamic>[]));
58+
self::expectThrows(() → void => self::test1(<core::int>[1, 2, 3]));
59+
self::expectThrows(() → void => self::test1("foo"));
60+
self::expectThrows(() → void => self::test1(null));
61+
self::expectEquals(self::test2(<core::int>[1]), 3);
62+
self::expectThrows(() → void => self::test2(1));
63+
}
64+
static method expectEquals(dynamic x, dynamic y) → dynamic {
65+
if(!(x =={core::Object::==}{(core::Object) → core::bool} y)) {
66+
throw "Expected ${x} to be equal to ${y}.";
67+
}
68+
}
69+
static method expectThrows(() → void f) → dynamic {
70+
core::bool hasThrown = true;
71+
try {
72+
f(){() → void};
73+
hasThrown = false;
74+
}
75+
on core::Object catch(final core::Object e) {
76+
}
77+
if(!hasThrown) {
78+
throw "Expected the function to throw.";
79+
}
80+
}

0 commit comments

Comments
 (0)