Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 1da7c0d

Browse files
Dmitry Stefantsovcommit-bot@chromium.org
authored andcommitted
[cfe] Fix type checks in spreads within lists and sets
Change-Id: Id0eb5bc798fdd425cf1bc46d60efb3a1d5455938 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/95383 Commit-Queue: Dmitry Stefantsov <dmitryas@google.com> Reviewed-by: Kevin Millikin <kmillikin@google.com>
1 parent 09a1cf6 commit 1da7c0d

14 files changed

+378
-183
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8224,6 +8224,39 @@ ${num3} ms/compilation unit.""",
82248224
});
82258225
}
82268226

8227+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
8228+
const Template<
8229+
Message Function(
8230+
DartType _type,
8231+
DartType
8232+
_type2)> templateSpreadElementTypeMismatch = const Template<
8233+
Message Function(DartType _type, DartType _type2)>(
8234+
messageTemplate:
8235+
r"""Can't assign spread elements of type '#type' to collection elements of type '#type2'.""",
8236+
withArguments: _withArgumentsSpreadElementTypeMismatch);
8237+
8238+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
8239+
const Code<Message Function(DartType _type, DartType _type2)>
8240+
codeSpreadElementTypeMismatch =
8241+
const Code<Message Function(DartType _type, DartType _type2)>(
8242+
"SpreadElementTypeMismatch", templateSpreadElementTypeMismatch,
8243+
analyzerCodes: <String>["LIST_ELEMENT_TYPE_NOT_ASSIGNABLE"]);
8244+
8245+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
8246+
Message _withArgumentsSpreadElementTypeMismatch(
8247+
DartType _type, DartType _type2) {
8248+
TypeLabeler labeler = new TypeLabeler();
8249+
List<Object> typeParts = labeler.labelType(_type);
8250+
List<Object> type2Parts = labeler.labelType(_type2);
8251+
String type = typeParts.join();
8252+
String type2 = type2Parts.join();
8253+
return new Message(codeSpreadElementTypeMismatch,
8254+
message:
8255+
"""Can't assign spread elements of type '${type}' to collection elements of type '${type2}'.""" +
8256+
labeler.originMessages,
8257+
arguments: {'type': _type, 'type2': _type2});
8258+
}
8259+
82278260
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
82288261
const Template<
82298262
Message Function(

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

Lines changed: 114 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,17 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
653653
inferrer.inferStatement(node.body);
654654
}
655655

656+
DartType getSpreadElementType(DartType spreadType) {
657+
if (spreadType is InterfaceType) {
658+
InterfaceType supertype = inferrer.typeSchemaEnvironment
659+
.getTypeAsInstanceOf(spreadType, inferrer.coreTypes.iterableClass);
660+
if (supertype == null) return null;
661+
return supertype.typeArguments[0];
662+
}
663+
if (spreadType is DynamicType) return const DynamicType();
664+
return null;
665+
}
666+
656667
void visitListLiteralJudgment(
657668
ListLiteralJudgment node, DartType typeContext) {
658669
var listClass = inferrer.coreTypes.listClass;
@@ -676,6 +687,8 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
676687
} else {
677688
inferredTypeArgument = node.typeArgument;
678689
}
690+
List<DartType> spreadTypes =
691+
typeChecksNeeded ? new List<DartType>(node.expressions.length) : null;
679692
if (inferenceNeeded || typeChecksNeeded) {
680693
for (int i = 0; i < node.expressions.length; ++i) {
681694
Expression judgment = node.expressions[i];
@@ -689,30 +702,12 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
689702
if (inferenceNeeded) {
690703
formalTypes.add(listType.typeArguments[0]);
691704
}
692-
if (spreadType is InterfaceType) {
693-
InterfaceType iterableType = inferrer.typeSchemaEnvironment
694-
.getTypeAsInstanceOf(
695-
spreadType, inferrer.coreTypes.iterableClass);
696-
if (iterableType != null) {
697-
actualTypes.add(iterableType.typeArguments[0]);
698-
} else {
699-
inferrer.helper.buildProblem(
700-
templateSpreadTypeMismatch.withArguments(spreadType),
701-
judgment.expression.fileOffset,
702-
1);
703-
// Use 'dynamic' for error recovery.
704-
actualTypes.add(const DynamicType());
705-
}
706-
} else if (spreadType is DynamicType) {
707-
actualTypes.add(const DynamicType());
708-
} else {
709-
inferrer.helper.buildProblem(
710-
templateSpreadTypeMismatch.withArguments(spreadType),
711-
judgment.expression.fileOffset,
712-
1);
713-
// Use 'dynamic' for error recovery.
714-
actualTypes.add(const DynamicType());
705+
if (typeChecksNeeded) {
706+
spreadTypes[i] = spreadType;
715707
}
708+
// Use 'dynamic' for error recovery.
709+
actualTypes
710+
.add(getSpreadElementType(spreadType) ?? const DynamicType());
716711
} else {
717712
inferrer.inferExpression(judgment, inferredTypeArgument,
718713
inferenceNeeded || typeChecksNeeded,
@@ -742,9 +737,44 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
742737
}
743738
if (typeChecksNeeded) {
744739
for (int i = 0; i < node.expressions.length; i++) {
745-
inferrer.ensureAssignable(node.typeArgument, actualTypes[i],
746-
node.expressions[i], node.expressions[i].fileOffset,
747-
isVoidAllowed: node.typeArgument is VoidType);
740+
Expression item = node.expressions[i];
741+
if (item is SpreadElement) {
742+
DartType spreadType = spreadTypes[i];
743+
DartType spreadElementType = getSpreadElementType(spreadType);
744+
if (spreadElementType == null) {
745+
node.replaceChild(
746+
node.expressions[i],
747+
inferrer.helper.desugarSyntheticExpression(inferrer.helper
748+
.buildProblem(
749+
templateSpreadTypeMismatch.withArguments(spreadType),
750+
item.expression.fileOffset,
751+
1)));
752+
} else if (spreadType is DynamicType) {
753+
inferrer.ensureAssignable(inferrer.coreTypes.iterableClass.rawType,
754+
spreadType, item.expression, item.expression.fileOffset);
755+
} else if (spreadType is InterfaceType) {
756+
if (spreadType.classNode == inferrer.coreTypes.nullClass) {
757+
// TODO(dmitryas): Handle this case when null-aware spreads are
758+
// supported by the parser.
759+
} else {
760+
if (!inferrer.isAssignable(
761+
node.typeArgument, spreadElementType)) {
762+
node.replaceChild(
763+
node.expressions[i],
764+
inferrer.helper.desugarSyntheticExpression(inferrer.helper
765+
.buildProblem(
766+
templateSpreadElementTypeMismatch.withArguments(
767+
spreadElementType, node.typeArgument),
768+
item.expression.fileOffset,
769+
1)));
770+
}
771+
}
772+
}
773+
} else {
774+
inferrer.ensureAssignable(
775+
node.typeArgument, actualTypes[i], item, item.fileOffset,
776+
isVoidAllowed: node.typeArgument is VoidType);
777+
}
748778
}
749779
}
750780
node.inferredType = new InterfaceType(listClass, [inferredTypeArgument]);
@@ -755,8 +785,6 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
755785
inferred: true);
756786
}
757787

758-
// Now, compile non-const list literals to block expressions if they
759-
// contain any spread or control-flow elements.
760788
return null;
761789
}
762790

@@ -1141,6 +1169,8 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
11411169
} else {
11421170
inferredTypeArgument = node.typeArgument;
11431171
}
1172+
List<DartType> spreadTypes =
1173+
typeChecksNeeded ? new List<DartType>(node.expressions.length) : null;
11441174
if (inferenceNeeded || typeChecksNeeded) {
11451175
for (int i = 0; i < node.expressions.length; ++i) {
11461176
Expression judgment = node.expressions[i];
@@ -1154,35 +1184,12 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
11541184
if (inferenceNeeded) {
11551185
formalTypes.add(setType.typeArguments[0]);
11561186
}
1157-
if (spreadType is InterfaceType) {
1158-
InterfaceType iterableType = inferrer.typeSchemaEnvironment
1159-
.getTypeAsInstanceOf(
1160-
spreadType, inferrer.coreTypes.iterableClass);
1161-
if (iterableType != null) {
1162-
actualTypes.add(iterableType.typeArguments[0]);
1163-
} else {
1164-
inferrer.helper.buildProblem(
1165-
templateSpreadTypeMismatch.withArguments(spreadType),
1166-
judgment.expression.fileOffset,
1167-
1);
1168-
// Use 'dynamic' for error recovery.
1169-
actualTypes.add(const DynamicType());
1170-
}
1171-
} else if (spreadType is DynamicType) {
1172-
actualTypes.add(const DynamicType());
1173-
} else {
1174-
inferrer.helper.buildProblem(
1175-
templateSpreadTypeMismatch.withArguments(spreadType),
1176-
judgment.expression.fileOffset,
1177-
1);
1178-
// Use 'dynamic' for error recovery.
1179-
actualTypes.add(const DynamicType());
1187+
if (typeChecksNeeded) {
1188+
spreadTypes[i] = spreadType;
11801189
}
1181-
1182-
node.replaceChild(
1183-
judgment,
1184-
InvalidExpression('unimplemented spread element')
1185-
..fileOffset = node.fileOffset);
1190+
// Use 'dynamic' for error recovery.
1191+
actualTypes
1192+
.add(getSpreadElementType(spreadType) ?? const DynamicType());
11861193
} else {
11871194
inferrer.inferExpression(judgment, inferredTypeArgument,
11881195
inferenceNeeded || typeChecksNeeded,
@@ -1212,9 +1219,57 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
12121219
}
12131220
if (typeChecksNeeded) {
12141221
for (int i = 0; i < node.expressions.length; i++) {
1215-
inferrer.ensureAssignable(node.typeArgument, actualTypes[i],
1216-
node.expressions[i], node.expressions[i].fileOffset,
1217-
isVoidAllowed: node.typeArgument is VoidType);
1222+
Expression item = node.expressions[i];
1223+
if (item is SpreadElement) {
1224+
// TODO(dmitrayas): Remove this flag and all related uses of it
1225+
// when the desugaring is implemented.
1226+
bool replaced = false;
1227+
1228+
DartType spreadType = spreadTypes[i];
1229+
DartType spreadElementType = getSpreadElementType(spreadType);
1230+
if (spreadElementType == null) {
1231+
node.replaceChild(
1232+
node.expressions[i],
1233+
inferrer.helper.desugarSyntheticExpression(inferrer.helper
1234+
.buildProblem(
1235+
templateSpreadTypeMismatch.withArguments(spreadType),
1236+
item.expression.fileOffset,
1237+
1)));
1238+
replaced = true;
1239+
} else if (spreadType is DynamicType) {
1240+
inferrer.ensureAssignable(inferrer.coreTypes.iterableClass.rawType,
1241+
spreadType, item.expression, item.expression.fileOffset);
1242+
} else if (spreadType is InterfaceType) {
1243+
if (spreadType.classNode == inferrer.coreTypes.nullClass) {
1244+
// TODO(dmitryas): Handle this case when null-aware spreads are
1245+
// supported by the parser.
1246+
} else {
1247+
if (!inferrer.isAssignable(
1248+
node.typeArgument, spreadElementType)) {
1249+
node.replaceChild(
1250+
node.expressions[i],
1251+
inferrer.helper.desugarSyntheticExpression(inferrer.helper
1252+
.buildProblem(
1253+
templateSpreadElementTypeMismatch.withArguments(
1254+
spreadElementType, node.typeArgument),
1255+
item.expression.fileOffset,
1256+
1)));
1257+
replaced = true;
1258+
}
1259+
}
1260+
}
1261+
1262+
if (!replaced) {
1263+
node.replaceChild(
1264+
node.expressions[i],
1265+
new InvalidExpression('unimplemented spread element')
1266+
..fileOffset = node.expressions[i].fileOffset);
1267+
}
1268+
} else {
1269+
inferrer.ensureAssignable(node.typeArgument, actualTypes[i],
1270+
node.expressions[i], node.expressions[i].fileOffset,
1271+
isVoidAllowed: node.typeArgument is VoidType);
1272+
}
12181273
}
12191274
}
12201275
node.inferredType = new InterfaceType(setClass, [inferredTypeArgument]);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import '../fasta_codes.dart'
4242
templateForInLoopElementTypeNotAssignable,
4343
templateForInLoopTypeNotIterable,
4444
templateIntegerLiteralIsOutOfRange,
45+
templateSpreadElementTypeMismatch,
4546
templateSpreadTypeMismatch,
4647
templateSwitchExpressionNotAssignable,
4748
templateWebLiteralCannotBeRepresentedExactly;

pkg/front_end/messages.status

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ SourceBodySummary/analyzerCode: Fail
336336
SourceBodySummary/example: Fail
337337
SourceOutlineSummary/analyzerCode: Fail
338338
SourceOutlineSummary/example: Fail
339+
SpreadElementTypeMismatch/script: Fail # Can't be tested until 'spread-collections' flag is on.
339340
SpreadTypeMismatch/analyzerCode: Fail # There's not analyzer code for that error yet.
340341
SpreadTypeMismatch/script1: Fail # Can't be tested until 'spread-collections' flag is on.
341342
SpreadTypeMismatch/script2: Fail # Can't be tested until 'spread-collections' flag is on.

pkg/front_end/messages.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3484,3 +3484,12 @@ SpreadTypeMismatch:
34843484
int Function() a = null;
34853485
var b = [...a];
34863486
}
3487+
3488+
SpreadElementTypeMismatch:
3489+
template: "Can't assign spread elements of type '#type' to collection elements of type '#type2'."
3490+
analyzerCode: LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
3491+
script: >
3492+
main() {
3493+
List<String> a = <String>["foo"];
3494+
List<int> b = <int>[...a];
3495+
}

pkg/front_end/testcases/spread_collection.dart.strong.expect

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ static method main() → dynamic {
77
final core::List<core::int> aList = block {
88
final core::List<core::int> #t1 = <core::int>[];
99
#t1.{core::List::add}(1);
10-
#t1.{core::List::addAll}(<core::int>[2]);
11-
#t1.{core::List::addAll}(<core::int>[3]);
10+
for (final core::int #t2 in <core::int>[2])
11+
#t1.{core::List::add}(#t2);
12+
for (final core::int #t3 in <core::int>[3])
13+
#t1.{core::List::add}(#t3);
1214
} =>#t1;
1315
final core::Map<core::int, core::int> aMap = <core::int, core::int>{1: 1, invalid-expression "unimplemented spread entry": null, invalid-expression "unimplemented spread entry": null};
14-
final core::Set<core::int> aSet = let final core::Set<core::int> #t2 = col::LinkedHashSet::•<core::int>() in let final dynamic #t3 = #t2.{core::Set::add}(1) in let final dynamic #t4 = #t2.{core::Set::add}(invalid-expression "unimplemented spread element") in let final dynamic #t5 = #t2.{core::Set::add}(invalid-expression "unimplemented spread element") in #t2;
16+
final core::Set<core::int> aSet = let final core::Set<core::int> #t4 = col::LinkedHashSet::•<core::int>() in let final dynamic #t5 = #t4.{core::Set::add}(1) in let final dynamic #t6 = #t4.{core::Set::add}(invalid-expression "unimplemented spread element") in let final dynamic #t7 = #t4.{core::Set::add}(invalid-expression "unimplemented spread element") in #t4;
1517
final core::Map<dynamic, dynamic> aSetOrMap = <dynamic, dynamic>{invalid-expression "unimplemented spread entry": null};
1618
core::print(aList);
1719
core::print(aSet);

pkg/front_end/testcases/spread_collection.dart.strong.transformed.expect

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ static method main() → dynamic {
77
final core::List<core::int> aList = block {
88
final core::List<core::int> #t1 = <core::int>[];
99
#t1.{core::List::add}(1);
10-
#t1.{core::List::addAll}(<core::int>[2]);
11-
#t1.{core::List::addAll}(<core::int>[3]);
10+
for (final core::int #t2 in <core::int>[2])
11+
#t1.{core::List::add}(#t2);
12+
for (final core::int #t3 in <core::int>[3])
13+
#t1.{core::List::add}(#t3);
1214
} =>#t1;
1315
final core::Map<core::int, core::int> aMap = <core::int, core::int>{1: 1, invalid-expression "unimplemented spread entry": null, invalid-expression "unimplemented spread entry": null};
14-
final core::Set<core::int> aSet = let final core::Set<core::int> #t2 = col::LinkedHashSet::•<core::int>() in let final core::bool #t3 = #t2.{core::Set::add}(1) in let final core::bool #t4 = #t2.{core::Set::add}(invalid-expression "unimplemented spread element") in let final core::bool #t5 = #t2.{core::Set::add}(invalid-expression "unimplemented spread element") in #t2;
16+
final core::Set<core::int> aSet = let final core::Set<core::int> #t4 = col::LinkedHashSet::•<core::int>() in let final core::bool #t5 = #t4.{core::Set::add}(1) in let final core::bool #t6 = #t4.{core::Set::add}(invalid-expression "unimplemented spread element") in let final core::bool #t7 = #t4.{core::Set::add}(invalid-expression "unimplemented spread element") in #t4;
1517
final core::Map<dynamic, dynamic> aSetOrMap = <dynamic, dynamic>{invalid-expression "unimplemented spread entry": null};
1618
core::print(aList);
1719
core::print(aSet);

pkg/front_end/testcases/spread_collection_inference.dart

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,22 @@ foo() {
4141

4242
int set30 = /*@error=InvalidAssignment*/ /*@typeArgs=int*/ {...spread, 42};
4343

44-
var /*@type=List<dynamic>*/ lhs40 = /*@typeArgs=dynamic*/ [...
45-
/*@error=SpreadTypeMismatch*/ notSpreadInt];
44+
List<dynamic> lhs40 = <dynamic>[... /*@error=SpreadTypeMismatch*/
45+
notSpreadInt];
4646

47-
var /*@type=Set<dynamic>*/ set40 = /*@typeArgs=dynamic*/ {...
48-
/*@error=SpreadTypeMismatch*/ notSpreadInt, 42};
47+
Set<dynamic> set40 = <dynamic>{... /*@error=SpreadTypeMismatch*/
48+
notSpreadInt};
4949

50-
var /*@type=List<dynamic>*/ lhs50 = /*@typeArgs=dynamic*/ [...
51-
/*@error=SpreadTypeMismatch*/ notSpreadFunction];
50+
List<dynamic> lhs50 = <dynamic> [... /*@error=SpreadTypeMismatch*/
51+
notSpreadFunction];
5252

53-
var /*@type=Set<dynamic>*/ set50 = /*@typeArgs=dynamic*/ {...
54-
/*@error=SpreadTypeMismatch*/ notSpreadFunction, 42};
53+
Set<dynamic> set50 = <dynamic> {... /*@error=SpreadTypeMismatch*/
54+
notSpreadFunction};
55+
56+
List<String> lhs60 = <String>[... /*@error=SpreadElementTypeMismatch*/
57+
spread];
58+
59+
Set<String> set60 = <String>{... /*@error=SpreadElementTypeMismatch*/ spread};
5560
}
5661

5762
main() {}

0 commit comments

Comments
 (0)