Implement runtime type equality for record types.
Change-Id: I06a717fddcc8600afa39b4a33890415456eb3173
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255546
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analyzer/lib/dart/element/type_visitor.dart b/pkg/analyzer/lib/dart/element/type_visitor.dart
index ceedb4b..b2e5271 100644
--- a/pkg/analyzer/lib/dart/element/type_visitor.dart
+++ b/pkg/analyzer/lib/dart/element/type_visitor.dart
@@ -39,6 +39,8 @@
R visitNeverType(NeverType type, A argument);
+ R visitRecordType(RecordType type, A argument);
+
R visitTypeParameterType(TypeParameterType type, A argument);
R visitVoidType(VoidType type, A argument);
diff --git a/pkg/analyzer/lib/src/dart/element/runtime_type_equality.dart b/pkg/analyzer/lib/src/dart/element/runtime_type_equality.dart
index c0bce30..3f67085 100644
--- a/pkg/analyzer/lib/src/dart/element/runtime_type_equality.dart
+++ b/pkg/analyzer/lib/src/dart/element/runtime_type_equality.dart
@@ -115,6 +115,50 @@
}
@override
+ bool visitRecordType(RecordType T1, DartType T2) {
+ if (T1 is! RecordTypeImpl || T2 is! RecordTypeImpl) {
+ return false;
+ }
+
+ if (!_compatibleNullability(T1, T2)) {
+ return false;
+ }
+
+ final positional1 = T1.positionalFields;
+ final positional2 = T2.positionalFields;
+ if (positional1.length != positional2.length) {
+ return false;
+ }
+
+ final named1 = T1.namedFields;
+ final named2 = T2.namedFields;
+ if (named1.length != named2.length) {
+ return false;
+ }
+
+ for (var i = 0; i < positional1.length; i++) {
+ final field1 = positional1[i];
+ final field2 = positional2[i];
+ if (!field1.type.acceptWithArgument(this, field2.type)) {
+ return false;
+ }
+ }
+
+ for (var i = 0; i < named1.length; i++) {
+ final field1 = named1[i];
+ final field2 = named2[i];
+ if (field1.name != field2.name) {
+ return false;
+ }
+ if (!field1.type.acceptWithArgument(this, field2.type)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @override
bool visitTypeParameterType(TypeParameterType T1, DartType T2) {
return T2 is TypeParameterType &&
_compatibleNullability(T1, T2) &&
diff --git a/pkg/analyzer/lib/src/dart/element/type.dart b/pkg/analyzer/lib/src/dart/element/type.dart
index 7a3f98b..5b34c0e 100644
--- a/pkg/analyzer/lib/src/dart/element/type.dart
+++ b/pkg/analyzer/lib/src/dart/element/type.dart
@@ -1090,8 +1090,7 @@
@override
R acceptWithArgument<R, A>(
TypeVisitorWithArgument<R, A> visitor, A argument) {
- // TODO: implement acceptWithArgument
- throw UnimplementedError();
+ return visitor.visitRecordType(this, argument);
}
@override
@@ -1100,9 +1099,16 @@
}
@override
- TypeImpl withNullability(NullabilitySuffix nullabilitySuffix) {
- // TODO: implement withNullability
- throw UnimplementedError();
+ RecordTypeImpl withNullability(NullabilitySuffix nullabilitySuffix) {
+ if (this.nullabilitySuffix == nullabilitySuffix) {
+ return this;
+ }
+
+ return RecordTypeImpl(
+ element2: element2,
+ fieldTypes: fieldTypes,
+ nullabilitySuffix: nullabilitySuffix,
+ );
}
}
diff --git a/pkg/analyzer/test/src/dart/element/runtime_type_equality_test.dart b/pkg/analyzer/test/src/dart/element/runtime_type_equality_test.dart
index 7efc4f3..0b6828c 100644
--- a/pkg/analyzer/test/src/dart/element/runtime_type_equality_test.dart
+++ b/pkg/analyzer/test/src/dart/element/runtime_type_equality_test.dart
@@ -8,6 +8,7 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../generated/type_system_base.dart';
+import 'string_types.dart';
main() {
defineReflectiveSuite(() {
@@ -16,7 +17,14 @@
}
@reflectiveTest
-class RuntimeTypeEqualityTypeTest extends AbstractTypeSystemTest {
+class RuntimeTypeEqualityTypeTest extends AbstractTypeSystemTest
+ with StringTypes {
+ @override
+ void setUp() {
+ super.setUp();
+ defineStringTypes();
+ }
+
test_dynamic() {
_equal(dynamicNone, dynamicNone);
_notEqual(dynamicNone, voidNone);
@@ -289,6 +297,35 @@
_equal(neverQuestion, nullNone);
}
+ test_recordType_andNot() {
+ _notEqual2('(int)', 'dynamic');
+ _notEqual2('(int)', 'int');
+ _notEqual2('(int)', 'void');
+ }
+
+ test_recordType_differentShape() {
+ _notEqual2('(int)', '(int, int)');
+ _notEqual2('(int)', '({int f1})');
+ _notEqual2('({int f1})', '({int f2})');
+ _notEqual2('({int f1})', '({int f1, int f2})');
+ }
+
+ test_recordType_sameShape_named() {
+ _equal2('({int f1})', '({int f1})');
+ _notEqual2('({int f1})', '({int? f1})');
+ _equal2('({int f1})', '({int* f1})');
+
+ _notEqual2('({int f1})', '({double f1})');
+ }
+
+ test_recordType_sameShape_positional() {
+ _equal2('(int)', '(int)');
+ _notEqual2('(int)', '(int?)');
+ _equal2('(int)', '(int*)');
+
+ _notEqual2('(int)', '(double)');
+ }
+
test_void() {
_equal(voidNone, voidNone);
_notEqual(voidNone, dynamicNone);
@@ -325,10 +362,24 @@
_check(T1, T2, true);
}
+ void _equal2(String T1, String T2) {
+ _equal(
+ typeOfString(T1),
+ typeOfString(T2),
+ );
+ }
+
void _notEqual(DartType T1, DartType T2) {
_check(T1, T2, false);
}
+ void _notEqual2(String T1, String T2) {
+ _notEqual(
+ typeOfString(T1),
+ typeOfString(T2),
+ );
+ }
+
String _typeString(DartType type) {
return type.getDisplayString(withNullability: true);
}
diff --git a/pkg/analyzer/test/src/dart/element/string_types.dart b/pkg/analyzer/test/src/dart/element/string_types.dart
index d11196a..2081d30 100644
--- a/pkg/analyzer/test/src/dart/element/string_types.dart
+++ b/pkg/analyzer/test/src/dart/element/string_types.dart
@@ -403,259 +403,68 @@
void _defineRecordTypes() {
_defineType('Record', recordNone);
- _defineType(
- '(double)',
- recordTypeNone(
+ void mixed(
+ String str,
+ List<DartType> positionalTypes,
+ Map<String, DartType> namedTypes,
+ ) {
+ final type = recordTypeNone(
element: recordElement(
- positionalFields: [
- recordPositionalField(type: doubleNone),
- ],
+ positionalFields: positionalTypes.map(
+ (fieldType) {
+ return recordPositionalField(type: fieldType);
+ },
+ ).toList(),
+ namedFields: namedTypes.entries.map((entry) {
+ return recordNamedField(name: entry.key, type: entry.value);
+ }).toList(),
),
- ),
- );
- _defineType(
- '(int)',
- recordTypeNone(
- element: recordElement(
- positionalFields: [
- recordPositionalField(type: intNone),
- ],
- ),
- ),
- );
- _defineType(
- '(num)',
- recordTypeNone(
- element: recordElement(
- positionalFields: [
- recordPositionalField(type: numNone),
- ],
- ),
- ),
- );
+ );
+ expect(type.toString(), str);
+ _defineType(str, type);
+ }
- _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),
- ],
- ),
- ),
- );
+ void allPositional(String str, List<DartType> types) {
+ mixed(str, types, const {});
+ }
- _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),
- ],
- ),
- ),
- );
+ allPositional('(double)', [doubleNone]);
+ allPositional('(int)', [intNone]);
+ allPositional('(int?)', [intQuestion]);
+ allPositional('(int*)', [intStar]);
+ allPositional('(num)', [numNone]);
- _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),
- ],
- ),
- ),
- );
+ allPositional('(double, int)', [doubleNone, intNone]);
+ allPositional('(int, double)', [intNone, doubleNone]);
+ allPositional('(int, int)', [intNone, intNone]);
+ allPositional('(int, Object)', [intNone, objectNone]);
+ allPositional('(int, String)', [intNone, stringNone]);
+ allPositional('(num, num)', [numNone, numNone]);
+ allPositional('(num, Object)', [numNone, objectNone]);
+ allPositional('(num, String)', [numNone, stringNone]);
+
+ void allNamed(String str, Map<String, DartType> types) {
+ mixed(str, const [], types);
+ }
+
+ allNamed('({double f1})', {'f1': doubleNone});
+ allNamed('({int f1})', {'f1': intNone});
+ allNamed('({int? f1})', {'f1': intQuestion});
+ allNamed('({int* f1})', {'f1': intStar});
+ allNamed('({num f1})', {'f1': numNone});
+ allNamed('({int f2})', {'f2': intNone});
+
+ allNamed('({double f1, int f2})', {'f1': doubleNone, 'f2': intNone});
+ allNamed('({int f1, double f2})', {'f1': intNone, 'f2': doubleNone});
+ allNamed('({int f1, int f2})', {'f1': intNone, 'f2': intNone});
+ allNamed('({int f1, Object f2})', {'f1': intNone, 'f2': objectNone});
+ allNamed('({int f1, String f2})', {'f1': intNone, 'f2': stringNone});
+ 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});
+
+ mixed('(int, {Object f2})', [intNone], {'f2': objectNone});
+ mixed('(int, {String f2})', [intNone], {'f2': stringNone});
}
void _defineType(String str, DartType type) {