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)}');