From c66faf9f3a8fe7e1118a18d42c0755beec94ef44 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Mon, 15 Aug 2022 17:31:36 +0000 Subject: [PATCH] RecordElement.instantiate(), substitution, type alias. Change-Id: Icd0e234e57d17a1c86140ed58fc9732da31bf1b3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/254942 Reviewed-by: Samuel Rawlins Commit-Queue: Konstantin Shcheglov Reviewed-by: Brian Wilkerson --- pkg/analyzer/lib/dart/element/element.dart | 5 + .../dart/element/display_string_builder.dart | 11 +-- .../lib/src/dart/element/element.dart | 24 +++++ pkg/analyzer/lib/src/dart/element/type.dart | 20 ++-- .../lib/src/dart/element/type_algebra.dart | 17 +++- .../test/generated/elements_types_mixin.dart | 5 +- .../src/dart/element/type_algebra_test.dart | 94 +++++++++++++++++++ 7 files changed, 154 insertions(+), 22 deletions(-) diff --git a/pkg/analyzer/lib/dart/element/element.dart b/pkg/analyzer/lib/dart/element/element.dart index 3b074ce557f..444e18a22ca 100644 --- a/pkg/analyzer/lib/dart/element/element.dart +++ b/pkg/analyzer/lib/dart/element/element.dart @@ -2261,6 +2261,11 @@ abstract class RecordElement implements _ExistingElement { /// The positional fields (might be empty). List get positionalFields; + + /// Returns [RecordType] with [nullabilitySuffix] and declared field types. + RecordType instantiate({ + required NullabilitySuffix nullabilitySuffix, + }); } /// A field in a [RecordElement]. diff --git a/pkg/analyzer/lib/src/dart/element/display_string_builder.dart b/pkg/analyzer/lib/src/dart/element/display_string_builder.dart index efb8bbed729..6cd85f2abae 100644 --- a/pkg/analyzer/lib/src/dart/element/display_string_builder.dart +++ b/pkg/analyzer/lib/src/dart/element/display_string_builder.dart @@ -178,22 +178,21 @@ class ElementDisplayStringBuilder { final fieldCount = positionalFields.length + namedFields.length; _write('('); - for (var i = 0; i < positionalFields.length; i++) { - final field = positionalFields[i]; + var index = 0; + for (final field in positionalFields) { _writeType(field.type); - if (i < fieldCount - 1) { + if (index++ < fieldCount - 1) { _write(', '); } } if (namedFields.isNotEmpty) { _write('{'); - for (var i = 0; i < namedFields.length; i++) { - final field = namedFields[i]; + for (final field in namedFields) { _writeType(field.type); _write(' '); _write(field.name); - if (i < fieldCount - 1) { + if (index++ < fieldCount - 1) { _write(', '); } } diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart index a4f128299e7..cf547ee92f7 100644 --- a/pkg/analyzer/lib/src/dart/element/element.dart +++ b/pkg/analyzer/lib/src/dart/element/element.dart @@ -6219,6 +6219,20 @@ class RecordElementImpl extends _ExistingElementImpl implements RecordElement { throw UnimplementedError(); } + @override + RecordTypeImpl instantiate({ + required NullabilitySuffix nullabilitySuffix, + }) { + return RecordTypeImpl( + element2: this, + fieldTypes: [ + ...positionalFields.map((field) => field.type), + ...namedFieldsSorted.map((field) => field.type), + ], + nullabilitySuffix: nullabilitySuffix, + ); + } + /// Returns [fields], if already sorted, or the sorted copy. static List _sortNamedFields( List fields, @@ -6597,6 +6611,16 @@ class TypeAliasElementImpl extends _ExistingElementImpl typeArguments: typeArguments, ), ); + } else if (type is RecordTypeImpl) { + return RecordTypeImpl( + element2: type.element2, + fieldTypes: type.fieldTypes, + nullabilitySuffix: resultNullability, + alias: InstantiatedTypeAliasElementImpl( + element: this, + typeArguments: typeArguments, + ), + ); } else if (type is TypeParameterType) { return TypeParameterTypeImpl( element: type.element2, diff --git a/pkg/analyzer/lib/src/dart/element/type.dart b/pkg/analyzer/lib/src/dart/element/type.dart index 1b5792455ef..6361b9a7370 100644 --- a/pkg/analyzer/lib/src/dart/element/type.dart +++ b/pkg/analyzer/lib/src/dart/element/type.dart @@ -16,6 +16,7 @@ import 'package:analyzer/src/dart/element/type_algebra.dart'; import 'package:analyzer/src/dart/element/type_system.dart'; import 'package:analyzer/src/generated/element_type_provider.dart'; import 'package:analyzer/src/generated/utilities_dart.dart'; +import 'package:collection/collection.dart'; /// The [Type] representing the type `dynamic`. class DynamicTypeImpl extends TypeImpl implements DynamicType { @@ -995,16 +996,18 @@ class RecordTypeImpl extends TypeImpl implements RecordType { @override final RecordElementImpl element2; - final Substitution substitution; + /// The types of all fields, first positional, then named. + final List fieldTypes; @override final NullabilitySuffix nullabilitySuffix; RecordTypeImpl({ required this.element2, - required this.substitution, + required this.fieldTypes, required this.nullabilitySuffix, - }) : super(element2); + InstantiatedTypeAliasElement? alias, + }) : super(element2, alias: alias); @override RecordElementImpl get element => element2; @@ -1015,23 +1018,22 @@ class RecordTypeImpl extends TypeImpl implements RecordType { @override List get namedFields { - return element.namedFieldsSorted.map((field) { - final type = substitution.substituteType(field.type); + final baseIndex = element.positionalFields.length; + return element.namedFieldsSorted.mapIndexed((index, field) { return RecordTypeNamedFieldImpl( element: field, name: field.name, - type: type, + type: fieldTypes[baseIndex + index], ); }).toList(); } @override List get positionalFields { - return element.positionalFields.map((field) { - final type = substitution.substituteType(field.type); + return element.positionalFields.mapIndexed((index, field) { return RecordTypePositionalFieldImpl( element: field, - type: type, + type: fieldTypes[index], ); }).toList(); } diff --git a/pkg/analyzer/lib/src/dart/element/type_algebra.dart b/pkg/analyzer/lib/src/dart/element/type_algebra.dart index 8bf961f3d42..fb61734f702 100644 --- a/pkg/analyzer/lib/src/dart/element/type_algebra.dart +++ b/pkg/analyzer/lib/src/dart/element/type_algebra.dart @@ -546,9 +546,20 @@ abstract class _TypeSubstitutor DartType visitNeverType(NeverType type) => type; @override - DartType visitRecordType(RecordType type) { - // TODO: implement visitRecordType - throw UnimplementedError(); + DartType visitRecordType(covariant RecordTypeImpl type) { + final before = useCounter; + final fieldTypes = _mapList(type.fieldTypes); + final alias = _mapAlias(type.alias); + if (useCounter == before) { + return type; + } + + return RecordTypeImpl( + element2: type.element2, + fieldTypes: fieldTypes, + nullabilitySuffix: type.nullabilitySuffix, + alias: alias, + ); } @override diff --git a/pkg/analyzer/test/generated/elements_types_mixin.dart b/pkg/analyzer/test/generated/elements_types_mixin.dart index 855e2466126..a3594b323a1 100644 --- a/pkg/analyzer/test/generated/elements_types_mixin.dart +++ b/pkg/analyzer/test/generated/elements_types_mixin.dart @@ -10,7 +10,6 @@ import 'package:analyzer/dart/element/type_provider.dart'; import 'package:analyzer/src/dart/analysis/session.dart'; import 'package:analyzer/src/dart/element/element.dart'; import 'package:analyzer/src/dart/element/type.dart'; -import 'package:analyzer/src/dart/element/type_algebra.dart'; import 'package:analyzer/src/dart/element/type_system.dart'; import 'package:analyzer/src/dart/resolver/variance.dart'; import 'package:analyzer/src/generated/engine.dart'; @@ -592,10 +591,8 @@ mixin ElementsTypesMixin { RecordTypeImpl recordTypeNone({ required RecordElementImpl element, }) { - return RecordTypeImpl( - element2: element, + return element.instantiate( nullabilitySuffix: NullabilitySuffix.none, - substitution: Substitution.empty, ); } diff --git a/pkg/analyzer/test/src/dart/element/type_algebra_test.dart b/pkg/analyzer/test/src/dart/element/type_algebra_test.dart index 15ed44b54bf..b1830f473fc 100644 --- a/pkg/analyzer/test/src/dart/element/type_algebra_test.dart +++ b/pkg/analyzer/test/src/dart/element/type_algebra_test.dart @@ -377,6 +377,100 @@ class SubstituteTest extends _Base { _assertIdenticalType(type, {T: intNone}); } + test_record_doesNotUseTypeParameter() async { + final T = typeParameter('T'); + + final type = recordTypeNone( + element: recordElement( + positionalFields: [ + recordPositionalField(type: intNone), + ], + ), + ); + + assertType(type, '(int)'); + _assertIdenticalType(type, {T: intNone}); + } + + test_record_fromAlias() async { + // typedef Alias = (int, String); + final T = typeParameter('T'); + final Alias = typeAlias( + name: 'Alias', + typeParameters: [T], + aliasedType: recordTypeNone( + element: recordElement( + positionalFields: [ + recordPositionalField(type: intNone), + recordPositionalField(type: stringNone), + ], + ), + ), + ); + + final U = typeParameter('U'); + final type = typeAliasTypeNone(Alias, typeArguments: [ + typeParameterTypeNone(U), + ]); + assertType(type, '(int, String) via Alias'); + _assertSubstitution(type, {U: intNone}, '(int, String) via Alias'); + } + + test_record_fromAlias2() async { + // typedef Alias = (T, List); + final T = typeParameter('T'); + final T_none = typeParameterTypeNone(T); + final Alias = typeAlias( + name: 'Alias', + typeParameters: [T], + aliasedType: recordTypeNone( + element: recordElement( + positionalFields: [ + recordPositionalField(type: T_none), + recordPositionalField(type: listNone(T_none)), + ], + ), + ), + ); + + final type = typeAliasTypeNone(Alias, typeArguments: [intNone]); + assertType(type, '(int, List) via Alias'); + } + + test_record_named() async { + final T = typeParameter('T'); + final T_none = typeParameterTypeNone(T); + + final type = recordTypeNone( + element: recordElement( + namedFields: [ + recordNamedField(name: 'f1', type: T_none), + recordNamedField(name: 'f2', type: listNone(T_none)), + ], + ), + ); + + assertType(type, '({T f1, List f2})'); + _assertSubstitution(type, {T: intNone}, '({int f1, List f2})'); + } + + test_record_positional() async { + final T = typeParameter('T'); + final T_none = typeParameterTypeNone(T); + + final type = recordTypeNone( + element: recordElement( + positionalFields: [ + recordPositionalField(type: T_none), + recordPositionalField(type: listNone(T_none)), + ], + ), + ); + + assertType(type, '(T, List)'); + _assertSubstitution(type, {T: intNone}, '(int, List)'); + } + test_typeParameter_nullability() async { var tElement = typeParameter('T');