Skip to content

Commit 1314cd0

Browse files
chloestefantsovaCommit Queue
authored and
Commit Queue
committed
[cfe] Add basic support for if-case elements in maps
Part of #49749 Change-Id: Ia75f8bf80e2014f8e8ab3b204226945b9f7a9d8a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/283361 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent 97d085c commit 1314cd0

13 files changed

+599
-27
lines changed

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

+22-6
Original file line numberDiff line numberDiff line change
@@ -6597,17 +6597,28 @@ class BodyBuilder extends StackListenerImpl
65976597
exitLocalScope(expectedScopeKinds: const [ScopeKind.ifElement]);
65986598
Token ifToken = pop() as Token;
65996599

6600+
PatternGuard? patternGuard = condition.patternGuard;
66006601
TreeNode node;
66016602
if (thenEntry is MapLiteralEntry) {
66026603
if (elseEntry is MapLiteralEntry) {
6603-
node = forest.createIfMapEntry(offsetForToken(ifToken),
6604-
condition.expression, thenEntry, elseEntry);
6604+
if (patternGuard == null) {
6605+
node = forest.createIfMapEntry(offsetForToken(ifToken),
6606+
condition.expression, thenEntry, elseEntry);
6607+
} else {
6608+
node = forest.createIfCaseMapEntry(offsetForToken(ifToken),
6609+
condition.expression, patternGuard, thenEntry, elseEntry);
6610+
}
66056611
} else if (elseEntry is ControlFlowElement) {
66066612
MapLiteralEntry? elseMapEntry = elseEntry
66076613
.toMapLiteralEntry(typeInferrer.assignedVariables.reassignInfo);
66086614
if (elseMapEntry != null) {
6609-
node = forest.createIfMapEntry(offsetForToken(ifToken),
6610-
condition.expression, thenEntry, elseMapEntry);
6615+
if (patternGuard == null) {
6616+
node = forest.createIfMapEntry(offsetForToken(ifToken),
6617+
condition.expression, thenEntry, elseMapEntry);
6618+
} else {
6619+
node = forest.createIfCaseMapEntry(offsetForToken(ifToken),
6620+
condition.expression, patternGuard, thenEntry, elseMapEntry);
6621+
}
66116622
} else {
66126623
int offset = elseEntry.fileOffset;
66136624
node = new MapLiteralEntry(
@@ -6631,8 +6642,13 @@ class BodyBuilder extends StackListenerImpl
66316642
MapLiteralEntry? thenMapEntry = thenEntry
66326643
.toMapLiteralEntry(typeInferrer.assignedVariables.reassignInfo);
66336644
if (thenMapEntry != null) {
6634-
node = forest.createIfMapEntry(offsetForToken(ifToken),
6635-
condition.expression, thenMapEntry, elseEntry);
6645+
if (patternGuard == null) {
6646+
node = forest.createIfMapEntry(offsetForToken(ifToken),
6647+
condition.expression, thenMapEntry, elseEntry);
6648+
} else {
6649+
node = forest.createIfCaseMapEntry(offsetForToken(ifToken),
6650+
condition.expression, patternGuard, thenMapEntry, elseEntry);
6651+
}
66366652
} else {
66376653
int offset = thenEntry.fileOffset;
66386654
node = new MapLiteralEntry(

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

+1
Original file line numberDiff line numberDiff line change
@@ -4861,6 +4861,7 @@ class IfCaseMapEntry extends TreeNode
48614861
PatternGuard patternGuard;
48624862
MapLiteralEntry then;
48634863
MapLiteralEntry? otherwise;
4864+
late List<Statement> replacement;
48644865

48654866
IfCaseMapEntry(
48664867
this.expression, this.patternGuard, this.then, this.otherwise) {

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

+194-21
Original file line numberDiff line numberDiff line change
@@ -2197,9 +2197,9 @@ class InferenceVisitorImpl extends InferenceVisitorBase
21972197
int? stackBase;
21982198
assert(checkStackBase(element, stackBase = stackHeight));
21992199

2200-
CollectionElementInferenceContext context =
2201-
new CollectionElementInferenceContext(
2202-
typeContext: inferredTypeArgument,
2200+
ListAndSetElementInferenceContext context =
2201+
new ListAndSetElementInferenceContext(
2202+
inferredTypeArgument: inferredTypeArgument,
22032203
inferredSpreadTypes: inferredSpreadTypes,
22042204
inferredConditionTypes: inferredConditionTypes);
22052205
analyzeIfCaseElement(
@@ -2972,6 +2972,9 @@ class InferenceVisitorImpl extends InferenceVisitorBase
29722972
entry, receiverType, keyType, valueType, result, body);
29732973
} else if (entry is IfMapEntry) {
29742974
_translateIfEntry(entry, receiverType, keyType, valueType, result, body);
2975+
} else if (entry is IfCaseMapEntry) {
2976+
_translateIfCaseEntry(
2977+
entry, receiverType, keyType, valueType, result, body);
29752978
} else if (entry is ForMapEntry) {
29762979
_translateForEntry(entry, receiverType, keyType, valueType, result, body);
29772980
} else if (entry is ForInMapEntry) {
@@ -3016,6 +3019,35 @@ class InferenceVisitorImpl extends InferenceVisitorBase
30163019
body.add(ifStatement);
30173020
}
30183021

3022+
void _translateIfCaseEntry(
3023+
IfCaseMapEntry entry,
3024+
InterfaceType receiverType,
3025+
DartType keyType,
3026+
DartType valueType,
3027+
VariableDeclaration result,
3028+
List<Statement> body) {
3029+
List<Statement> thenBody = [];
3030+
_translateEntry(
3031+
entry.then, receiverType, keyType, valueType, result, thenBody);
3032+
List<Statement>? elseBody;
3033+
if (entry.otherwise != null) {
3034+
_translateEntry(entry.otherwise!, receiverType, keyType, valueType,
3035+
result, elseBody = <Statement>[]);
3036+
}
3037+
Statement thenStatement =
3038+
thenBody.length == 1 ? thenBody.first : _createBlock(thenBody);
3039+
Statement? elseStatement;
3040+
if (elseBody != null && elseBody.isNotEmpty) {
3041+
elseStatement =
3042+
elseBody.length == 1 ? elseBody.first : _createBlock(elseBody);
3043+
}
3044+
IfStatement ifStatement = _createIf(
3045+
entry.fileOffset, entry.expression, thenStatement, elseStatement);
3046+
libraryBuilder.loader.dataForTesting?.registerAlias(entry, ifStatement);
3047+
body.addAll(entry.replacement);
3048+
body.add(ifStatement);
3049+
}
3050+
30193051
void _translateForEntry(
30203052
ForMapEntry entry,
30213053
InterfaceType receiverType,
@@ -3911,7 +3943,18 @@ class InferenceVisitorImpl extends InferenceVisitorBase
39113943
flowAnalysis.ifStatement_end(entry.otherwise != null);
39123944
return entry;
39133945
} else if (entry is IfCaseMapEntry) {
3914-
// TODO(cstefantsova): Pass an appropriate context message.
3946+
int? stackBase;
3947+
assert(checkStackBase(entry, stackBase = stackHeight));
3948+
3949+
MapEntryInferenceContext context = new MapEntryInferenceContext(
3950+
inferredKeyType: inferredKeyType,
3951+
inferredValueType: inferredValueType,
3952+
spreadContext: spreadContext,
3953+
actualTypes: actualTypes,
3954+
actualTypesForSet: actualTypesForSet,
3955+
offsets: offsets,
3956+
inferredSpreadTypes: inferredSpreadTypes,
3957+
inferredConditionTypes: inferredConditionTypes);
39153958
analyzeIfCaseElement(
39163959
node: entry,
39173960
expression: entry.expression,
@@ -3924,9 +3967,86 @@ class InferenceVisitorImpl extends InferenceVisitorBase
39243967
guard: entry.patternGuard.guard,
39253968
ifTrue: entry.then,
39263969
ifFalse: entry.otherwise,
3927-
context: null);
3928-
// TODO(cstefantsova): Implement inference for if-case map entries.
3929-
throw new UnimplementedError();
3970+
context: context);
3971+
if (entry.otherwise != null) {
3972+
DartType actualValueType = actualTypes.removeLast();
3973+
DartType actualKeyType = actualTypes.removeLast();
3974+
int length = actualTypes.length;
3975+
actualTypes[length - 2] = typeSchemaEnvironment.getStandardUpperBound(
3976+
actualKeyType, actualTypes[length - 2],
3977+
isNonNullableByDefault: libraryBuilder.isNonNullableByDefault);
3978+
actualTypes[length - 1] = typeSchemaEnvironment.getStandardUpperBound(
3979+
actualValueType, actualTypes[length - 1],
3980+
isNonNullableByDefault: libraryBuilder.isNonNullableByDefault);
3981+
DartType actualTypeForSet = actualTypesForSet.removeLast();
3982+
int lengthForSet = actualTypesForSet.length;
3983+
actualTypesForSet[lengthForSet - 1] =
3984+
typeSchemaEnvironment.getStandardUpperBound(
3985+
actualTypeForSet, actualTypesForSet[lengthForSet - 1],
3986+
isNonNullableByDefault: libraryBuilder.isNonNullableByDefault);
3987+
}
3988+
3989+
assert(checkStack(entry, stackBase, [
3990+
/* ifFalse = */ unionOfKinds(
3991+
[ValueKinds.MapLiteralEntryOrNull, ValueKinds.ExpressionOrNull]),
3992+
/* ifTrue = */ unionOfKinds(
3993+
[ValueKinds.MapLiteralEntry, ValueKinds.Expression]),
3994+
/* guard = */ ValueKinds.ExpressionOrNull,
3995+
/* pattern = */ ValueKinds.Pattern,
3996+
/* scrutinee = */ ValueKinds.Expression,
3997+
]));
3998+
3999+
Object? rewrite = popRewrite(NullValues.Expression);
4000+
if (!identical(entry.otherwise, rewrite)) {
4001+
entry.otherwise = (rewrite as MapLiteralEntry?)?..parent = entry;
4002+
}
4003+
4004+
rewrite = popRewrite();
4005+
if (!identical(entry.then, rewrite)) {
4006+
entry.then = (rewrite as MapLiteralEntry)..parent = entry;
4007+
}
4008+
4009+
PatternGuard patternGuard = entry.patternGuard;
4010+
rewrite = popRewrite(NullValues.Expression);
4011+
if (!identical(patternGuard.guard, rewrite)) {
4012+
patternGuard.guard = (rewrite as Expression?)?..parent = patternGuard;
4013+
}
4014+
4015+
rewrite = popRewrite();
4016+
if (!identical(patternGuard.pattern, rewrite)) {
4017+
patternGuard.pattern = (rewrite as Pattern)..parent = patternGuard;
4018+
}
4019+
4020+
rewrite = popRewrite();
4021+
if (!identical(entry.expression, rewrite)) {
4022+
entry.expression = (rewrite as Expression)..parent = patternGuard;
4023+
}
4024+
4025+
MatchingCache matchingCache = createMatchingCache();
4026+
MatchingExpressionVisitor matchingExpressionVisitor =
4027+
new MatchingExpressionVisitor(matchingCache);
4028+
// TODO(cstefantsova): Provide a more precise scrutinee type.
4029+
CacheableExpression matchedExpression = matchingCache
4030+
.createRootExpression(entry.expression, const DynamicType());
4031+
DelayedExpression matchingExpression = matchingExpressionVisitor
4032+
.visitPattern(entry.patternGuard.pattern, matchedExpression);
4033+
4034+
matchingExpression.registerUse();
4035+
4036+
Expression condition =
4037+
matchingExpression.createExpression(typeSchemaEnvironment);
4038+
Expression? guard = entry.patternGuard.guard;
4039+
if (guard != null) {
4040+
condition =
4041+
createAndExpression(condition, guard, fileOffset: guard.fileOffset);
4042+
}
4043+
entry.expression = condition;
4044+
entry.replacement = [
4045+
...entry.patternGuard.pattern.declaredVariables,
4046+
...matchingCache.declarations,
4047+
];
4048+
4049+
return entry;
39304050
} else if (entry is ForMapEntry) {
39314051
// TODO(johnniwinther): Use _visitStatements instead.
39324052
List<VariableDeclaration>? variables;
@@ -10587,17 +10707,37 @@ class InferenceVisitorImpl extends InferenceVisitorBase
1058710707
}
1058810708

1058910709
@override
10590-
void dispatchCollectionElement(covariant Expression element,
10710+
void dispatchCollectionElement(covariant TreeNode element,
1059110711
covariant CollectionElementInferenceContext context) {
10592-
ExpressionInferenceResult inferenceResult = inferElement(
10593-
element,
10594-
context.typeContext,
10595-
context.inferredSpreadTypes,
10596-
context.inferredConditionTypes);
10597-
// TODO(cstefantsova): Should the key to the map be [element] instead?
10598-
context.inferredConditionTypes[inferenceResult.expression] =
10599-
inferenceResult.inferredType;
10600-
pushRewrite(inferenceResult.expression);
10712+
if (element is Expression) {
10713+
context as ListAndSetElementInferenceContext;
10714+
ExpressionInferenceResult inferenceResult = inferElement(
10715+
element,
10716+
context.inferredTypeArgument,
10717+
context.inferredSpreadTypes,
10718+
context.inferredConditionTypes);
10719+
// TODO(cstefantsova): Should the key to the map be [element] instead?
10720+
context.inferredConditionTypes[inferenceResult.expression] =
10721+
inferenceResult.inferredType;
10722+
pushRewrite(inferenceResult.expression);
10723+
} else if (element is MapLiteralEntry) {
10724+
context as MapEntryInferenceContext;
10725+
element = inferMapEntry(
10726+
element,
10727+
element.parent!,
10728+
context.inferredKeyType,
10729+
context.inferredValueType,
10730+
context.spreadContext,
10731+
context.actualTypes,
10732+
context.actualTypesForSet,
10733+
context.inferredSpreadTypes,
10734+
context.inferredConditionTypes,
10735+
context.offsets);
10736+
pushRewrite(element);
10737+
} else {
10738+
problems.unsupported(
10739+
"${element.runtimeType}", element.fileOffset, helper.uri);
10740+
}
1060110741
}
1060210742

1060310743
@override
@@ -10811,13 +10951,46 @@ extension on SwitchCase {
1081110951
}
1081210952
}
1081310953

10814-
class CollectionElementInferenceContext {
10815-
DartType typeContext;
10954+
abstract class CollectionElementInferenceContext {
1081610955
Map<TreeNode, DartType> inferredSpreadTypes;
1081710956
Map<Expression, DartType> inferredConditionTypes;
1081810957

1081910958
CollectionElementInferenceContext(
10820-
{required this.typeContext,
10821-
required this.inferredSpreadTypes,
10959+
{required this.inferredSpreadTypes,
1082210960
required this.inferredConditionTypes});
1082310961
}
10962+
10963+
class ListAndSetElementInferenceContext
10964+
extends CollectionElementInferenceContext {
10965+
DartType inferredTypeArgument;
10966+
10967+
ListAndSetElementInferenceContext(
10968+
{required this.inferredTypeArgument,
10969+
required Map<TreeNode, DartType> inferredSpreadTypes,
10970+
required Map<Expression, DartType> inferredConditionTypes})
10971+
: super(
10972+
inferredSpreadTypes: inferredSpreadTypes,
10973+
inferredConditionTypes: inferredConditionTypes);
10974+
}
10975+
10976+
class MapEntryInferenceContext extends CollectionElementInferenceContext {
10977+
DartType inferredKeyType;
10978+
DartType inferredValueType;
10979+
DartType spreadContext;
10980+
List<DartType> actualTypes;
10981+
List<DartType> actualTypesForSet;
10982+
_MapLiteralEntryOffsets offsets;
10983+
10984+
MapEntryInferenceContext(
10985+
{required this.inferredKeyType,
10986+
required this.inferredValueType,
10987+
required this.spreadContext,
10988+
required this.actualTypes,
10989+
required this.actualTypesForSet,
10990+
required this.offsets,
10991+
required Map<TreeNode, DartType> inferredSpreadTypes,
10992+
required Map<Expression, DartType> inferredConditionTypes})
10993+
: super(
10994+
inferredSpreadTypes: inferredSpreadTypes,
10995+
inferredConditionTypes: inferredConditionTypes);
10996+
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import '../kernel/internal_ast.dart' as type;
1010
class NullValues {
1111
static const NullValue<type.Expression> Expression =
1212
const NullValue<type.Expression>();
13+
static const NullValue<type.MapLiteralEntry> MapLiteralEntry =
14+
const NullValue<type.MapLiteralEntry>();
1315
static const NullValue<type.Pattern> Pattern =
1416
const NullValue<type.Pattern>();
1517
static const NullValue<type.Statement> Statement =
@@ -23,6 +25,10 @@ class ValueKinds {
2325
const SingleValueKind<type.Expression>();
2426
static const SingleValueKind<type.Expression> ExpressionOrNull =
2527
const SingleValueKind<type.Expression>(NullValues.Expression);
28+
static const SingleValueKind<type.MapLiteralEntry> MapLiteralEntry =
29+
const SingleValueKind<type.MapLiteralEntry>();
30+
static const SingleValueKind<type.MapLiteralEntry> MapLiteralEntryOrNull =
31+
const SingleValueKind<type.MapLiteralEntry>(NullValues.MapLiteralEntry);
2632
static const SingleValueKind<type.MapPatternEntry> MapPatternEntry =
2733
const SingleValueKind<type.MapPatternEntry>();
2834
static const SingleValueKind<type.Pattern> Pattern =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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) => {1: 1, if (x case int y) 2: y, 3: 3};
6+
7+
test2(dynamic x) => {1: 1, if (x case String y) 2: y else 2: null, 3: 3};
8+
9+
test3(dynamic x) => {1: 1, if (x case bool b when b) 2: b, 3: 3};
10+
11+
main() {
12+
expectEquals(
13+
mapToString(test1(0)),
14+
mapToString({1: 1, 2: 0, 3: 3}),
15+
);
16+
expectEquals(
17+
mapToString(test1("foo")),
18+
mapToString({1: 1, 3: 3}),
19+
);
20+
21+
expectEquals(
22+
mapToString(test2("foo")),
23+
mapToString({1: 1, 2: "foo", 3: 3}),
24+
);
25+
expectEquals(
26+
mapToString(test2(false)),
27+
mapToString({1: 1, 2: null, 3: 3}),
28+
);
29+
30+
expectEquals(
31+
mapToString(test3(true)),
32+
mapToString({1: 1, 2: true, 3: 3}),
33+
);
34+
expectEquals(
35+
mapToString(test3(false)),
36+
mapToString({1: 1, 3: 3}),
37+
);
38+
}
39+
40+
expectEquals(x, y) {
41+
if (x != y) {
42+
throw "Expected '${x}' to be equal to '${y}'.";
43+
}
44+
}
45+
46+
mapToString(Map<dynamic, dynamic> map) {
47+
List<String> entryStrings = [
48+
for (var entry in map.entries)
49+
"${entry.key}:${entry.value}"
50+
];
51+
entryStrings.sort();
52+
return "{${entryStrings.join(',')}}";
53+
}

0 commit comments

Comments
 (0)