Implement DOWN for RecordType.
Change-Id: I578e98de509ae6ded1d6a5618f75f0576ceef6a8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255682
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/element/greatest_lower_bound.dart b/pkg/analyzer/lib/src/dart/element/greatest_lower_bound.dart
index 92fc5ba..a358259 100644
--- a/pkg/analyzer/lib/src/dart/element/greatest_lower_bound.dart
+++ b/pkg/analyzer/lib/src/dart/element/greatest_lower_bound.dart
@@ -208,6 +208,10 @@
return _functionType(T1, T2);
}
+ if (T1 is RecordTypeImpl && T2 is RecordTypeImpl) {
+ return _recordType(T1, T2);
+ }
+
// DOWN(T1, T2) = T1 if T1 <: T2
if (_typeSystem.isSubtypeOf(T1, T2)) {
return T1;
@@ -380,4 +384,43 @@
nullabilitySuffix: NullabilitySuffix.none,
);
}
+
+ DartType _recordType(RecordTypeImpl T1, RecordTypeImpl T2) {
+ final positional1 = T1.positionalFields;
+ final positional2 = T2.positionalFields;
+ if (positional1.length != positional2.length) {
+ return _typeSystem.typeProvider.neverType;
+ }
+
+ final named1 = T1.namedFields;
+ final named2 = T2.namedFields;
+ if (named1.length != named2.length) {
+ return _typeSystem.typeProvider.neverType;
+ }
+
+ final fieldTypes = <DartType>[];
+
+ for (var i = 0; i < positional1.length; i++) {
+ final field1 = positional1[i];
+ final field2 = positional2[i];
+ final type = getGreatestLowerBound(field1.type, field2.type);
+ fieldTypes.add(type);
+ }
+
+ for (var i = 0; i < named1.length; i++) {
+ final field1 = named1[i];
+ final field2 = named2[i];
+ if (field1.name != field2.name) {
+ return _typeSystem.typeProvider.neverType;
+ }
+ final type = getGreatestLowerBound(field1.type, field2.type);
+ fieldTypes.add(type);
+ }
+
+ return RecordTypeImpl(
+ element2: T1.element2,
+ fieldTypes: fieldTypes,
+ nullabilitySuffix: NullabilitySuffix.none,
+ );
+ }
}
diff --git a/pkg/analyzer/test/src/dart/element/string_types.dart b/pkg/analyzer/test/src/dart/element/string_types.dart
index 883cfe5..8bc2f6e 100644
--- a/pkg/analyzer/test/src/dart/element/string_types.dart
+++ b/pkg/analyzer/test/src/dart/element/string_types.dart
@@ -438,6 +438,7 @@
allPositional('(int?)', [intQuestion]);
allPositional('(int*)', [intStar]);
allPositional('(num)', [numNone]);
+ allPositional('(Never)', [neverNone]);
allPositionalQuestion('(int)?', [intNone]);
allPositionalQuestion('(int?)?', [intQuestion]);
@@ -455,6 +456,7 @@
allPositional('(num, num)', [numNone, numNone]);
allPositional('(num, Object)', [numNone, objectNone]);
allPositional('(num, String)', [numNone, stringNone]);
+ allPositional('(Never, Never)', [neverNone, neverNone]);
void allNamed(String str, Map<String, DartType> types) {
mixed(str, const [], types);
@@ -480,6 +482,7 @@
allNamed('({int* f1})', {'f1': intStar});
allNamed('({num f1})', {'f1': numNone});
allNamed('({int f2})', {'f2': intNone});
+ allNamed('({Never f1})', {'f1': neverNone});
allNamedQuestion('({int f1})?', {'f1': intNone});
allNamedQuestion('({int? f1})?', {'f1': intQuestion});
@@ -497,6 +500,7 @@
allNamed('({num f1, num f2})', {'f1': numNone, 'f2': numNone});
allNamed('({num f1, Object f2})', {'f1': numNone, 'f2': objectNone});
allNamed('({num f1, String f2})', {'f1': numNone, 'f2': stringNone});
+ allNamed('({Never f1, Never f2})', {'f1': neverNone, 'f2': neverNone});
mixed('(int, {Object f2})', [intNone], {'f2': objectNone});
mixed('(int, {String f2})', [intNone], {'f2': stringNone});
diff --git a/pkg/analyzer/test/src/dart/element/upper_lower_bound_test.dart b/pkg/analyzer/test/src/dart/element/upper_lower_bound_test.dart
index 4a3a6d0..ef5a8c1 100644
--- a/pkg/analyzer/test/src/dart/element/upper_lower_bound_test.dart
+++ b/pkg/analyzer/test/src/dart/element/upper_lower_bound_test.dart
@@ -1243,6 +1243,66 @@
check(intQuestion, doubleQuestion, neverQuestion);
}
+ test_recordType2_differentShape() {
+ void check(String T1, String T2) {
+ _checkGreatestLowerBound2(T1, T2, 'Never');
+ }
+
+ check('(int)', '(int, String)');
+
+ check('({int f1, String f2})', '({int f1})');
+ check('({int f1})', '({int f2})');
+ }
+
+ test_recordType2_sameShape_named() {
+ _checkGreatestLowerBound2(
+ '({int f1})',
+ '({int f1})',
+ '({int f1})',
+ );
+
+ _checkGreatestLowerBound2(
+ '({int f1})',
+ '({num f1})',
+ '({int f1})',
+ );
+
+ _checkGreatestLowerBound2(
+ '({int f1})',
+ '({double f1})',
+ '({Never f1})',
+ );
+
+ _checkGreatestLowerBound2(
+ '({int f1, double f2})',
+ '({double f1, int f2})',
+ '({Never f1, Never f2})',
+ );
+ }
+
+ test_recordType2_sameShape_positional() {
+ _checkGreatestLowerBound2('(int)', '(int)', '(int)');
+ _checkGreatestLowerBound2('(int)', '(num)', '(int)');
+ _checkGreatestLowerBound2('(int)', '(double)', '(Never)');
+
+ _checkGreatestLowerBound2(
+ '(int, String)',
+ '(int, String)',
+ '(int, String)',
+ );
+
+ _checkGreatestLowerBound2(
+ '(int, double)',
+ '(double, int)',
+ '(Never, Never)',
+ );
+ }
+
+ test_recordType_andNot() {
+ _checkGreatestLowerBound2('(int)', 'int', 'Never');
+ _checkGreatestLowerBound2('(int)', 'void Function()', 'Never');
+ }
+
test_self() {
var T = typeParameter('T');
@@ -1310,6 +1370,7 @@
check(voidNone, futureOrNone(intNone));
check(voidNone, neverNone);
check(voidNone, functionTypeNone(returnType: voidNone));
+ check(voidNone, typeOfString('(int, int)'));
check(dynamicNone, objectNone);
check(dynamicNone, intNone);
@@ -1319,6 +1380,7 @@
check(dynamicNone, futureOrNone(intNone));
check(dynamicNone, neverNone);
check(dynamicNone, functionTypeNone(returnType: voidNone));
+ check(dynamicNone, typeOfString('(int, int)'));
check(objectQuestion, objectNone);
check(objectQuestion, intNone);
@@ -1328,6 +1390,7 @@
check(objectQuestion, futureOrNone(intNone));
check(objectQuestion, neverNone);
check(objectQuestion, functionTypeNone(returnType: voidNone));
+ check(objectQuestion, typeOfString('(int, int)'));
check(objectStar, objectNone);
check(objectStar, intNone);
@@ -1337,10 +1400,12 @@
check(objectStar, futureOrNone(intNone));
check(objectStar, neverNone);
check(objectStar, functionTypeNone(returnType: voidNone));
+ check(objectStar, typeOfString('(int, int)'));
check(futureOrNone(voidNone), intNone);
check(futureOrQuestion(voidNone), intNone);
check(futureOrStar(voidNone), intNone);
+ check(futureOrStar(voidNone), typeOfString('(int, int)'));
}
test_top_top() {
@@ -1440,6 +1505,14 @@
actual: $resultStr
''');
}
+
+ void _checkGreatestLowerBound2(String T1, String T2, String expected) {
+ _checkGreatestLowerBound(
+ typeOfString(T1),
+ typeOfString(T2),
+ typeOfString(expected),
+ );
+ }
}
@reflectiveTest
@@ -2552,12 +2625,6 @@
@reflectiveTest
class UpperBound_RecordTypes_Test extends _BoundsTestBase {
- @override
- void setUp() {
- super.setUp();
- defineStringTypes();
- }
-
test_differentShape() {
void check(String T1, String T2) {
_checkLeastUpperBound2(T1, T2, 'Record');
@@ -3423,6 +3490,12 @@
@reflectiveTest
class _BoundsTestBase extends AbstractTypeSystemTest with StringTypes {
+ @override
+ void setUp() {
+ super.setUp();
+ defineStringTypes();
+ }
+
void _assertBottom(DartType type) {
if (!typeSystem.isBottom(type)) {
fail('isBottom must be true: ${_typeString(type)}');