Implement isSubtypeOf() for RecordType.
Change-Id: I42003fb5ce7bb374a37cc704a85f9eb16ca3aa42
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/254841
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index 4b09012..a4f1282 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -6194,10 +6194,14 @@
@override
final List<RecordNamedFieldElementImpl> namedFields;
+ /// Maybe copy of [namedFields] lexically sorted by names.
+ final List<RecordNamedFieldElementImpl> namedFieldsSorted;
+
RecordElementImpl({
required this.positionalFields,
required this.namedFields,
- }) : super(null, -1) {
+ }) : namedFieldsSorted = _sortNamedFields(namedFields),
+ super(null, -1) {
for (final field in positionalFields) {
field.enclosingElement = this;
}
@@ -6214,6 +6218,28 @@
// TODO: implement accept
throw UnimplementedError();
}
+
+ /// Returns [fields], if already sorted, or the sorted copy.
+ static List<RecordNamedFieldElementImpl> _sortNamedFields(
+ List<RecordNamedFieldElementImpl> fields,
+ ) {
+ var isSorted = true;
+ String? lastName;
+ for (final field in fields) {
+ final name = field.name;
+ if (lastName != null && lastName.compareTo(name) > 0) {
+ isSorted = false;
+ break;
+ }
+ lastName = name;
+ }
+
+ if (isSorted) {
+ return fields;
+ }
+
+ return fields.sortedBy((field) => field.name);
+ }
}
class RecordFieldElementImpl extends _ExistingElementImpl
diff --git a/pkg/analyzer/lib/src/dart/element/subtype.dart b/pkg/analyzer/lib/src/dart/element/subtype.dart
index 7ae9fa77..9dfb25a 100644
--- a/pkg/analyzer/lib/src/dart/element/subtype.dart
+++ b/pkg/analyzer/lib/src/dart/element/subtype.dart
@@ -303,7 +303,7 @@
return true;
}
if (T1 is RecordTypeImpl) {
- // TODO(scheglov) record types
+ return _isRecordSubtypeOf(T0, T1);
}
}
@@ -483,6 +483,42 @@
return false;
}
+ /// Check that [subType] is a subtype of [superType].
+ bool _isRecordSubtypeOf(RecordType subType, RecordType superType) {
+ final subPositional = subType.positionalFields;
+ final superPositional = superType.positionalFields;
+ if (subPositional.length != superPositional.length) {
+ return false;
+ }
+
+ final subNamed = subType.namedFields;
+ final superNamed = superType.namedFields;
+ if (subNamed.length != superNamed.length) {
+ return false;
+ }
+
+ for (var i = 0; i < subPositional.length; i++) {
+ final subField = subPositional[i];
+ final superField = superPositional[i];
+ if (!isSubtypeOf(subField.type, superField.type)) {
+ return false;
+ }
+ }
+
+ for (var i = 0; i < subNamed.length; i++) {
+ final subField = subNamed[i];
+ final superField = superNamed[i];
+ if (subField.name != superField.name) {
+ return false;
+ }
+ if (!isSubtypeOf(subField.type, superField.type)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
static FunctionTypeImpl _functionTypeWithNamedRequired(FunctionType type) {
return FunctionTypeImpl(
typeFormals: type.typeFormals,
diff --git a/pkg/analyzer/lib/src/dart/element/type.dart b/pkg/analyzer/lib/src/dart/element/type.dart
index b7f1abb..1b57924 100644
--- a/pkg/analyzer/lib/src/dart/element/type.dart
+++ b/pkg/analyzer/lib/src/dart/element/type.dart
@@ -1015,7 +1015,7 @@
@override
List<RecordTypeNamedField> get namedFields {
- return element.namedFields.map((field) {
+ return element.namedFieldsSorted.map((field) {
final type = substitution.substituteType(field.type);
return RecordTypeNamedFieldImpl(
element: field,
diff --git a/pkg/analyzer/test/src/dart/element/subtype_test.dart b/pkg/analyzer/test/src/dart/element/subtype_test.dart
index 514f8e5..cd2c985 100644
--- a/pkg/analyzer/test/src/dart/element/subtype_test.dart
+++ b/pkg/analyzer/test/src/dart/element/subtype_test.dart
@@ -4121,103 +4121,103 @@
);
}
- test_record_dynamic() {
- isSubtype(
- recordTypeNone(
- element: recordElement(
- namedFields: [
- recordNamedField(name: 'foo', type: intNone),
- ],
- ),
- ),
- dynamicNone,
- strT0: '({int foo})',
- strT1: 'dynamic',
- );
- }
-
test_record_functionType() {
- isNotSubtype(
- recordTypeNone(
- element: recordElement(
- namedFields: [
- recordNamedField(name: 'foo', type: intNone),
- ],
- ),
- ),
- functionTypeNone(returnType: voidNone),
- strT0: '({int foo})',
- strT1: 'void Function()',
- );
+ isNotSubtype2('({int foo})', 'void Function()');
}
- test_record_int() {
- final R = recordTypeNone(
- element: recordElement(
- namedFields: [
- recordNamedField(name: 'foo', type: intNone),
- ],
- ),
- );
-
- isNotSubtype(
- R,
- intNone,
- strT0: '({int foo})',
- strT1: 'int',
- );
-
- isNotSubtype(
- intNone,
- R,
- strT0: 'int',
- strT1: '({int foo})',
- );
+ test_record_interfaceType() {
+ isNotSubtype2('({int foo})', 'int');
+ isNotSubtype2('int', '({int foo})');
}
test_record_Never() {
- isSubtype(
- neverNone,
+ isNotSubtype2('({int foo})', 'Never');
+ isSubtype2('Never', '({int foo})');
+ }
+
+ test_record_record2_differentShape() {
+ void check(String T1, String T2) {
+ isNotSubtype2(T1, T2);
+ isNotSubtype2(T2, T1);
+ }
+
+ check('(int)', '(int, String)');
+
+ check('({int foo, String bar})', '({int foo})');
+ check('({int foo})', '({int bar})');
+ }
+
+ test_record_record2_sameShape_mixed() {
+ void check(String subType, String superType) {
+ isSubtype2(subType, superType);
+ isNotSubtype2(superType, subType);
+ }
+
+ check('(int, {String bar})', '(int, {Object bar})');
+ }
+
+ test_record_record2_sameShape_named() {
+ void check(String subType, String superType) {
+ isSubtype2(subType, superType);
+ isNotSubtype2(superType, subType);
+ }
+
+ check('({int foo})', '({num foo})');
+
+ 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})');
+ }
+
+ test_record_record2_sameShape_named_order() {
+ void check(RecordType subType, RecordType superType) {
+ isSubtype(subType, superType);
+ isSubtype(superType, subType);
+ }
+
+ check(
recordTypeNone(
element: recordElement(
namedFields: [
- recordNamedField(name: 'foo', type: intNone),
+ recordNamedField(name: 'foo01', type: intNone),
+ recordNamedField(name: 'foo02', type: intNone),
+ recordNamedField(name: 'foo03', type: intNone),
+ recordNamedField(name: 'foo04', type: intNone),
],
),
),
- strT0: 'Never',
- strT1: '({int foo})',
+ recordTypeNone(
+ element: recordElement(
+ namedFields: [
+ recordNamedField(name: 'foo04', type: intNone),
+ recordNamedField(name: 'foo03', type: intNone),
+ recordNamedField(name: 'foo02', type: intNone),
+ recordNamedField(name: 'foo01', type: intNone),
+ ],
+ ),
+ ),
);
}
- test_record_Object() {
- isSubtype(
- recordTypeNone(
- element: recordElement(
- namedFields: [
- recordNamedField(name: 'foo', type: intNone),
- ],
- ),
- ),
- objectNone,
- strT0: '({int foo})',
- strT1: 'Object',
- );
+ test_record_record2_sameShape_positional() {
+ void check(String subType, String superType) {
+ isSubtype2(subType, superType);
+ isNotSubtype2(superType, subType);
+ }
+
+ check('(int)', '(num)');
+
+ isSubtype2('(int, String)', '(int, String)');
+ check('(int, String)', '(num, String)');
+ check('(int, String)', '(num, Object)');
+ check('(int, String)', '(int, Object)');
}
- test_record_Record() {
- isSubtype(
- recordTypeNone(
- element: recordElement(
- namedFields: [
- recordNamedField(name: 'foo', type: intNone),
- ],
- ),
- ),
- recordNone,
- strT0: '({int foo})',
- strT1: 'Record',
- );
+ test_record_top() {
+ isSubtype2('({int foo})', 'dynamic');
+ isSubtype2('({int foo})', 'Object');
+ isSubtype2('({int foo})', 'Record');
}
/// The class `Record` is a subtype of `Object` and `dynamic`, and a
@@ -5529,6 +5529,13 @@
);
_defineType(
+ 'void Function()',
+ functionTypeNone(
+ returnType: voidNone,
+ ),
+ );
+
+ _defineType(
'int* Function()',
functionTypeNone(
returnType: intStar,
@@ -5708,6 +5715,174 @@
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) {