Skip to content

Commit 563ef79

Browse files
chloestefantsovaCommit Queue
authored and
Commit Queue
committed
[cfe] Desugar MapPattern
Part of #49749 Change-Id: Ic2b555a4384187b358e001ea29755ca14588decd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/271366 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent 0da4417 commit 563ef79

15 files changed

+610
-28
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4408,8 +4408,8 @@ class BodyBuilder extends StackListenerImpl
44084408
ValueKinds.Pattern,
44094409
])
44104410
]));
4411-
Pattern key = toPattern(pop());
44124411
Pattern value = toPattern(pop());
4412+
Pattern key = toPattern(pop());
44134413
push(new MapPatternEntry(key, value, colon.charOffset));
44144414
}
44154415

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

Lines changed: 154 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
/// kernel class, because multiple constructs in Dart may desugar to a tree
1919
/// with the same kind of root node.
2020
import 'package:kernel/ast.dart';
21+
import 'package:kernel/clone.dart';
2122
import 'package:kernel/src/printer.dart';
2223
import 'package:kernel/text/ast_to_text.dart' show Precedence, Printer;
2324
import 'package:kernel/type_environment.dart';
@@ -5597,6 +5598,15 @@ class ListPattern extends Pattern {
55975598
.createVariableGet(fileOffset, listElementVariable),
55985599
inferenceVisitor);
55995600

5601+
// If the sub-pattern transformation doesn't declare captured variables
5602+
// and consists of a single empty element, it means that it simply
5603+
// doesn't have a place where it could refer to the element expression.
5604+
// In that case we can avoid creating the intermediary variable for the
5605+
// element expression.
5606+
//
5607+
// An example of such sub-pattern is in the following:
5608+
//
5609+
// if (x case [var _]) { /* ... */ }
56005610
if (patterns[i].declaredVariables.isNotEmpty ||
56015611
!(subpatternTransformationResult.elements.length == 1 &&
56025612
subpatternTransformationResult.elements.single.isEmpty)) {
@@ -6001,14 +6011,11 @@ class MapPattern extends Pattern {
60016011
final DartType? keyType;
60026012
final DartType? valueType;
60036013
final List<MapPatternEntry> entries;
6014+
late final InterfaceType type;
60046015

60056016
@override
6006-
List<VariableDeclaration> get declaredVariables => [
6007-
for (MapPatternEntry entry in entries) ...[
6008-
...entry.key.declaredVariables,
6009-
...entry.value.declaredVariables
6010-
]
6011-
];
6017+
List<VariableDeclaration> get declaredVariables =>
6018+
[for (MapPatternEntry entry in entries) ...entry.value.declaredVariables];
60126019

60136020
MapPattern(this.keyType, this.valueType, this.entries, int fileOffset)
60146021
: assert((keyType == null) == (valueType == null)),
@@ -6050,12 +6057,147 @@ class MapPattern extends Pattern {
60506057
DartType matchedType,
60516058
Expression variableInitializingContext,
60526059
InferenceVisitorBase inferenceVisitor) {
6053-
return new PatternTransformationResult([
6054-
new PatternTransformationElement(
6055-
condition:
6056-
new InvalidExpression("Unimplemented MapPattern.transform"),
6057-
variableInitializers: [])
6058-
]);
6060+
DartType valueType = type.typeArguments[1];
6061+
6062+
ObjectAccessTarget containsKeyTarget = inferenceVisitor.findInterfaceMember(
6063+
type, containsKeyName, fileOffset,
6064+
callSiteAccessKind: CallSiteAccessKind.methodInvocation);
6065+
bool typeCheckForTargetMapNeeded =
6066+
!inferenceVisitor.isAssignable(type, matchedType) ||
6067+
matchedType is DynamicType;
6068+
6069+
// mapVariable: `matchedType` MVAR = `matchedExpression`
6070+
VariableDeclaration mapVariable = inferenceVisitor.engine.forest
6071+
.createVariableDeclarationForValue(matchedExpression,
6072+
type: matchedType);
6073+
6074+
Expression? keysCheck;
6075+
for (int i = entries.length - 1; i >= 0; i--) {
6076+
MapPatternEntry entry = entries[i];
6077+
ExpressionPattern keyPattern = entry.key as ExpressionPattern;
6078+
6079+
// containsKeyCheck: `mapVariable`.containsKey(`keyPattern.expression`)
6080+
// ==> MVAR.containsKey(`keyPattern.expression`)
6081+
Expression containsKeyCheck = new InstanceInvocation(
6082+
InstanceAccessKind.Instance,
6083+
inferenceVisitor.engine.forest
6084+
.createVariableGet(fileOffset, mapVariable)
6085+
..promotedType = typeCheckForTargetMapNeeded ? type : null,
6086+
containsKeyName,
6087+
inferenceVisitor.engine.forest
6088+
.createArguments(fileOffset, [keyPattern.expression]),
6089+
functionType: containsKeyTarget.getFunctionType(inferenceVisitor),
6090+
interfaceTarget: containsKeyTarget.member as Procedure)
6091+
..fileOffset = fileOffset;
6092+
6093+
if (keysCheck == null) {
6094+
// keyCheck: `containsKeyCheck`
6095+
keysCheck = containsKeyCheck;
6096+
} else {
6097+
// keyCheck: `containsKeyCheck` && `keyCheck`
6098+
keysCheck = inferenceVisitor.engine.forest.createLogicalExpression(
6099+
fileOffset, containsKeyCheck, doubleAmpersandName.text, keysCheck);
6100+
}
6101+
}
6102+
6103+
Expression? typeCheck;
6104+
if (typeCheckForTargetMapNeeded) {
6105+
// typeCheck: `mapVariable` is `targetMapType`
6106+
// ==> MVAR is Map<`keyType`, `valueType`>
6107+
typeCheck = inferenceVisitor.engine.forest.createIsExpression(
6108+
fileOffset,
6109+
inferenceVisitor.engine.forest
6110+
.createVariableGet(fileOffset, mapVariable),
6111+
type,
6112+
forNonNullableByDefault: inferenceVisitor.isNonNullableByDefault);
6113+
}
6114+
6115+
Expression? typeAndKeysCheck;
6116+
if (typeCheck != null && keysCheck != null) {
6117+
// typeAndKeysCheck: `typeCheck` && `keysCheck`
6118+
typeAndKeysCheck = inferenceVisitor.engine.forest.createLogicalExpression(
6119+
fileOffset, typeCheck, doubleAmpersandName.text, keysCheck);
6120+
} else if (typeCheck != null && keysCheck == null) {
6121+
typeAndKeysCheck = typeCheck;
6122+
} else if (typeCheck == null && keysCheck != null) {
6123+
typeAndKeysCheck = keysCheck;
6124+
} else {
6125+
typeAndKeysCheck = null;
6126+
}
6127+
6128+
ObjectAccessTarget valueAccess = inferenceVisitor.findInterfaceMember(
6129+
type, indexGetName, fileOffset,
6130+
callSiteAccessKind: CallSiteAccessKind.operatorInvocation);
6131+
FunctionType valueAccessFunctionType =
6132+
valueAccess.getFunctionType(inferenceVisitor);
6133+
PatternTransformationResult transformationResult =
6134+
new PatternTransformationResult([]);
6135+
List<VariableDeclaration> valueAccessVariables = [];
6136+
CloneVisitorNotMembers cloner = new CloneVisitorNotMembers();
6137+
for (MapPatternEntry entry in entries) {
6138+
ExpressionPattern keyPattern = entry.key as ExpressionPattern;
6139+
6140+
// [keyPattern.expression] can be cloned without caching because it's a
6141+
// const expression according to the spec, and the constant
6142+
// canonicalization will eliminate the duplicated code.
6143+
//
6144+
// mapValue: `mapVariable`[`keyPattern.expression`]
6145+
// ==> MVAR[`keyPattern.expression`]
6146+
Expression mapValue = new InstanceInvocation(
6147+
InstanceAccessKind.Instance,
6148+
inferenceVisitor.engine.forest
6149+
.createVariableGet(fileOffset, mapVariable)
6150+
..promotedType = typeCheckForTargetMapNeeded ? type : null,
6151+
indexGetName,
6152+
inferenceVisitor.engine.forest.createArguments(
6153+
fileOffset, [cloner.clone(keyPattern.expression)]),
6154+
functionType: valueAccessFunctionType,
6155+
interfaceTarget: valueAccess.member as Procedure);
6156+
6157+
// mapValueVariable: `valueType` VVAR = `mapValue`
6158+
// ==> `valueType` VVAR = MVAR[`keyPattern.expression`]
6159+
VariableDeclaration mapValueVariable = inferenceVisitor.engine.forest
6160+
.createVariableDeclarationForValue(mapValue, type: valueType);
6161+
6162+
PatternTransformationResult subpatternTransformationResult = entry.value
6163+
.transform(
6164+
inferenceVisitor.engine.forest
6165+
.createVariableGet(fileOffset, mapValueVariable),
6166+
valueType,
6167+
inferenceVisitor.engine.forest
6168+
.createVariableGet(fileOffset, mapValueVariable),
6169+
inferenceVisitor);
6170+
6171+
// If the sub-pattern transformation doesn't declare captured variables
6172+
// and consists of a single empty element, it means that it simply
6173+
// doesn't have a place where it could refer to the element expression.
6174+
// In that case we can avoid creating the intermediary variable for the
6175+
// element expression.
6176+
//
6177+
// An example of such sub-pattern is in the following:
6178+
//
6179+
// if (x case {"key": var _}) { /* ... */ }
6180+
if (entry.value.declaredVariables.isNotEmpty ||
6181+
!(subpatternTransformationResult.elements.length == 1 &&
6182+
subpatternTransformationResult.elements.single.isEmpty)) {
6183+
valueAccessVariables.add(mapValueVariable);
6184+
transformationResult = transformationResult.combine(
6185+
subpatternTransformationResult, inferenceVisitor);
6186+
}
6187+
}
6188+
6189+
transformationResult = transformationResult.prependElement(
6190+
new PatternTransformationElement(
6191+
condition: typeAndKeysCheck,
6192+
variableInitializers: valueAccessVariables),
6193+
inferenceVisitor);
6194+
6195+
transformationResult = transformationResult.prependElement(
6196+
new PatternTransformationElement(
6197+
condition: null, variableInitializers: [mapVariable]),
6198+
inferenceVisitor);
6199+
6200+
return transformationResult;
60596201
}
60606202
}
60616203

pkg/front_end/lib/src/fasta/names.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,5 @@ final Name hashCodeName = new Name("hashCode");
7171
final Name toStringName = new Name("toString");
7272

7373
final Name runtimeTypeName = new Name("runtimeType");
74+
75+
final Name containsKeyName = new Name("containsKey");

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8883,7 +8883,20 @@ class InferenceVisitorImpl extends InferenceVisitorBase
88838883
required DartType matchedType,
88848884
required SharedMatchContext context,
88858885
}) {
8886-
// TODO(cstefantsova): Implement visitMapPattern.
8886+
// TODO(cstefantsova): Use [analyzeMapPattern] when it's available.
8887+
// Until then, an ad-hoc inference is used.
8888+
DartType keyType = const DynamicType();
8889+
DartType valueType = const DynamicType();
8890+
if (matchedType is InterfaceType) {
8891+
List<DartType>? typeArguments = hierarchyBuilder
8892+
.getTypeArgumentsAsInstanceOf(matchedType, coreTypes.mapClass);
8893+
if (typeArguments != null) {
8894+
keyType = typeArguments[0];
8895+
valueType = typeArguments[1];
8896+
}
8897+
}
8898+
pattern.type = new InterfaceType(coreTypes.mapClass,
8899+
Nullability.nonNullable, <DartType>[keyType, valueType]);
88878900
return const PatternInferenceResult();
88888901
}
88898902

pkg/front_end/test/spell_checking_list_code.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,7 @@ msdn
883883
msg
884884
murmur
885885
mus
886+
mvar
886887
n
887888
na
888889
nameless
@@ -1631,6 +1632,7 @@ vm's
16311632
vn
16321633
vs
16331634
vtab
1635+
vvar
16341636
w
16351637
waiting
16361638
walker

pkg/front_end/test/spell_checking_list_common.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,6 +1604,7 @@ interests
16041604
interface
16051605
interfaces
16061606
interferes
1607+
intermediary
16071608
intern
16081609
internal
16091610
internally

pkg/front_end/testcases/patterns/map_pattern_inside_if_case.dart

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
test(dynamic x) {
5+
abstract class MyMap implements Map<String, int> {}
6+
7+
test1(dynamic x) {
68
if (x case {'a': 1, 'b': 2}) {}
79
}
10+
11+
test2(dynamic x) {
12+
if (x case {1: 2, "foo": "bar"}) {
13+
return 0;
14+
} else {
15+
return 1;
16+
}
17+
}
18+
19+
test3(Map<bool, double> x) {
20+
if (x case {true: 3.14}) {}
21+
if (x case {}) {}
22+
}
23+
24+
test4(MyMap x) {
25+
if (x case {"one": 1, "two": 2}) {}
26+
}
27+
28+
test5(dynamic x) {
29+
if (x case {"one": var y1, "two": String y2!}) {
30+
return 0;
31+
} else {
32+
return 1;
33+
}
34+
}
Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,82 @@
11
library /*isNonNullableByDefault*/;
22
import self as self;
3+
import "dart:core" as core;
34

4-
static method test(dynamic x) → dynamic {
5+
abstract class MyMap extends core::Object implements core::Map<core::String, core::int> {
6+
synthetic constructor •() → self::MyMap
7+
: super core::Object::•()
8+
;
9+
}
10+
static method test1(dynamic x) → dynamic {
511
final dynamic #t1 = x;
6-
if(invalid-expression "Unimplemented MapPattern.transform") {
12+
final dynamic #t2 = #t1;
13+
if(#t2 is{ForNonNullableByDefault} core::Map<dynamic, dynamic> && (#t2{core::Map<dynamic, dynamic>}.{core::Map::containsKey}("a"){(core::Object?) → core::bool} && #t2{core::Map<dynamic, dynamic>}.{core::Map::containsKey}("b"){(core::Object?) → core::bool})) {
14+
final dynamic #t3 = #t2{core::Map<dynamic, dynamic>}.{core::Map::[]}("a"){(core::Object?) → dynamic};
15+
final dynamic #t4 = #t2{core::Map<dynamic, dynamic>}.{core::Map::[]}("b"){(core::Object?) → dynamic};
16+
if(#t3 =={core::Object::==}{(core::Object) → core::bool} 1 && #t4 =={core::Object::==}{(core::Object) → core::bool} 2) {
17+
}
18+
}
19+
}
20+
static method test2(dynamic x) → dynamic {
21+
final dynamic #t5 = x;
22+
final core::bool #t6 = true;
23+
final dynamic #t7 = #t5;
24+
if(#t7 is{ForNonNullableByDefault} core::Map<dynamic, dynamic> && (#t7{core::Map<dynamic, dynamic>}.{core::Map::containsKey}(1){(core::Object?) → core::bool} && #t7{core::Map<dynamic, dynamic>}.{core::Map::containsKey}("foo"){(core::Object?) → core::bool})) {
25+
final dynamic #t8 = #t7{core::Map<dynamic, dynamic>}.{core::Map::[]}(1){(core::Object?) → dynamic};
26+
final dynamic #t9 = #t7{core::Map<dynamic, dynamic>}.{core::Map::[]}("foo"){(core::Object?) → dynamic};
27+
if(#t8 =={core::Object::==}{(core::Object) → core::bool} 2 && #t9 =={core::Object::==}{(core::Object) → core::bool} "bar") {
28+
#t6 = false;
29+
{
30+
return 0;
31+
}
32+
}
33+
}
34+
if(#t6) {
35+
return 1;
36+
}
37+
}
38+
static method test3(core::Map<core::bool, core::double> x) → dynamic {
39+
final core::Map<core::bool, core::double> #t10 = x;
40+
final core::Map<core::bool, core::double> #t11 = #t10;
41+
if(#t11.{core::Map::containsKey}(true){(core::Object?) → core::bool}) {
42+
final core::double #t12 = #t11.{core::Map::[]}(true){(core::Object?) → core::double?};
43+
if(#t12 =={core::num::==}{(core::Object) → core::bool} 3.14) {
44+
}
45+
}
46+
final core::Map<core::bool, core::double> #t13 = x;
47+
final core::Map<core::bool, core::double> #t14 = #t13;
48+
}
49+
static method test4(self::MyMap x) → dynamic {
50+
final self::MyMap #t15 = x;
51+
final self::MyMap #t16 = #t15;
52+
if(#t16.{core::Map::containsKey}("one"){(core::Object?) → core::bool} && #t16.{core::Map::containsKey}("two"){(core::Object?) → core::bool}) {
53+
final core::int #t17 = #t16.{core::Map::[]}("one"){(core::Object?) → core::int?};
54+
final core::int #t18 = #t16.{core::Map::[]}("two"){(core::Object?) → core::int?};
55+
if(#t17 =={core::num::==}{(core::Object) → core::bool} 1 && #t18 =={core::num::==}{(core::Object) → core::bool} 2) {
56+
}
57+
}
58+
}
59+
static method test5(dynamic x) → dynamic {
60+
final dynamic #t19 = x;
61+
final core::bool #t20 = true;
62+
final dynamic #t21 = #t19;
63+
if(#t21 is{ForNonNullableByDefault} core::Map<dynamic, dynamic> && (#t21{core::Map<dynamic, dynamic>}.{core::Map::containsKey}("one"){(core::Object?) → core::bool} && #t21{core::Map<dynamic, dynamic>}.{core::Map::containsKey}("two"){(core::Object?) → core::bool})) {
64+
final dynamic #t22 = #t21{core::Map<dynamic, dynamic>}.{core::Map::[]}("one"){(core::Object?) → dynamic};
65+
final dynamic #t23 = #t21{core::Map<dynamic, dynamic>}.{core::Map::[]}("two"){(core::Object?) → dynamic};
66+
final dynamic #t24 = #t23!;
67+
final dynamic #t25 = #t24;
68+
if(#t25 is core::String) {
69+
#t20 = false;
70+
{
71+
dynamic y1 = #t22;
72+
core::String y2 = #t25{core::String};
73+
{
74+
return 0;
75+
}
76+
}
77+
}
78+
}
79+
if(#t20) {
80+
return 1;
781
}
882
}

0 commit comments

Comments
 (0)