Skip to content

Commit c658b46

Browse files
chloestefantsovaCommit Queue
authored and
Commit Queue
committed
[cfe] Update UP for the case of two extension types
Closes #53290 Closes #53730 Change-Id: Ic3b720898b5877e3ab122cb904f0e9dbb9c63caa Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/330422 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent 4d1bdaa commit c658b46

File tree

6 files changed

+357
-69
lines changed

6 files changed

+357
-69
lines changed

pkg/front_end/lib/src/fasta/kernel/hierarchy/hierarchy_builder.dart

Lines changed: 111 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -132,69 +132,145 @@ class ClassHierarchyBuilder
132132
return getClassAsInstanceOf(subtype, superclass) != null;
133133
}
134134

135-
@override
136-
InterfaceType getLegacyLeastUpperBound(
137-
InterfaceType type1, InterfaceType type2,
135+
InterfaceType _getLegacyLeastUpperBoundInternal(
136+
/* InterfaceType | ExtensionType */ DartType type1,
137+
/* InterfaceType | ExtensionType */ DartType type2,
138+
List<ClassHierarchyNode> supertypeNodes1,
139+
List<ClassHierarchyNode> supertypeNodes2,
138140
{required bool isNonNullableByDefault}) {
139-
if (type1 == type2) return type1;
141+
assert(type1 is InterfaceType || type1 is ExtensionType);
142+
assert(type2 is InterfaceType || type2 is ExtensionType);
140143

141-
// LLUB(Null, List<dynamic>*) works differently for opt-in and opt-out
142-
// libraries. In opt-out libraries the legacy behavior is preserved, so
143-
// LLUB(Null, List<dynamic>*) = List<dynamic>*. In opt-out libraries the
144-
// rules imply that LLUB(Null, List<dynamic>*) = List<dynamic>?.
145-
if (!isNonNullableByDefault) {
146-
if (type1 is NullType) {
147-
return type2;
148-
}
149-
if (type2 is NullType) {
150-
return type1;
151-
}
152-
}
144+
// This is a workaround for the case when `Object?` should be found as the
145+
// upper bound of extension types. If the representation type of an
146+
// extension type is nullable, then `Object` is not a supertype of that
147+
// extension type, but `Object?` is.
148+
//
149+
// TODO(cstefantsova): Remove this workaround and account for extension
150+
// types with nullable representation types directly.
151+
Nullability type1NullabilityForResult = type1 is InterfaceType
152+
? type1.nullability
153+
: (type2.isPotentiallyNullable
154+
? Nullability.nullable
155+
: type2.nullability);
156+
Nullability type2NullabilityForResult = type2 is InterfaceType
157+
? type2.nullability
158+
: (type2.isPotentiallyNullable
159+
? Nullability.nullable
160+
: type2.nullability);
153161

154-
ClassHierarchyNode node1 = getNodeFromClass(type1.classNode);
155-
ClassHierarchyNode node2 = getNodeFromClass(type2.classNode);
156-
Set<ClassHierarchyNode> nodes1 = node1.computeAllSuperNodes(this).toSet();
157-
List<ClassHierarchyNode> nodes2 = node2.computeAllSuperNodes(this);
162+
Set<ClassHierarchyNode> supertypeNodesSet1 = supertypeNodes1.toSet();
158163
List<ClassHierarchyNode> common = <ClassHierarchyNode>[];
159164

160-
for (int i = 0; i < nodes2.length; i++) {
161-
ClassHierarchyNode node = nodes2[i];
165+
for (int i = 0; i < supertypeNodes2.length; i++) {
166+
ClassHierarchyNode node = supertypeNodes2[i];
162167
if (node.classBuilder.cls.isAnonymousMixin) {
163168
// Never find unnamed mixin application in least upper bound.
164169
continue;
165170
}
166-
if (nodes1.contains(node)) {
167-
DartType candidate1 = getTypeAsInstanceOf(type1, node.classBuilder.cls,
168-
isNonNullableByDefault: isNonNullableByDefault);
169-
DartType candidate2 = getTypeAsInstanceOf(type2, node.classBuilder.cls,
170-
isNonNullableByDefault: isNonNullableByDefault);
171+
if (supertypeNodesSet1.contains(node)) {
172+
DartType candidate1;
173+
if (type1 is InterfaceType) {
174+
candidate1 = getTypeAsInstanceOf(type1, node.classBuilder.cls,
175+
isNonNullableByDefault: isNonNullableByDefault);
176+
} else {
177+
type1 as ExtensionType;
178+
candidate1 = getExtensionTypeAsInstanceOfClass(
179+
type1, node.classBuilder.cls,
180+
isNonNullableByDefault: isNonNullableByDefault)!;
181+
}
182+
183+
DartType candidate2;
184+
if (type2 is InterfaceType) {
185+
candidate2 = getTypeAsInstanceOf(type2, node.classBuilder.cls,
186+
isNonNullableByDefault: isNonNullableByDefault);
187+
} else {
188+
type2 as ExtensionType;
189+
candidate2 = getExtensionTypeAsInstanceOfClass(
190+
type2, node.classBuilder.cls,
191+
isNonNullableByDefault: isNonNullableByDefault)!;
192+
}
171193
if (candidate1 == candidate2) {
172194
common.add(node);
173195
}
174196
}
175197
}
176198

177199
if (common.length == 1) {
178-
return coreTypes.objectRawType(
179-
uniteNullabilities(type1.nullability, type2.nullability));
200+
assert(common.single.classBuilder.cls == coreTypes.objectClass);
201+
return coreTypes.objectRawType(uniteNullabilities(
202+
type1NullabilityForResult, type2NullabilityForResult));
180203
}
181204
common.sort(ClassHierarchyNode.compareMaxInheritancePath);
182205

183206
for (int i = 0; i < common.length - 1; i++) {
184207
ClassHierarchyNode node = common[i];
185208
if (node.maxInheritancePath != common[i + 1].maxInheritancePath) {
186-
return getTypeAsInstanceOf(type1, node.classBuilder.cls,
187-
isNonNullableByDefault: isNonNullableByDefault)
188-
.withDeclaredNullability(
189-
uniteNullabilities(type1.nullability, type2.nullability));
209+
if (type1 is InterfaceType) {
210+
return getTypeAsInstanceOf(type1, node.classBuilder.cls,
211+
isNonNullableByDefault: isNonNullableByDefault)
212+
.withDeclaredNullability(uniteNullabilities(
213+
type1NullabilityForResult, type2NullabilityForResult));
214+
} else {
215+
type1 as ExtensionType;
216+
return getExtensionTypeAsInstanceOfClass(type1, node.classBuilder.cls,
217+
isNonNullableByDefault: isNonNullableByDefault)!
218+
.withDeclaredNullability(uniteNullabilities(
219+
type1NullabilityForResult, type2NullabilityForResult));
220+
}
190221
} else {
191222
do {
192223
i++;
193224
} while (node.maxInheritancePath == common[i + 1].maxInheritancePath);
194225
}
195226
}
196-
return coreTypes.objectRawType(
197-
uniteNullabilities(type1.nullability, type2.nullability));
227+
return coreTypes.objectRawType(uniteNullabilities(
228+
type1NullabilityForResult, type2NullabilityForResult));
229+
}
230+
231+
@override
232+
InterfaceType getLegacyLeastUpperBound(
233+
InterfaceType type1, InterfaceType type2,
234+
{required bool isNonNullableByDefault}) {
235+
if (type1 == type2) return type1;
236+
237+
// LLUB(Null, List<dynamic>*) works differently for opt-in and opt-out
238+
// libraries. In opt-out libraries the legacy behavior is preserved, so
239+
// LLUB(Null, List<dynamic>*) = List<dynamic>*. In opt-out libraries the
240+
// rules imply that LLUB(Null, List<dynamic>*) = List<dynamic>?.
241+
if (!isNonNullableByDefault) {
242+
if (type1 is NullType) {
243+
return type2;
244+
}
245+
if (type2 is NullType) {
246+
return type1;
247+
}
248+
}
249+
250+
return getLegacyLeastUpperBoundFromSupertypeLists(
251+
type1, type2, <InterfaceType>[type1], <InterfaceType>[type2],
252+
isNonNullableByDefault: isNonNullableByDefault);
253+
}
254+
255+
@override
256+
InterfaceType getLegacyLeastUpperBoundFromSupertypeLists(
257+
/* InterfaceType | ExtensionType */ DartType type1,
258+
/* InterfaceType | ExtensionType */ DartType type2,
259+
List<InterfaceType> supertypes1,
260+
List<InterfaceType> supertypes2,
261+
{required bool isNonNullableByDefault}) {
262+
List<ClassHierarchyNode> supertypeNodes1 = <ClassHierarchyNode>[
263+
for (InterfaceType supertype in supertypes1)
264+
...getNodeFromClass(supertype.classNode).computeAllSuperNodes(this)
265+
];
266+
List<ClassHierarchyNode> supertypeNodes2 = <ClassHierarchyNode>[
267+
for (InterfaceType supertype in supertypes2)
268+
...getNodeFromClass(supertype.classNode).computeAllSuperNodes(this)
269+
];
270+
271+
return _getLegacyLeastUpperBoundInternal(
272+
type1, type2, supertypeNodes1, supertypeNodes2,
273+
isNonNullableByDefault: isNonNullableByDefault);
198274
}
199275

200276
static ClassHierarchyBuilder build(

pkg/front_end/test/fasta/type_inference/type_schema_environment_nnbd_test.dart

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,59 @@ class TypeSchemaEnvironmentTest extends TypeSchemaEnvironmentTestBase {
15211521
checkIsSubtype("Pair<A*, Null>*", "Pair<UNKNOWN, UNKNOWN>*");
15221522
}
15231523

1524+
void test_upper_bound_extension_type() {
1525+
parseTestLibrary("extension type E1(Object? it); "
1526+
"extension type E2(Object it);"
1527+
"extension type E3<X>(X it);");
1528+
checkUpperBound(type1: "E1", type2: "E1", upperBound: "E1");
1529+
checkUpperBound(type1: "E2", type2: "E2", upperBound: "E2");
1530+
checkUpperBound(type1: "E1", type2: "E2", upperBound: "Object?");
1531+
checkUpperBound(type1: "E1", type2: "E3<Object?>", upperBound: "Object?");
1532+
checkUpperBound(type1: "E1", type2: "E3<num?>", upperBound: "Object?");
1533+
checkUpperBound(type1: "E2", type2: "E3<Object?>", upperBound: "Object?");
1534+
checkUpperBound(type1: "E2", type2: "E3<num?>", upperBound: "Object?");
1535+
checkUpperBound(type1: "E2", type2: "E3<Object>", upperBound: "Object");
1536+
}
1537+
1538+
void test_upper_bound_extension_type_implements() {
1539+
parseTestLibrary("extension type E1(int it) implements num; "
1540+
"extension type E2(double it) implements double; "
1541+
"extension type E3<X extends num>(X it) implements num; "
1542+
"extension type E4<X extends num>(X it) implements E3<X>; "
1543+
"extension type E5(num? it); "
1544+
"extension type E6(num? it) implements E5; "
1545+
"extension type E7(String it) implements String; "
1546+
"extension type E8(bool it); "
1547+
"extension type E9(int it) implements E6; "
1548+
"extension type E10(double it) implements E5; "
1549+
"extension type E11<X>(X it); ");
1550+
checkUpperBound(type1: "E1", type2: "E2", upperBound: "num");
1551+
checkUpperBound(type1: "E1", type2: "E3<int>", upperBound: "num");
1552+
checkUpperBound(type1: "E2", type2: "E3<double>", upperBound: "num");
1553+
checkUpperBound(type1: "E1", type2: "E4<int>", upperBound: "num");
1554+
checkUpperBound(type1: "E2", type2: "E4<double>", upperBound: "num");
1555+
checkUpperBound(type1: "E1", type2: "E5", upperBound: "Object?");
1556+
checkUpperBound(type1: "E2", type2: "E5", upperBound: "Object?");
1557+
checkUpperBound(type1: "E1", type2: "E6", upperBound: "Object?");
1558+
checkUpperBound(type1: "E2", type2: "E6", upperBound: "Object?");
1559+
checkUpperBound(type1: "E1", type2: "E7", upperBound: "Object");
1560+
checkUpperBound(type1: "E1", type2: "E8", upperBound: "Object");
1561+
checkUpperBound(type1: "E6", type2: "E9", upperBound: "E6");
1562+
checkUpperBound(type1: "E5", type2: "E9", upperBound: "E5");
1563+
checkUpperBound(type1: "E5", type2: "E6", upperBound: "E5");
1564+
checkUpperBound(type1: "E1", type2: "E1?", upperBound: "E1?");
1565+
checkUpperBound(type1: "E6?", type2: "E9", upperBound: "E6?");
1566+
checkUpperBound(type1: "E6", type2: "E9?", upperBound: "E6?");
1567+
checkUpperBound(type1: "E6", type2: "E10", upperBound: "E5");
1568+
checkUpperBound(type1: "E5", type2: "E10", upperBound: "E5");
1569+
checkUpperBound(type1: "E6?", type2: "E10", upperBound: "E5?");
1570+
checkUpperBound(type1: "E6", type2: "E10?", upperBound: "E5?");
1571+
checkUpperBound(
1572+
type1: "E11<num>", type2: "E11<num?>", upperBound: "E11<num?>");
1573+
checkUpperBound(
1574+
type1: "E11<int>", type2: "E11<String>", upperBound: "E11<Object>");
1575+
}
1576+
15241577
void checkUpperBound(
15251578
{required String type1,
15261579
required String type2,

pkg/front_end/test/spell_checking_list_common.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,6 +1331,7 @@ gathered
13311331
gatherer
13321332
gathering
13331333
general
1334+
generalization
13341335
generalized
13351336
generalizes
13361337
generally

0 commit comments

Comments
 (0)