Implement UP for RecordType(s).

Change-Id: I9960fbdc5cadbd600ad2ae5c9991b83852c428d9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255143
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/element/least_upper_bound.dart b/pkg/analyzer/lib/src/dart/element/least_upper_bound.dart
index e6447f4..076b037 100644
--- a/pkg/analyzer/lib/src/dart/element/least_upper_bound.dart
+++ b/pkg/analyzer/lib/src/dart/element/least_upper_bound.dart
@@ -710,6 +710,21 @@
       return getLeastUpperBound(T1, _typeSystem.objectNone);
     }
 
+    // Record types.
+    if (T1 is RecordTypeImpl && T2 is RecordTypeImpl) {
+      return _recordType(T1, T2);
+    }
+
+    // UP(RecordType, T2) = UP(Object, T2)
+    if (T1 is RecordTypeImpl) {
+      return getLeastUpperBound(_typeSystem.objectNone, T2);
+    }
+
+    // UP(T1, RecordType) = UP(T1, Object)
+    if (T2 is RecordTypeImpl) {
+      return getLeastUpperBound(T1, _typeSystem.objectNone);
+    }
+
     var futureOrResult = _futureOr(T1, T2);
     if (futureOrResult != null) {
       return futureOrResult;
@@ -896,6 +911,45 @@
     return _typeSystem.getGreatestLowerBound(a.type, b.type);
   }
 
+  DartType _recordType(RecordTypeImpl T1, RecordTypeImpl T2) {
+    final positional1 = T1.positionalFields;
+    final positional2 = T2.positionalFields;
+    if (positional1.length != positional2.length) {
+      return _typeSystem.typeProvider.recordType;
+    }
+
+    final named1 = T1.namedFields;
+    final named2 = T2.namedFields;
+    if (named1.length != named2.length) {
+      return _typeSystem.typeProvider.recordType;
+    }
+
+    final fieldTypes = <DartType>[];
+
+    for (var i = 0; i < positional1.length; i++) {
+      final field1 = positional1[i];
+      final field2 = positional2[i];
+      final type = getLeastUpperBound(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.recordType;
+      }
+      final type = getLeastUpperBound(field1.type, field2.type);
+      fieldTypes.add(type);
+    }
+
+    return RecordTypeImpl(
+      element2: T1.element2,
+      fieldTypes: fieldTypes,
+      nullabilitySuffix: NullabilitySuffix.none,
+    );
+  }
+
   /// Return the promoted or declared bound of the type parameter.
   DartType _typeParameterBound(TypeParameterTypeImpl type) {
     var bound = type.promotedBound ?? type.element2.bound;
diff --git a/pkg/analyzer/lib/src/dart/element/type_provider.dart b/pkg/analyzer/lib/src/dart/element/type_provider.dart
index a16dcb9..d313754 100644
--- a/pkg/analyzer/lib/src/dart/element/type_provider.dart
+++ b/pkg/analyzer/lib/src/dart/element/type_provider.dart
@@ -142,6 +142,7 @@
   InterfaceType? _numType;
   InterfaceType? _numTypeQuestion;
   InterfaceType? _objectType;
+  InterfaceType? _recordType;
   InterfaceType? _stackTraceType;
   InterfaceType? _streamDynamicType;
   InterfaceType? _stringType;
@@ -396,6 +397,13 @@
     return _recordElement ??= _getClassElement(_coreLibrary, 'Record');
   }
 
+  InterfaceType get recordType {
+    return _recordType ??= recordElement.instantiate(
+      typeArguments: const [],
+      nullabilitySuffix: NullabilitySuffix.none,
+    );
+  }
+
   @override
   ClassElement get setElement {
     return _setElement ??= _getClassElement(_coreLibrary, 'Set');
diff --git a/pkg/analyzer/test/src/dart/element/string_types.dart b/pkg/analyzer/test/src/dart/element/string_types.dart
new file mode 100644
index 0000000..b346f81
--- /dev/null
+++ b/pkg/analyzer/test/src/dart/element/string_types.dart
@@ -0,0 +1,766 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/dart/element/type_visitor.dart';
+import 'package:test/test.dart';
+
+import '../../../generated/type_system_test.dart';
+
+mixin StringTypes on AbstractTypeSystemTest {
+  final Map<String, DartType> _types = {};
+
+  void assertExpectedString(DartType type, String? expectedString) {
+    if (expectedString != null) {
+      var typeStr = _typeStr(type);
+
+      typeStr += _typeParametersStr(type);
+
+      expect(typeStr, expectedString);
+    }
+  }
+
+  void defineStringTypes() {
+    _defineType('dynamic', dynamicNone);
+    _defineType('void', voidNone);
+
+    _defineType('Never', neverNone);
+    _defineType('Never*', neverStar);
+    _defineType('Never?', neverQuestion);
+
+    _defineType('Null?', nullQuestion);
+
+    _defineType('Object', objectNone);
+    _defineType('Object*', objectStar);
+    _defineType('Object?', objectQuestion);
+
+    _defineType('Comparable<Object*>*', comparableStar(objectStar));
+    _defineType('Comparable<num*>*', comparableStar(numStar));
+    _defineType('Comparable<int*>*', comparableStar(intStar));
+
+    _defineType('num', numNone);
+    _defineType('num*', numStar);
+    _defineType('num?', numQuestion);
+
+    _defineType('int', intNone);
+    _defineType('int*', intStar);
+    _defineType('int?', intQuestion);
+
+    _defineType('double', doubleNone);
+    _defineType('double*', doubleStar);
+    _defineType('double?', doubleQuestion);
+
+    _defineType('List<Object*>*', listStar(objectStar));
+    _defineType('List<num*>*', listStar(numStar));
+    _defineType('List<int>', listNone(intNone));
+    _defineType('List<int>*', listStar(intNone));
+    _defineType('List<int>?', listQuestion(intNone));
+    _defineType('List<int*>', listNone(intStar));
+    _defineType('List<int*>*', listStar(intStar));
+    _defineType('List<int*>?', listQuestion(intStar));
+    _defineType('List<int?>', listNone(intQuestion));
+
+    _defineType(
+      'List<Comparable<Object*>*>*',
+      listStar(
+        comparableStar(objectStar),
+      ),
+    );
+    _defineType(
+      'List<Comparable<num*>*>*',
+      listStar(
+        comparableStar(numStar),
+      ),
+    );
+    _defineType(
+      'List<Comparable<Comparable<num*>*>*>*',
+      listStar(
+        comparableStar(
+          comparableStar(numStar),
+        ),
+      ),
+    );
+
+    _defineType('Iterable<Object*>*', iterableStar(objectStar));
+    _defineType('Iterable<int*>*', iterableStar(intStar));
+    _defineType('Iterable<num*>*', iterableStar(numStar));
+
+    _defineFunctionTypes();
+    _defineFutureTypes();
+    _defineRecordTypes();
+  }
+
+  DartType typeOfString(String str) {
+    var type = _types[str];
+    if (type == null) {
+      fail('No DartType for: $str');
+    }
+    return type;
+  }
+
+  void _defineFunctionTypes() {
+    _defineType('Function', functionNone);
+    _defineType('Function*', functionStar);
+    _defineType('Function?', functionQuestion);
+
+    _defineType(
+      'void Function()',
+      functionTypeNone(
+        returnType: voidNone,
+      ),
+    );
+
+    _defineType(
+      'int* Function()',
+      functionTypeNone(
+        returnType: intStar,
+      ),
+    );
+    _defineType(
+      'int* Function()*',
+      functionTypeStar(
+        returnType: intStar,
+      ),
+    );
+    _defineType(
+      'int* Function()?',
+      functionTypeQuestion(
+        returnType: intStar,
+      ),
+    );
+
+    _defineType(
+      'num Function(num)',
+      functionTypeNone(
+        parameters: [requiredParameter(type: numNone)],
+        returnType: numNone,
+      ),
+    );
+    _defineType(
+      'num Function(num)?',
+      functionTypeQuestion(
+        parameters: [requiredParameter(type: numNone)],
+        returnType: numNone,
+      ),
+    );
+    _defineType(
+      'num Function(num)*',
+      functionTypeStar(
+        parameters: [requiredParameter(type: numNone)],
+        returnType: numNone,
+      ),
+    );
+
+    _defineType(
+      'num* Function(num*)',
+      functionTypeNone(
+        parameters: [requiredParameter(type: numStar)],
+        returnType: numStar,
+      ),
+    );
+    _defineType(
+      'num* Function(num*)*',
+      functionTypeStar(
+        parameters: [requiredParameter(type: numStar)],
+        returnType: numStar,
+      ),
+    );
+    _defineType(
+      'num* Function(num*)?',
+      functionTypeQuestion(
+        parameters: [requiredParameter(type: numStar)],
+        returnType: numStar,
+      ),
+    );
+
+    _defineType(
+      'int* Function(num*)*',
+      functionTypeStar(
+        parameters: [requiredParameter(type: numStar)],
+        returnType: intStar,
+      ),
+    );
+
+    _defineType(
+      'num* Function(int*)',
+      functionTypeNone(
+        parameters: [requiredParameter(type: intStar)],
+        returnType: numStar,
+      ),
+    );
+    _defineType(
+      'num* Function(int*)*',
+      functionTypeStar(
+        parameters: [requiredParameter(type: intStar)],
+        returnType: numStar,
+      ),
+    );
+    _defineType(
+      'num* Function(int*)?',
+      functionTypeQuestion(
+        parameters: [requiredParameter(type: intStar)],
+        returnType: numStar,
+      ),
+    );
+
+    _defineType(
+      'int* Function(int*)*',
+      functionTypeStar(
+        parameters: [requiredParameter(type: intStar)],
+        returnType: intStar,
+      ),
+    );
+
+    _defineType(
+      'num Function(num?)',
+      functionTypeNone(
+        parameters: [requiredParameter(type: numQuestion)],
+        returnType: numNone,
+      ),
+    );
+    _defineType(
+      'num? Function(num)',
+      functionTypeNone(
+        parameters: [requiredParameter(type: numNone)],
+        returnType: numQuestion,
+      ),
+    );
+    _defineType(
+      'num? Function(num?)',
+      functionTypeNone(
+        parameters: [requiredParameter(type: numQuestion)],
+        returnType: numQuestion,
+      ),
+    );
+
+    _defineType(
+      'num Function({num x})',
+      functionTypeNone(
+        parameters: [namedParameter(name: 'x', type: numNone)],
+        returnType: numNone,
+      ),
+    );
+    _defineType(
+      'num Function({num? x})',
+      functionTypeNone(
+        parameters: [namedParameter(name: 'x', type: numQuestion)],
+        returnType: numNone,
+      ),
+    );
+    _defineType(
+      'num? Function({num x})',
+      functionTypeNone(
+        parameters: [namedParameter(name: 'x', type: numNone)],
+        returnType: numQuestion,
+      ),
+    );
+    _defineType(
+      'num? Function({num? x})',
+      functionTypeNone(
+        parameters: [namedParameter(name: 'x', type: numQuestion)],
+        returnType: numQuestion,
+      ),
+    );
+
+    _defineType(
+      'num Function([num])',
+      functionTypeNone(
+        parameters: [positionalParameter(type: numNone)],
+        returnType: numNone,
+      ),
+    );
+    _defineType(
+      'num Function([num?])',
+      functionTypeNone(
+        parameters: [positionalParameter(type: numQuestion)],
+        returnType: numNone,
+      ),
+    );
+    _defineType(
+      'num? Function([num])',
+      functionTypeNone(
+        parameters: [positionalParameter(type: numNone)],
+        returnType: numQuestion,
+      ),
+    );
+    _defineType(
+      'num? Function([num?])',
+      functionTypeNone(
+        parameters: [positionalParameter(type: numQuestion)],
+        returnType: numQuestion,
+      ),
+    );
+  }
+
+  void _defineFutureTypes() {
+    _defineType('FutureOr<Object*>*', futureOrStar(objectStar));
+    _defineType('FutureOr<num*>*', futureOrStar(numStar));
+    _defineType('FutureOr<int*>*', futureOrStar(intStar));
+    _defineType('FutureOr<num?>?', futureOrQuestion(numQuestion));
+
+    _defineType('FutureOr<Object>', futureOrNone(objectNone));
+    _defineType('FutureOr<Object>?', futureOrQuestion(objectNone));
+    _defineType('FutureOr<Object?>', futureOrNone(objectQuestion));
+    _defineType('FutureOr<Object?>?', futureOrQuestion(objectQuestion));
+
+    _defineType('Future<num>', futureNone(numNone));
+    _defineType('Future<num>?', futureQuestion(numNone));
+    _defineType('Future<num?>', futureNone(numQuestion));
+    _defineType('Future<num?>?', futureQuestion(numQuestion));
+
+    _defineType('FutureOr<int>', futureOrNone(intNone));
+    _defineType('FutureOr<int>?', futureOrQuestion(intNone));
+    _defineType('FutureOr<int?>', futureOrNone(intQuestion));
+    _defineType('FutureOr<int?>?', futureOrQuestion(intQuestion));
+
+    _defineType('FutureOr<int>*', futureOrStar(intNone));
+    _defineType('FutureOr<int*>', futureOrNone(intStar));
+    _defineType('Future<int*>*', futureStar(intStar));
+
+    _defineType('FutureOr<num>', futureOrNone(numNone));
+    _defineType('FutureOr<num>*', futureOrStar(numNone));
+    _defineType('FutureOr<num>?', futureOrQuestion(numNone));
+
+    _defineType('FutureOr<num*>', futureOrNone(numStar));
+    _defineType('FutureOr<num?>', futureOrNone(numQuestion));
+
+    _defineType('Future<Object>', futureNone(objectNone));
+    _defineType(
+      'FutureOr<Future<Object>>',
+      futureOrNone(
+        futureNone(objectNone),
+      ),
+    );
+    _defineType(
+      'FutureOr<Future<Object>>?',
+      futureOrQuestion(
+        futureNone(objectNone),
+      ),
+    );
+    _defineType(
+      'FutureOr<Future<Object>?>',
+      futureOrNone(
+        futureQuestion(objectNone),
+      ),
+    );
+    _defineType(
+      'FutureOr<Future<Object>?>?',
+      futureOrQuestion(
+        futureQuestion(objectNone),
+      ),
+    );
+
+    _defineType(
+      'Future<Future<num>>?',
+      futureQuestion(
+        futureNone(numNone),
+      ),
+    );
+    _defineType(
+      'Future<Future<num?>?>?',
+      futureQuestion(
+        futureQuestion(numQuestion),
+      ),
+    );
+
+    _defineType(
+      'Future<Future<Future<num>>>?',
+      futureQuestion(
+        futureNone(
+          futureNone(numNone),
+        ),
+      ),
+    );
+    _defineType(
+      'Future<Future<Future<num?>?>?>?',
+      futureQuestion(
+        futureQuestion(
+          futureQuestion(numQuestion),
+        ),
+      ),
+    );
+
+    _defineType(
+      'FutureOr<FutureOr<FutureOr<num>>?>',
+      futureOrNone(
+        futureOrQuestion(
+          futureOrNone(numNone),
+        ),
+      ),
+    );
+    _defineType(
+      'FutureOr<FutureOr<FutureOr<num?>>>',
+      futureOrNone(
+        futureOrNone(
+          futureOrNone(numQuestion),
+        ),
+      ),
+    );
+  }
+
+  void _defineRecordTypes() {
+    _defineType('Record', recordNone);
+
+    _defineType(
+      '(double)',
+      recordTypeNone(
+        element: recordElement(
+          positionalFields: [
+            recordPositionalField(type: doubleNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '(int)',
+      recordTypeNone(
+        element: recordElement(
+          positionalFields: [
+            recordPositionalField(type: intNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '(num)',
+      recordTypeNone(
+        element: recordElement(
+          positionalFields: [
+            recordPositionalField(type: numNone),
+          ],
+        ),
+      ),
+    );
+
+    _defineType(
+      '(double, int)',
+      recordTypeNone(
+        element: recordElement(
+          positionalFields: [
+            recordPositionalField(type: doubleNone),
+            recordPositionalField(type: intNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '(int, double)',
+      recordTypeNone(
+        element: recordElement(
+          positionalFields: [
+            recordPositionalField(type: intNone),
+            recordPositionalField(type: doubleNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '(int, Object)',
+      recordTypeNone(
+        element: recordElement(
+          positionalFields: [
+            recordPositionalField(type: intNone),
+            recordPositionalField(type: objectNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '(int, String)',
+      recordTypeNone(
+        element: recordElement(
+          positionalFields: [
+            recordPositionalField(type: intNone),
+            recordPositionalField(type: stringNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '(num, num)',
+      recordTypeNone(
+        element: recordElement(
+          positionalFields: [
+            recordPositionalField(type: numNone),
+            recordPositionalField(type: numNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '(num, Object)',
+      recordTypeNone(
+        element: recordElement(
+          positionalFields: [
+            recordPositionalField(type: numNone),
+            recordPositionalField(type: objectNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '(num, String)',
+      recordTypeNone(
+        element: recordElement(
+          positionalFields: [
+            recordPositionalField(type: numNone),
+            recordPositionalField(type: stringNone),
+          ],
+        ),
+      ),
+    );
+
+    _defineType(
+      '({double f1})',
+      recordTypeNone(
+        element: recordElement(
+          namedFields: [
+            recordNamedField(name: 'f1', type: doubleNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '({int f1})',
+      recordTypeNone(
+        element: recordElement(
+          namedFields: [
+            recordNamedField(name: 'f1', type: intNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '({num f1})',
+      recordTypeNone(
+        element: recordElement(
+          namedFields: [
+            recordNamedField(name: 'f1', type: numNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '({int f2})',
+      recordTypeNone(
+        element: recordElement(
+          namedFields: [
+            recordNamedField(name: 'f2', type: intNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '({double f1, int f2})',
+      recordTypeNone(
+        element: recordElement(
+          namedFields: [
+            recordNamedField(name: 'f1', type: doubleNone),
+            recordNamedField(name: 'f2', type: intNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '({int f1, double f2})',
+      recordTypeNone(
+        element: recordElement(
+          namedFields: [
+            recordNamedField(name: 'f1', type: intNone),
+            recordNamedField(name: 'f2', type: doubleNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '({int f1, String f2})',
+      recordTypeNone(
+        element: recordElement(
+          namedFields: [
+            recordNamedField(name: 'f1', type: intNone),
+            recordNamedField(name: 'f2', type: stringNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '({int f1, Object f2})',
+      recordTypeNone(
+        element: recordElement(
+          namedFields: [
+            recordNamedField(name: 'f1', type: intNone),
+            recordNamedField(name: 'f2', type: objectNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '({num f1, num f2})',
+      recordTypeNone(
+        element: recordElement(
+          namedFields: [
+            recordNamedField(name: 'f1', type: numNone),
+            recordNamedField(name: 'f2', type: numNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '({num f1, String f2})',
+      recordTypeNone(
+        element: recordElement(
+          namedFields: [
+            recordNamedField(name: 'f1', type: numNone),
+            recordNamedField(name: 'f2', type: stringNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '({num f1, Object f2})',
+      recordTypeNone(
+        element: recordElement(
+          namedFields: [
+            recordNamedField(name: 'f1', type: numNone),
+            recordNamedField(name: 'f2', type: objectNone),
+          ],
+        ),
+      ),
+    );
+
+    _defineType(
+      '(int, {String f2})',
+      recordTypeNone(
+        element: recordElement(
+          positionalFields: [
+            recordPositionalField(type: intNone),
+          ],
+          namedFields: [
+            recordNamedField(name: 'f2', type: stringNone),
+          ],
+        ),
+      ),
+    );
+    _defineType(
+      '(int, {Object f2})',
+      recordTypeNone(
+        element: recordElement(
+          positionalFields: [
+            recordPositionalField(type: intNone),
+          ],
+          namedFields: [
+            recordNamedField(name: 'f2', type: objectNone),
+          ],
+        ),
+      ),
+    );
+  }
+
+  void _defineType(String str, DartType type) {
+    if (_typeStr(type) != str) {
+      fail('Expected: $str\nActual: ${_typeStr(type)}');
+    }
+
+    for (var entry in _types.entries) {
+      var key = entry.key;
+      if (key == 'Never' || _typeStr(type) == 'Never') {
+        // We have aliases for Never.
+      } else {
+        var value = entry.value;
+        if (key == str) {
+          fail('Duplicate type: $str;  existing: $value;  new: $type');
+        }
+        if (_typeStr(value) == _typeStr(type)) {
+          fail('Duplicate type: $str');
+        }
+      }
+    }
+    _types[str] = type;
+  }
+
+  String _typeParametersStr(DartType type) {
+    var typeStr = '';
+
+    var typeParameterCollector = _TypeParameterCollector();
+    type.accept(typeParameterCollector);
+    for (var typeParameter in typeParameterCollector.typeParameters) {
+      typeStr += ', $typeParameter';
+    }
+    return typeStr;
+  }
+
+  static String _typeStr(DartType type) {
+    return type.getDisplayString(withNullability: true);
+  }
+}
+
+class _TypeParameterCollector extends TypeVisitor<void> {
+  final Set<String> typeParameters = {};
+
+  /// We don't need to print bounds for these type parameters, because
+  /// they are already included into the function type itself, and cannot
+  /// be promoted.
+  final Set<TypeParameterElement> functionTypeParameters = {};
+
+  @override
+  void visitDynamicType(DynamicType type) {}
+
+  @override
+  void visitFunctionType(FunctionType type) {
+    functionTypeParameters.addAll(type.typeFormals);
+    for (var typeParameter in type.typeFormals) {
+      var bound = typeParameter.bound;
+      if (bound != null) {
+        bound.accept(this);
+      }
+    }
+    for (var parameter in type.parameters) {
+      parameter.type.accept(this);
+    }
+    type.returnType.accept(this);
+  }
+
+  @override
+  void visitInterfaceType(InterfaceType type) {
+    for (var typeArgument in type.typeArguments) {
+      typeArgument.accept(this);
+    }
+  }
+
+  @override
+  void visitNeverType(NeverType type) {}
+
+  @override
+  void visitRecordType(RecordType type) {
+    final fields = [
+      ...type.positionalFields,
+      ...type.namedFields,
+    ];
+    for (final field in fields) {
+      field.type.accept(this);
+    }
+  }
+
+  @override
+  void visitTypeParameterType(TypeParameterType type) {
+    if (!functionTypeParameters.contains(type.element2)) {
+      var bound = type.element2.bound;
+
+      if (bound == null) {
+        return;
+      }
+
+      var str = '';
+
+      var boundStr = bound.getDisplayString(withNullability: true);
+      str += '${type.element2.name} extends $boundStr';
+
+      typeParameters.add(str);
+    }
+  }
+
+  @override
+  void visitVoidType(VoidType type) {}
+}
diff --git a/pkg/analyzer/test/src/dart/element/subtype_test.dart b/pkg/analyzer/test/src/dart/element/subtype_test.dart
index cd2c985..a53a92f 100644
--- a/pkg/analyzer/test/src/dart/element/subtype_test.dart
+++ b/pkg/analyzer/test/src/dart/element/subtype_test.dart
@@ -5,12 +5,12 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/nullability_suffix.dart';
 import 'package:analyzer/dart/element/type.dart';
-import 'package:analyzer/dart/element/type_visitor.dart';
 import 'package:analyzer/src/dart/resolver/variance.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import '../../../generated/type_system_test.dart';
+import 'string_types.dart';
 
 main() {
   defineReflectiveSuite(() {
@@ -20,19 +20,7 @@
 }
 
 @reflectiveTest
-class SubtypeTest extends _SubtypingTestBase {
-  final Map<String, DartType> _types = {};
-
-  void assertExpectedString(DartType type, String? expectedString) {
-    if (expectedString != null) {
-      var typeStr = _typeStr(type);
-
-      typeStr += _typeParametersStr(type);
-
-      expect(typeStr, expectedString);
-    }
-  }
-
+class SubtypeTest extends _SubtypingTestBase with StringTypes {
   void isNotSubtype(
     DartType T0,
     DartType T1, {
@@ -48,8 +36,8 @@
     String strT0,
     String strT1,
   ) {
-    var T0 = _getTypeByStr(strT0);
-    var T1 = _getTypeByStr(strT1);
+    var T0 = typeOfString(strT0);
+    var T1 = typeOfString(strT1);
     expect(typeSystem.isSubtypeOf(T0, T1), isFalse);
   }
 
@@ -75,15 +63,15 @@
     String strT0,
     String strT1,
   ) {
-    var T0 = _getTypeByStr(strT0);
-    var T1 = _getTypeByStr(strT1);
+    var T0 = typeOfString(strT0);
+    var T1 = typeOfString(strT1);
     expect(typeSystem.isSubtypeOf(T0, T1), isTrue);
   }
 
   @override
   void setUp() {
     super.setUp();
-    _defineTypes();
+    defineStringTypes();
   }
 
   test_functionType_01() {
@@ -4122,17 +4110,17 @@
   }
 
   test_record_functionType() {
-    isNotSubtype2('({int foo})', 'void Function()');
+    isNotSubtype2('({int f1})', 'void Function()');
   }
 
   test_record_interfaceType() {
-    isNotSubtype2('({int foo})', 'int');
-    isNotSubtype2('int', '({int foo})');
+    isNotSubtype2('({int f1})', 'int');
+    isNotSubtype2('int', '({int f1})');
   }
 
   test_record_Never() {
-    isNotSubtype2('({int foo})', 'Never');
-    isSubtype2('Never', '({int foo})');
+    isNotSubtype2('({int f1})', 'Never');
+    isSubtype2('Never', '({int f1})');
   }
 
   test_record_record2_differentShape() {
@@ -4143,8 +4131,8 @@
 
     check('(int)', '(int, String)');
 
-    check('({int foo, String bar})', '({int foo})');
-    check('({int foo})', '({int bar})');
+    check('({int f1, String f2})', '({int f1})');
+    check('({int f1})', '({int f2})');
   }
 
   test_record_record2_sameShape_mixed() {
@@ -4153,7 +4141,7 @@
       isNotSubtype2(superType, subType);
     }
 
-    check('(int, {String bar})', '(int, {Object bar})');
+    check('(int, {String f2})', '(int, {Object f2})');
   }
 
   test_record_record2_sameShape_named() {
@@ -4162,12 +4150,12 @@
       isNotSubtype2(superType, subType);
     }
 
-    check('({int foo})', '({num foo})');
+    check('({int f1})', '({num f1})');
 
-    isSubtype2('({int foo, String bar})', '({int foo, String bar})');
-    check('({int foo, String bar})', '({int foo, Object bar})');
-    check('({int foo, String bar})', '({num foo, String bar})');
-    check('({int foo, String bar})', '({num foo, Object bar})');
+    isSubtype2('({int f1, String f2})', '({int f1, String f2})');
+    check('({int f1, String f2})', '({int f1, Object f2})');
+    check('({int f1, String f2})', '({num f1, String f2})');
+    check('({int f1, String f2})', '({num f1, Object f2})');
   }
 
   test_record_record2_sameShape_named_order() {
@@ -4180,20 +4168,20 @@
       recordTypeNone(
         element: recordElement(
           namedFields: [
-            recordNamedField(name: 'foo01', type: intNone),
-            recordNamedField(name: 'foo02', type: intNone),
-            recordNamedField(name: 'foo03', type: intNone),
-            recordNamedField(name: 'foo04', type: intNone),
+            recordNamedField(name: 'f1', type: intNone),
+            recordNamedField(name: 'f2', type: intNone),
+            recordNamedField(name: 'f3', type: intNone),
+            recordNamedField(name: 'f4', type: intNone),
           ],
         ),
       ),
       recordTypeNone(
         element: recordElement(
           namedFields: [
-            recordNamedField(name: 'foo04', type: intNone),
-            recordNamedField(name: 'foo03', type: intNone),
-            recordNamedField(name: 'foo02', type: intNone),
-            recordNamedField(name: 'foo01', type: intNone),
+            recordNamedField(name: 'f4', type: intNone),
+            recordNamedField(name: 'f3', type: intNone),
+            recordNamedField(name: 'f2', type: intNone),
+            recordNamedField(name: 'f1', type: intNone),
           ],
         ),
       ),
@@ -4215,9 +4203,9 @@
   }
 
   test_record_top() {
-    isSubtype2('({int foo})', 'dynamic');
-    isSubtype2('({int foo})', 'Object');
-    isSubtype2('({int foo})', 'Record');
+    isSubtype2('({int f1})', 'dynamic');
+    isSubtype2('({int f1})', 'Object');
+    isSubtype2('({int f1})', 'Record');
   }
 
   /// The class `Record` is a subtype of `Object` and `dynamic`, and a
@@ -5336,577 +5324,6 @@
       strT1: 'FutureOr<T>, T extends FutureOr<T>',
     );
   }
-
-  void _defineType(String str, DartType type) {
-    for (var entry in _types.entries) {
-      var key = entry.key;
-      if (key == 'Never' || _typeStr(type) == 'Never') {
-        // We have aliases for Never.
-      } else {
-        var value = entry.value;
-        if (key == str) {
-          fail('Duplicate type: $str;  existing: $value;  new: $type');
-        }
-        if (_typeStr(value) == _typeStr(type)) {
-          fail('Duplicate type: $str');
-        }
-      }
-    }
-    _types[str] = type;
-  }
-
-  void _defineTypes() {
-    _defineType('dynamic', dynamicNone);
-    _defineType('void', voidNone);
-
-    _defineType('Never', neverNone);
-    _defineType('Never*', neverStar);
-    _defineType('Never?', neverQuestion);
-
-    _defineType('Null?', nullQuestion);
-
-    _defineType('Object', objectNone);
-    _defineType('Object*', objectStar);
-    _defineType('Object?', objectQuestion);
-
-    _defineType('Comparable<Object*>*', comparableStar(objectStar));
-    _defineType('Comparable<num*>*', comparableStar(numStar));
-    _defineType('Comparable<int*>*', comparableStar(intStar));
-
-    _defineType('num', numNone);
-    _defineType('num*', numStar);
-    _defineType('num?', numQuestion);
-
-    _defineType('int', intNone);
-    _defineType('int*', intStar);
-    _defineType('int?', intQuestion);
-
-    _defineType('double', doubleNone);
-    _defineType('double*', doubleStar);
-    _defineType('double?', doubleQuestion);
-
-    _defineType('List<Object*>*', listStar(objectStar));
-    _defineType('List<num*>*', listStar(numStar));
-    _defineType('List<int>', listNone(intNone));
-    _defineType('List<int>*', listStar(intNone));
-    _defineType('List<int>?', listQuestion(intNone));
-    _defineType('List<int*>', listNone(intStar));
-    _defineType('List<int*>*', listStar(intStar));
-    _defineType('List<int*>?', listQuestion(intStar));
-    _defineType('List<int?>', listNone(intQuestion));
-
-    _defineType(
-      'List<Comparable<Object*>*>*',
-      listStar(
-        comparableStar(objectStar),
-      ),
-    );
-    _defineType(
-      'List<Comparable<num*>*>*',
-      listStar(
-        comparableStar(numStar),
-      ),
-    );
-    _defineType(
-      'List<Comparable<Comparable<num*>*>*>*',
-      listStar(
-        comparableStar(
-          comparableStar(numStar),
-        ),
-      ),
-    );
-
-    _defineType('Iterable<Object*>*', iterableStar(objectStar));
-    _defineType('Iterable<int*>*', iterableStar(intStar));
-    _defineType('Iterable<num*>*', iterableStar(numStar));
-
-    _defineType('Function', functionNone);
-    _defineType('Function*', functionStar);
-    _defineType('Function?', functionQuestion);
-
-    _defineType('FutureOr<Object*>*', futureOrStar(objectStar));
-    _defineType('FutureOr<num*>*', futureOrStar(numStar));
-    _defineType('FutureOr<int*>*', futureOrStar(intStar));
-    _defineType('FutureOr<num?>?', futureOrQuestion(numQuestion));
-
-    _defineType('FutureOr<Object>', futureOrNone(objectNone));
-    _defineType('FutureOr<Object>?', futureOrQuestion(objectNone));
-    _defineType('FutureOr<Object?>', futureOrNone(objectQuestion));
-    _defineType('FutureOr<Object?>?', futureOrQuestion(objectQuestion));
-
-    _defineType('Future<num>', futureNone(numNone));
-    _defineType('Future<num>?', futureQuestion(numNone));
-    _defineType('Future<num?>', futureNone(numQuestion));
-    _defineType('Future<num?>?', futureQuestion(numQuestion));
-
-    _defineType('FutureOr<int>', futureOrNone(intNone));
-    _defineType('FutureOr<int>?', futureOrQuestion(intNone));
-    _defineType('FutureOr<int?>', futureOrNone(intQuestion));
-    _defineType('FutureOr<int?>?', futureOrQuestion(intQuestion));
-
-    _defineType('FutureOr<int>*', futureOrStar(intNone));
-    _defineType('FutureOr<int*>', futureOrNone(intStar));
-    _defineType('Future<int*>*', futureStar(intStar));
-
-    _defineType('FutureOr<num>', futureOrNone(numNone));
-    _defineType('FutureOr<num>*', futureOrStar(numNone));
-    _defineType('FutureOr<num>?', futureOrQuestion(numNone));
-
-    _defineType('FutureOr<num*>', futureOrNone(numStar));
-    _defineType('FutureOr<num?>', futureOrNone(numQuestion));
-
-    _defineType('Future<Object>', futureNone(objectNone));
-    _defineType(
-      'FutureOr<Future<Object>>',
-      futureOrNone(
-        futureNone(objectNone),
-      ),
-    );
-    _defineType(
-      'FutureOr<Future<Object>>?',
-      futureOrQuestion(
-        futureNone(objectNone),
-      ),
-    );
-    _defineType(
-      'FutureOr<Future<Object>?>',
-      futureOrNone(
-        futureQuestion(objectNone),
-      ),
-    );
-    _defineType(
-      'FutureOr<Future<Object>?>?',
-      futureOrQuestion(
-        futureQuestion(objectNone),
-      ),
-    );
-
-    _defineType(
-      'Future<Future<num>>?',
-      futureQuestion(
-        futureNone(numNone),
-      ),
-    );
-    _defineType(
-      'Future<Future<num?>?>?',
-      futureQuestion(
-        futureQuestion(numQuestion),
-      ),
-    );
-
-    _defineType(
-      'Future<Future<Future<num>>>?',
-      futureQuestion(
-        futureNone(
-          futureNone(numNone),
-        ),
-      ),
-    );
-    _defineType(
-      'Future<Future<Future<num?>?>?>?',
-      futureQuestion(
-        futureQuestion(
-          futureQuestion(numQuestion),
-        ),
-      ),
-    );
-
-    _defineType(
-      'FutureOr<FutureOr<FutureOr<num>>?>',
-      futureOrNone(
-        futureOrQuestion(
-          futureOrNone(numNone),
-        ),
-      ),
-    );
-    _defineType(
-      'FutureOr<FutureOr<FutureOr<num?>>>',
-      futureOrNone(
-        futureOrNone(
-          futureOrNone(numQuestion),
-        ),
-      ),
-    );
-
-    _defineType(
-      'void Function()',
-      functionTypeNone(
-        returnType: voidNone,
-      ),
-    );
-
-    _defineType(
-      'int* Function()',
-      functionTypeNone(
-        returnType: intStar,
-      ),
-    );
-    _defineType(
-      'int* Function()*',
-      functionTypeStar(
-        returnType: intStar,
-      ),
-    );
-    _defineType(
-      'int* Function()?',
-      functionTypeQuestion(
-        returnType: intStar,
-      ),
-    );
-
-    _defineType(
-      'num Function(num)',
-      functionTypeNone(
-        parameters: [requiredParameter(type: numNone)],
-        returnType: numNone,
-      ),
-    );
-    _defineType(
-      'num Function(num)?',
-      functionTypeQuestion(
-        parameters: [requiredParameter(type: numNone)],
-        returnType: numNone,
-      ),
-    );
-    _defineType(
-      'num Function(num)*',
-      functionTypeStar(
-        parameters: [requiredParameter(type: numNone)],
-        returnType: numNone,
-      ),
-    );
-
-    _defineType(
-      'num* Function(num*)',
-      functionTypeNone(
-        parameters: [requiredParameter(type: numStar)],
-        returnType: numStar,
-      ),
-    );
-    _defineType(
-      'num* Function(num*)*',
-      functionTypeStar(
-        parameters: [requiredParameter(type: numStar)],
-        returnType: numStar,
-      ),
-    );
-    _defineType(
-      'num* Function(num*)?',
-      functionTypeQuestion(
-        parameters: [requiredParameter(type: numStar)],
-        returnType: numStar,
-      ),
-    );
-
-    _defineType(
-      'int* Function(num*)*',
-      functionTypeStar(
-        parameters: [requiredParameter(type: numStar)],
-        returnType: intStar,
-      ),
-    );
-
-    _defineType(
-      'num* Function(int*)',
-      functionTypeNone(
-        parameters: [requiredParameter(type: intStar)],
-        returnType: numStar,
-      ),
-    );
-    _defineType(
-      'num* Function(int*)*',
-      functionTypeStar(
-        parameters: [requiredParameter(type: intStar)],
-        returnType: numStar,
-      ),
-    );
-    _defineType(
-      'num* Function(int*)?',
-      functionTypeQuestion(
-        parameters: [requiredParameter(type: intStar)],
-        returnType: numStar,
-      ),
-    );
-
-    _defineType(
-      'int* Function(int*)*',
-      functionTypeStar(
-        parameters: [requiredParameter(type: intStar)],
-        returnType: intStar,
-      ),
-    );
-
-    _defineType(
-      'num Function(num?)',
-      functionTypeNone(
-        parameters: [requiredParameter(type: numQuestion)],
-        returnType: numNone,
-      ),
-    );
-    _defineType(
-      'num? Function(num)',
-      functionTypeNone(
-        parameters: [requiredParameter(type: numNone)],
-        returnType: numQuestion,
-      ),
-    );
-    _defineType(
-      'num? Function(num?)',
-      functionTypeNone(
-        parameters: [requiredParameter(type: numQuestion)],
-        returnType: numQuestion,
-      ),
-    );
-
-    _defineType(
-      'num Function({num x})',
-      functionTypeNone(
-        parameters: [namedParameter(name: 'x', type: numNone)],
-        returnType: numNone,
-      ),
-    );
-    _defineType(
-      'num Function({num? x})',
-      functionTypeNone(
-        parameters: [namedParameter(name: 'x', type: numQuestion)],
-        returnType: numNone,
-      ),
-    );
-    _defineType(
-      'num? Function({num x})',
-      functionTypeNone(
-        parameters: [namedParameter(name: 'x', type: numNone)],
-        returnType: numQuestion,
-      ),
-    );
-    _defineType(
-      'num? Function({num? x})',
-      functionTypeNone(
-        parameters: [namedParameter(name: 'x', type: numQuestion)],
-        returnType: numQuestion,
-      ),
-    );
-
-    _defineType(
-      'num Function([num])',
-      functionTypeNone(
-        parameters: [positionalParameter(type: numNone)],
-        returnType: numNone,
-      ),
-    );
-    _defineType(
-      'num Function([num?])',
-      functionTypeNone(
-        parameters: [positionalParameter(type: numQuestion)],
-        returnType: numNone,
-      ),
-    );
-    _defineType(
-      'num? Function([num])',
-      functionTypeNone(
-        parameters: [positionalParameter(type: numNone)],
-        returnType: numQuestion,
-      ),
-    );
-    _defineType(
-      'num? Function([num?])',
-      functionTypeNone(
-        parameters: [positionalParameter(type: numQuestion)],
-        returnType: numQuestion,
-      ),
-    );
-
-    _defineType('Record', recordNone);
-    _defineType(
-      '(int, String)',
-      recordTypeNone(
-        element: recordElement(
-          positionalFields: [
-            recordPositionalField(type: intNone),
-            recordPositionalField(type: stringNone),
-          ],
-        ),
-      ),
-    );
-    _defineType(
-      '(num, String)',
-      recordTypeNone(
-        element: recordElement(
-          positionalFields: [
-            recordPositionalField(type: numNone),
-            recordPositionalField(type: stringNone),
-          ],
-        ),
-      ),
-    );
-    _defineType(
-      '(int, Object)',
-      recordTypeNone(
-        element: recordElement(
-          positionalFields: [
-            recordPositionalField(type: intNone),
-            recordPositionalField(type: objectNone),
-          ],
-        ),
-      ),
-    );
-    _defineType(
-      '(num, Object)',
-      recordTypeNone(
-        element: recordElement(
-          positionalFields: [
-            recordPositionalField(type: numNone),
-            recordPositionalField(type: objectNone),
-          ],
-        ),
-      ),
-    );
-    _defineType(
-      '(int)',
-      recordTypeNone(
-        element: recordElement(
-          positionalFields: [
-            recordPositionalField(type: intNone),
-          ],
-        ),
-      ),
-    );
-    _defineType(
-      '(num)',
-      recordTypeNone(
-        element: recordElement(
-          positionalFields: [
-            recordPositionalField(type: numNone),
-          ],
-        ),
-      ),
-    );
-
-    _defineType(
-      '({int foo})',
-      recordTypeNone(
-        element: recordElement(
-          namedFields: [
-            recordNamedField(name: 'foo', type: intNone),
-          ],
-        ),
-      ),
-    );
-    _defineType(
-      '({num foo})',
-      recordTypeNone(
-        element: recordElement(
-          namedFields: [
-            recordNamedField(name: 'foo', type: numNone),
-          ],
-        ),
-      ),
-    );
-    _defineType(
-      '({int bar})',
-      recordTypeNone(
-        element: recordElement(
-          namedFields: [
-            recordNamedField(name: 'bar', type: intNone),
-          ],
-        ),
-      ),
-    );
-    _defineType(
-      '({int foo, String bar})',
-      recordTypeNone(
-        element: recordElement(
-          namedFields: [
-            recordNamedField(name: 'foo', type: intNone),
-            recordNamedField(name: 'bar', type: stringNone),
-          ],
-        ),
-      ),
-    );
-    _defineType(
-      '({int foo, Object bar})',
-      recordTypeNone(
-        element: recordElement(
-          namedFields: [
-            recordNamedField(name: 'foo', type: intNone),
-            recordNamedField(name: 'bar', type: objectNone),
-          ],
-        ),
-      ),
-    );
-    _defineType(
-      '({num foo, String bar})',
-      recordTypeNone(
-        element: recordElement(
-          namedFields: [
-            recordNamedField(name: 'foo', type: numNone),
-            recordNamedField(name: 'bar', type: stringNone),
-          ],
-        ),
-      ),
-    );
-    _defineType(
-      '({num foo, Object bar})',
-      recordTypeNone(
-        element: recordElement(
-          namedFields: [
-            recordNamedField(name: 'foo', type: numNone),
-            recordNamedField(name: 'bar', type: objectNone),
-          ],
-        ),
-      ),
-    );
-
-    _defineType(
-      '(int, {String bar})',
-      recordTypeNone(
-        element: recordElement(
-          positionalFields: [
-            recordPositionalField(type: intNone),
-          ],
-          namedFields: [
-            recordNamedField(name: 'bar', type: stringNone),
-          ],
-        ),
-      ),
-    );
-    _defineType(
-      '(int, {Object bar})',
-      recordTypeNone(
-        element: recordElement(
-          positionalFields: [
-            recordPositionalField(type: intNone),
-          ],
-          namedFields: [
-            recordNamedField(name: 'bar', type: objectNone),
-          ],
-        ),
-      ),
-    );
-  }
-
-  DartType _getTypeByStr(String str) {
-    var type = _types[str];
-    if (type == null) {
-      fail('No DartType for: $str');
-    }
-    return type;
-  }
-
-  String _typeParametersStr(DartType type) {
-    var typeStr = '';
-
-    var typeParameterCollector = _TypeParameterCollector();
-    type.accept(typeParameterCollector);
-    for (var typeParameter in typeParameterCollector.typeParameters) {
-      typeStr += ', $typeParameter';
-    }
-    return typeStr;
-  }
-
-  static String _typeStr(DartType type) {
-    return type.getDisplayString(withNullability: true);
-  }
 }
 
 @reflectiveTest
@@ -6311,72 +5728,3 @@
 }
 
 class _SubtypingTestBase extends AbstractTypeSystemTest {}
-
-class _TypeParameterCollector extends TypeVisitor<void> {
-  final Set<String> typeParameters = {};
-
-  /// We don't need to print bounds for these type parameters, because
-  /// they are already included into the function type itself, and cannot
-  /// be promoted.
-  final Set<TypeParameterElement> functionTypeParameters = {};
-
-  @override
-  void visitDynamicType(DynamicType type) {}
-
-  @override
-  void visitFunctionType(FunctionType type) {
-    functionTypeParameters.addAll(type.typeFormals);
-    for (var typeParameter in type.typeFormals) {
-      var bound = typeParameter.bound;
-      if (bound != null) {
-        bound.accept(this);
-      }
-    }
-    for (var parameter in type.parameters) {
-      parameter.type.accept(this);
-    }
-    type.returnType.accept(this);
-  }
-
-  @override
-  void visitInterfaceType(InterfaceType type) {
-    for (var typeArgument in type.typeArguments) {
-      typeArgument.accept(this);
-    }
-  }
-
-  @override
-  void visitNeverType(NeverType type) {}
-
-  @override
-  void visitRecordType(RecordType type) {
-    final fields = [
-      ...type.positionalFields,
-      ...type.namedFields,
-    ];
-    for (final field in fields) {
-      field.type.accept(this);
-    }
-  }
-
-  @override
-  void visitTypeParameterType(TypeParameterType type) {
-    if (!functionTypeParameters.contains(type.element2)) {
-      var bound = type.element2.bound;
-
-      if (bound == null) {
-        return;
-      }
-
-      var str = '';
-
-      var boundStr = bound.getDisplayString(withNullability: true);
-      str += '${type.element2.name} extends $boundStr';
-
-      typeParameters.add(str);
-    }
-  }
-
-  @override
-  void visitVoidType(VoidType type) {}
-}
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 0cd3424..4231cb3 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
@@ -15,6 +15,7 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import '../../../generated/type_system_test.dart';
+import 'string_types.dart';
 
 main() {
   defineReflectiveSuite(() {
@@ -22,6 +23,7 @@
     defineReflectiveTests(LowerBoundTest);
     defineReflectiveTests(UpperBound_FunctionTypes_Test);
     defineReflectiveTests(UpperBound_InterfaceTypes_Test);
+    defineReflectiveTests(UpperBound_RecordTypes_Test);
     defineReflectiveTests(UpperBoundTest);
   });
 }
@@ -1972,23 +1974,26 @@
     );
 
     {
-      var T = typeParameter('T', bound: numNone);
-      var U = typeParameter('U', bound: numNone);
-      var R = typeParameter('R', bound: numNone);
-      check(
-        functionTypeNone(
-          returnType: typeParameterTypeNone(T),
-          typeFormals: [T],
-        ),
-        functionTypeNone(
-          returnType: typeParameterTypeNone(U),
-          typeFormals: [U],
-        ),
-        functionTypeNone(
-          returnType: typeParameterTypeNone(R),
-          typeFormals: [R],
-        ),
+      final T = typeParameter('T', bound: numNone);
+      final U = typeParameter('U', bound: numNone);
+      final T1 = functionTypeNone(
+        returnType: typeParameterTypeNone(T),
+        typeFormals: [T],
       );
+      final T2 = functionTypeNone(
+        returnType: typeParameterTypeNone(U),
+        typeFormals: [U],
+      );
+      {
+        final result = typeSystem.getLeastUpperBound(T1, T2);
+        final resultStr = _typeString(result);
+        expect(resultStr, 'T Function<T extends num>()');
+      }
+      {
+        final result = typeSystem.getLeastUpperBound(T2, T1);
+        final resultStr = _typeString(result);
+        expect(resultStr, 'U Function<U extends num>()');
+      }
     }
   }
 
@@ -2546,6 +2551,84 @@
 }
 
 @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');
+    }
+
+    check('(int)', '(int, String)');
+
+    check('({int f1, String f2})', '({int f1})');
+    check('({int f1})', '({int f2})');
+  }
+
+  test_Never() {
+    _checkLeastUpperBound2('(int)', 'Never', '(int)');
+  }
+
+  test_record_andNot() {
+    _checkLeastUpperBound2('(int)', 'int', 'Object');
+    _checkLeastUpperBound2('(int)', 'void Function()', 'Object');
+  }
+
+  test_sameShape_named() {
+    _checkLeastUpperBound2(
+      '({int f1})',
+      '({int f1})',
+      '({int f1})',
+    );
+
+    _checkLeastUpperBound2(
+      '({int f1})',
+      '({num f1})',
+      '({num f1})',
+    );
+
+    _checkLeastUpperBound2(
+      '({int f1})',
+      '({double f1})',
+      '({num f1})',
+    );
+
+    _checkLeastUpperBound2(
+      '({int f1, double f2})',
+      '({double f1, int f2})',
+      '({num f1, num f2})',
+    );
+  }
+
+  test_sameShape_positional() {
+    _checkLeastUpperBound2('(int)', '(int)', '(int)');
+    _checkLeastUpperBound2('(int)', '(num)', '(num)');
+    _checkLeastUpperBound2('(int)', '(double)', '(num)');
+
+    _checkLeastUpperBound2(
+      '(int, String)',
+      '(int, String)',
+      '(int, String)',
+    );
+
+    _checkLeastUpperBound2(
+      '(int, double)',
+      '(double, int)',
+      '(num, num)',
+    );
+  }
+
+  test_top() {
+    _checkLeastUpperBound2('(int)', 'dynamic', 'dynamic');
+    _checkLeastUpperBound2('(int)', 'Object?', 'Object?');
+  }
+}
+
+@reflectiveTest
 class UpperBoundTest extends _BoundsTestBase {
   test_bottom_any() {
     void check(DartType T1, DartType T2) {
@@ -3339,7 +3422,7 @@
 }
 
 @reflectiveTest
-class _BoundsTestBase extends AbstractTypeSystemTest {
+class _BoundsTestBase extends AbstractTypeSystemTest with StringTypes {
   void _assertBottom(DartType type) {
     if (!typeSystem.isBottom(type)) {
       fail('isBottom must be true: ${_typeString(type)}');
@@ -3418,10 +3501,7 @@
 
     var result = typeSystem.getLeastUpperBound(T1, T2);
     var resultStr = _typeString(result);
-    expect(result, expected, reason: '''
-expected: $expectedStr
-actual: $resultStr
-''');
+    expect(resultStr, expectedStr);
 
     // Check that the result is an upper bound.
     expect(typeSystem.isSubtypeOf(T1, result), true);
@@ -3430,10 +3510,15 @@
     // Check for symmetry.
     result = typeSystem.getLeastUpperBound(T2, T1);
     resultStr = _typeString(result);
-    expect(result, expected, reason: '''
-expected: $expectedStr
-actual: $resultStr
-''');
+    expect(resultStr, expectedStr);
+  }
+
+  void _checkLeastUpperBound2(String T1, String T2, String expected) {
+    _checkLeastUpperBound(
+      typeOfString(T1),
+      typeOfString(T2),
+      typeOfString(expected),
+    );
   }
 
   String _typeParametersStr(DartType type) {