Skip to content

Commit dc4c7a1

Browse files
chloestefantsovaCommit Bot
authored and
Commit Bot
committed
[cfe] Extend UP and DOWN onto record types
Closes #49914 Part of #49713 Change-Id: I49b1ca4ca9b68c8a8dfcdd9d0fc855f173088efe Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/258005 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent db6311b commit dc4c7a1

File tree

3 files changed

+238
-0
lines changed

3 files changed

+238
-0
lines changed

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,36 @@ class TypeSchemaEnvironmentTest extends TypeSchemaEnvironmentTestBase {
460460
lowerBound: "<Z extends FutureOr<dynamic>?>() -> void");
461461
}
462462

463+
void test_lower_bound_record() {
464+
parseTestLibrary("""
465+
class A;
466+
class B extends A;
467+
""");
468+
469+
checkLowerBound(type1: "(A, B)", type2: "(B, A)", lowerBound: "(B, B)");
470+
checkLowerBound(
471+
type1: "(A, {B b})", type2: "(B, {A b})", lowerBound: "(B, {B b})");
472+
checkLowerBound(
473+
type1: "(A, {(B, {A a}) b})",
474+
type2: "(B, {(A, {B a}) b})",
475+
lowerBound: "(B, {(B, {B a}) b})");
476+
checkLowerBound(type1: "(A?, B)", type2: "(B, A?)", lowerBound: "(B, B)");
477+
checkLowerBound(type1: "(A, B?)", type2: "(B?, A)", lowerBound: "(B, B)");
478+
479+
checkLowerBound(type1: "(A, A)", type2: "(A, A, A)", lowerBound: "Never");
480+
checkLowerBound(type1: "(A, A)", type2: "(A, {A a})", lowerBound: "Never");
481+
checkLowerBound(type1: "({A a})", type2: "(A, A)", lowerBound: "Never");
482+
checkLowerBound(
483+
type1: "({A a, B b})", type2: "({A a})", lowerBound: "Never");
484+
485+
checkLowerBound(type1: "(A, B)", type2: "Record", lowerBound: "(A, B)");
486+
checkLowerBound(type2: "Record", type1: "(A, B)", lowerBound: "(A, B)");
487+
488+
checkLowerBound(
489+
type1: "(A, B)", type2: "(A, B) -> void", lowerBound: "Never");
490+
checkLowerBound(type1: "Record", type2: "A", lowerBound: "Never");
491+
}
492+
463493
void test_lower_bound_identical() {
464494
parseTestLibrary("class A;");
465495

@@ -911,6 +941,36 @@ class TypeSchemaEnvironmentTest extends TypeSchemaEnvironmentTestBase {
911941
upperBound: "([dynamic]) -> dynamic");
912942
}
913943

944+
void test_upper_bound_record() {
945+
parseTestLibrary("""
946+
class A;
947+
class B extends A;
948+
""");
949+
950+
checkUpperBound(type1: "(A, B)", type2: "(B, A)", upperBound: "(A, A)");
951+
checkUpperBound(
952+
type1: "(A, {B b})", type2: "(B, {A b})", upperBound: "(A, {A b})");
953+
checkUpperBound(
954+
type1: "(A, {(B, {A a}) b})",
955+
type2: "(B, {(A, {B a}) b})",
956+
upperBound: "(A, {(A, {A a}) b})");
957+
checkUpperBound(type1: "(A?, B)", type2: "(B, A?)", upperBound: "(A?, A?)");
958+
checkUpperBound(type1: "(A, B?)", type2: "(B?, A)", upperBound: "(A?, A?)");
959+
960+
checkUpperBound(type1: "(A, A)", type2: "(A, A, A)", upperBound: "Record");
961+
checkUpperBound(type1: "(A, A)", type2: "(A, {A a})", upperBound: "Record");
962+
checkUpperBound(type1: "({A a})", type2: "(A, A)", upperBound: "Record");
963+
checkUpperBound(
964+
type1: "({A a, B b})", type2: "({A a})", upperBound: "Record");
965+
966+
checkUpperBound(type1: "(A, B)", type2: "Record", upperBound: "Record");
967+
checkUpperBound(type2: "Record", type1: "(A, B)", upperBound: "Record");
968+
969+
checkUpperBound(
970+
type1: "(A, B)", type2: "(A, B) -> void", upperBound: "Object");
971+
checkUpperBound(type1: "Record", type2: "A", upperBound: "Object");
972+
}
973+
914974
void test_upper_bound_identical() {
915975
parseTestLibrary("class A;");
916976

pkg/kernel/lib/core_types.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ class CoreTypes {
8686
InterfaceType? _functionLegacyRawType;
8787
InterfaceType? _functionNullableRawType;
8888
InterfaceType? _functionNonNullableRawType;
89+
InterfaceType? _recordLegacyRawType;
90+
InterfaceType? _recordNullableRawType;
91+
InterfaceType? _recordNonNullableRawType;
8992
InterfaceType? _invocationLegacyRawType;
9093
InterfaceType? _invocationNullableRawType;
9194
InterfaceType? _invocationNonNullableRawType;
@@ -795,6 +798,38 @@ class CoreTypes {
795798
}
796799
}
797800

801+
InterfaceType get recordLegacyRawType {
802+
return _recordLegacyRawType ??= _legacyRawTypes[recordClass] ??=
803+
new InterfaceType(recordClass, Nullability.legacy, const <DartType>[]);
804+
}
805+
806+
InterfaceType get recordNullableRawType {
807+
return _recordNullableRawType ??= _nullableRawTypes[recordClass] ??=
808+
new InterfaceType(
809+
recordClass, Nullability.nullable, const <DartType>[]);
810+
}
811+
812+
InterfaceType get recordNonNullableRawType {
813+
return _recordNonNullableRawType ??= _nonNullableRawTypes[recordClass] ??=
814+
new InterfaceType(
815+
recordClass, Nullability.nonNullable, const <DartType>[]);
816+
}
817+
818+
InterfaceType recordRawType(Nullability nullability) {
819+
switch (nullability) {
820+
case Nullability.legacy:
821+
return recordLegacyRawType;
822+
case Nullability.nullable:
823+
return recordNullableRawType;
824+
case Nullability.nonNullable:
825+
return recordNonNullableRawType;
826+
case Nullability.undetermined:
827+
default:
828+
throw new StateError(
829+
"Unsupported nullability $nullability on an InterfaceType.");
830+
}
831+
}
832+
798833
InterfaceType get invocationLegacyRawType {
799834
return _invocationLegacyRawType ??= _legacyRawTypes[invocationClass] ??=
800835
new InterfaceType(

pkg/kernel/lib/src/standard_bounds.dart

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,11 @@ mixin StandardBounds {
411411
type1, type2, clientLibrary);
412412
}
413413

414+
if (type1 is RecordType && type2 is RecordType) {
415+
return _getNullabilityAwareRecordStandardLowerBound(
416+
type1, type2, clientLibrary);
417+
}
418+
414419
// DOWN(T1, T2) = T1 if T1 <: T2.
415420
// DOWN(T1, T2) = T2 if T2 <: T1.
416421

@@ -780,6 +785,33 @@ mixin StandardBounds {
780785
type1, coreTypes.objectNonNullableRawType, clientLibrary);
781786
}
782787

788+
if (type1 is RecordType) {
789+
if (type2 is RecordType) {
790+
return _getNullabilityAwareRecordStandardUpperBound(
791+
type1, type2, clientLibrary);
792+
}
793+
794+
if (type2 is InterfaceType && type2.classNode == coreTypes.recordClass) {
795+
// UP(Record(...), Record) = Record
796+
return coreTypes.recordRawType(uniteNullabilities(
797+
type1.declaredNullability, type2.declaredNullability));
798+
}
799+
800+
// UP(Record(...), T2) = UP(Object, T2)
801+
return _getNullabilityAwareStandardUpperBound(
802+
coreTypes.objectNonNullableRawType, type2, clientLibrary);
803+
} else if (type2 is RecordType) {
804+
if (type1 is InterfaceType && type1.classNode == coreTypes.recordClass) {
805+
// UP(Record, Record(...)) = Record
806+
return coreTypes.recordRawType(uniteNullabilities(
807+
type1.declaredNullability, type2.declaredNullability));
808+
}
809+
810+
// UP(T1, Record(...)) = UP(T1, Object)
811+
return _getNullabilityAwareStandardUpperBound(
812+
type1, coreTypes.objectNonNullableRawType, clientLibrary);
813+
}
814+
783815
// UP(FutureOr<T1>, FutureOr<T2>) = FutureOr<T3> where T3 = UP(T1, T2)
784816
// UP(Future<T1>, FutureOr<T2>) = FutureOr<T3> where T3 = UP(T1, T2)
785817
// UP(FutureOr<T1>, Future<T2>) = FutureOr<T3> where T3 = UP(T1, T2)
@@ -1051,6 +1083,60 @@ mixin StandardBounds {
10511083
math.min(f.requiredParameterCount, g.requiredParameterCount));
10521084
}
10531085

1086+
/// Computes the nullability-aware lower bound of two record types.
1087+
///
1088+
/// The algorithm is defined as follows:
1089+
/// DOWN((P00, ..., P0k, Named0), (P10, ..., P1k, Named1)) =
1090+
/// (P20, ..., P2k, Named2)
1091+
/// if:
1092+
/// P2i is DOWN(P0i, P1i),
1093+
/// Named0 contains R0i xi
1094+
/// if Named1 contains R1i xi
1095+
/// Named1 contains R1i xi
1096+
/// if Named0 contains R0i xi
1097+
/// Named2 contains exactly R2i xi
1098+
/// for each xi in both Named0 and Named1
1099+
/// where R0i xi is in Named0
1100+
/// where R1i xi is in Named1
1101+
/// and R2i is UP(R0i, R1i)
1102+
/// DOWN(Record(...), Record(...)) = Never otherwise.
1103+
DartType _getNullabilityAwareRecordStandardLowerBound(
1104+
RecordType r1, RecordType r2, Library clientLibrary) {
1105+
// The fallback result for whenever the following rule applies:
1106+
// DOWN(Record(...), Record(...)) = Never otherwise.
1107+
late final DartType fallbackResult = NeverType.fromNullability(
1108+
intersectNullabilities(r1.declaredNullability, r2.declaredNullability));
1109+
1110+
if (r1.positional.length != r2.positional.length ||
1111+
r1.named.length != r2.named.length) {
1112+
return fallbackResult;
1113+
}
1114+
1115+
int positionalLength = r1.positional.length;
1116+
int namedLength = r1.named.length;
1117+
1118+
for (int i = 0; i < namedLength; i++) {
1119+
if (r1.named[i].name != r2.named[i].name) {
1120+
return fallbackResult;
1121+
}
1122+
}
1123+
1124+
List<DartType> positional = new List<DartType>.generate(
1125+
positionalLength,
1126+
(i) => _getNullabilityAwareStandardLowerBound(
1127+
r1.positional[i], r2.positional[i], clientLibrary));
1128+
1129+
List<NamedType> named = new List<NamedType>.generate(
1130+
namedLength,
1131+
(i) => new NamedType(
1132+
r1.named[i].name,
1133+
_getNullabilityAwareStandardLowerBound(
1134+
r1.named[i].type, r2.named[i].type, clientLibrary)));
1135+
1136+
return new RecordType(positional, named,
1137+
intersectNullabilities(r1.declaredNullability, r2.declaredNullability));
1138+
}
1139+
10541140
/// Computes the nullability-aware lower bound of two function types.
10551141
///
10561142
/// UP(
@@ -1219,6 +1305,63 @@ mixin StandardBounds {
12191305
requiredParameterCount: f.requiredParameterCount);
12201306
}
12211307

1308+
/// Computes the nullability-aware lower bound of two record types.
1309+
///
1310+
/// UP((P00, ... P0k, Named0), (P10, ... P1k, Named1)) =
1311+
/// (P20, ..., P2k, Named2)
1312+
/// if:
1313+
/// P2i is UP(P0i, P1i)
1314+
/// Named0 contains R0i xi
1315+
/// if Named1 contains R1i xi
1316+
/// Named1 contains R1i xi
1317+
/// if Named0 contains R0i xi
1318+
/// Named2 contains exactly R2i xi
1319+
/// for each xi in both Named0 and Named1
1320+
/// where R0i xi is in Named0
1321+
/// where R1i xi is in Named1
1322+
/// and R2i is UP(R0i, R1i)
1323+
/// UP(Record(...), Record(...)) = Record otherwise
1324+
DartType _getNullabilityAwareRecordStandardUpperBound(
1325+
RecordType r1, RecordType r2, Library clientLibrary) {
1326+
// The return value for whenever the following applies:
1327+
// UP(Record(...), Record(...)) = Record otherwise
1328+
late final DartType fallbackResult = coreTypes.recordRawType(
1329+
uniteNullabilities(r1.declaredNullability, r2.declaredNullability));
1330+
1331+
// Here we perform a quick check on the function types to figure out if we
1332+
// can compute a non-trivial upper bound for them.
1333+
if (r1.positional.length != r2.positional.length ||
1334+
r1.named.length != r2.named.length) {
1335+
return fallbackResult;
1336+
}
1337+
1338+
int positionalLength = r1.positional.length;
1339+
int namedLength = r1.named.length;
1340+
1341+
for (int i = 0; i < namedLength; i++) {
1342+
// The named parameters of record types are assumed to be sorted
1343+
// lexicographically.
1344+
if (r1.named[i].name != r2.named[i].name) {
1345+
return fallbackResult;
1346+
}
1347+
}
1348+
1349+
List<DartType> positional = new List<DartType>.generate(
1350+
positionalLength,
1351+
(i) => _getNullabilityAwareStandardUpperBound(
1352+
r1.positional[i], r2.positional[i], clientLibrary));
1353+
1354+
List<NamedType> named = new List<NamedType>.generate(
1355+
namedLength,
1356+
(i) => new NamedType(
1357+
r1.named[i].name,
1358+
_getNullabilityAwareStandardUpperBound(
1359+
r1.named[i].type, r2.named[i].type, clientLibrary)));
1360+
1361+
return new RecordType(positional, named,
1362+
uniteNullabilities(r1.declaredNullability, r2.declaredNullability));
1363+
}
1364+
12221365
DartType _getNullabilityAwareTypeParameterStandardUpperBound(
12231366
TypeParameterType type1, DartType type2, Library clientLibrary) {
12241367
// UP(X1 extends B1, T2) =

0 commit comments

Comments
 (0)