More tests for RecordTypeAnnotation positions. Change-Id: Iad0b676280e59f93ab8794c79525ee375c677831 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/256370 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/element/replacement_visitor.dart b/pkg/analyzer/lib/src/dart/element/replacement_visitor.dart index c921fe1..54539c6 100644 --- a/pkg/analyzer/lib/src/dart/element/replacement_visitor.dart +++ b/pkg/analyzer/lib/src/dart/element/replacement_visitor.dart
@@ -15,6 +15,7 @@ import 'package:analyzer/src/generated/utilities_dart.dart'; import 'package:analyzer/src/summary2/function_type_builder.dart'; import 'package:analyzer/src/summary2/named_type_builder.dart'; +import 'package:analyzer/src/summary2/record_type_builder.dart'; /// Helper visitor that clones a type if a nested type is replaced, and /// otherwise returns `null`. @@ -139,28 +140,6 @@ ); } - RecordTypeImpl? createRecordType({ - required RecordTypeImpl type, - required InstantiatedTypeAliasElement? newAlias, - required List<RecordTypePositionalFieldImpl>? newPositionalFields, - required List<RecordTypeNamedFieldImpl>? newNamedFields, - required NullabilitySuffix? newNullability, - }) { - if (newAlias == null && - newPositionalFields == null && - newNamedFields == null && - newNullability == null) { - return null; - } - - return RecordTypeImpl( - positionalFields: newPositionalFields ?? type.positionalFields, - namedFields: newNamedFields ?? type.namedFields, - nullabilitySuffix: newNullability ?? type.nullabilitySuffix, - alias: newAlias ?? type.alias, - ); - } - DartType? createTypeParameterType({ required TypeParameterType type, required NullabilitySuffix? newNullability, @@ -490,12 +469,45 @@ } } - return createRecordType( - type: type, - newAlias: newAlias, - newPositionalFields: newPositionalFields, - newNamedFields: newNamedFields, - newNullability: newNullability, + if (newAlias == null && + newPositionalFields == null && + newNamedFields == null && + newNullability == null) { + return null; + } + + return RecordTypeImpl( + positionalFields: newPositionalFields ?? type.positionalFields, + namedFields: newNamedFields ?? type.namedFields, + nullabilitySuffix: newNullability ?? type.nullabilitySuffix, + alias: newAlias ?? type.alias, + ); + } + + @override + DartType? visitRecordTypeBuilder(RecordTypeBuilder type) { + List<DartType>? newFieldTypes; + final fieldTypes = type.fieldTypes; + for (var i = 0; i < fieldTypes.length; i++) { + final fieldType = fieldTypes[i]; + final newFieldType = fieldType.accept(this); + if (newFieldType != null) { + newFieldTypes ??= fieldTypes.toList(growable: false); + newFieldTypes[i] = newFieldType; + } + } + + final newNullability = visitNullability(type); + + if (newFieldTypes == null && newNullability == null) { + return null; + } + + return RecordTypeBuilder( + typeSystem: type.typeSystem, + node: type.node, + fieldTypes: newFieldTypes ?? type.fieldTypes, + nullabilitySuffix: newNullability ?? type.nullabilitySuffix, ); }
diff --git a/pkg/analyzer/lib/src/dart/element/type_algebra.dart b/pkg/analyzer/lib/src/dart/element/type_algebra.dart index 7cfb79c..a9354ec 100644 --- a/pkg/analyzer/lib/src/dart/element/type_algebra.dart +++ b/pkg/analyzer/lib/src/dart/element/type_algebra.dart
@@ -13,6 +13,7 @@ import 'package:analyzer/src/dart/element/type_visitor.dart'; import 'package:analyzer/src/summary2/function_type_builder.dart'; import 'package:analyzer/src/summary2/named_type_builder.dart'; +import 'package:analyzer/src/summary2/record_type_builder.dart'; /// Generates a fresh copy of the given type parameters, with their bounds /// substituted to reference the new parameters. @@ -576,6 +577,12 @@ } @override + DartType visitRecordTypeBuilder(RecordTypeBuilder type) { + // TODO: implement visitRecordTypeBuilder + throw UnimplementedError(); + } + + @override DartType visitTypeParameterType(TypeParameterType type) { var argument = getSubstitute(type.element2); if (argument == null) {
diff --git a/pkg/analyzer/lib/src/dart/element/type_visitor.dart b/pkg/analyzer/lib/src/dart/element/type_visitor.dart index 9a6e354..c74551a1 100644 --- a/pkg/analyzer/lib/src/dart/element/type_visitor.dart +++ b/pkg/analyzer/lib/src/dart/element/type_visitor.dart
@@ -8,6 +8,7 @@ import 'package:analyzer/src/dart/element/type_schema.dart'; import 'package:analyzer/src/summary2/function_type_builder.dart'; import 'package:analyzer/src/summary2/named_type_builder.dart'; +import 'package:analyzer/src/summary2/record_type_builder.dart'; /// Visitors that implement this interface can be used to visit partially /// inferred types, during type inference. @@ -27,6 +28,8 @@ R visitFunctionTypeBuilder(FunctionTypeBuilder type); R visitNamedTypeBuilder(NamedTypeBuilder type); + + R visitRecordTypeBuilder(RecordTypeBuilder type); } /// Recursively visits a DartType tree until any visit method returns `false`.
diff --git a/pkg/analyzer/lib/src/summary2/record_type_builder.dart b/pkg/analyzer/lib/src/summary2/record_type_builder.dart index 1b0bf71..7d17359 100644 --- a/pkg/analyzer/lib/src/summary2/record_type_builder.dart +++ b/pkg/analyzer/lib/src/summary2/record_type_builder.dart
@@ -2,13 +2,14 @@ // 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/ast/ast.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/ast/ast.dart'; import 'package:analyzer/src/dart/ast/extensions.dart'; import 'package:analyzer/src/dart/element/type.dart'; import 'package:analyzer/src/dart/element/type_system.dart'; +import 'package:analyzer/src/dart/element/type_visitor.dart'; import 'package:analyzer/src/summary2/type_builder.dart'; /// The type builder for a [RecordType]. @@ -19,13 +20,48 @@ /// The node for which this builder is created. final RecordTypeAnnotationImpl node; + /// The ordered list of field types, first positional, then named. + final List<DartType> fieldTypes; + + @override + final NullabilitySuffix nullabilitySuffix; + /// This flag is set to `true` while building this type. bool _isBuilding = false; /// The actual built type, not a [TypeBuilder] anymore. RecordTypeImpl? _type; - RecordTypeBuilder(this.typeSystem, this.node); + RecordTypeBuilder({ + required this.typeSystem, + required this.node, + required this.fieldTypes, + required this.nullabilitySuffix, + }); + + factory RecordTypeBuilder.of( + TypeSystemImpl typeSystem, + RecordTypeAnnotationImpl node, + ) { + return RecordTypeBuilder( + typeSystem: typeSystem, + node: node, + fieldTypes: node.fields.map((field) => field.type.typeOrThrow).toList(), + nullabilitySuffix: node.question != null + ? NullabilitySuffix.question + : NullabilitySuffix.none, + ); + } + + @override + R accept<R>(TypeVisitor<R> visitor) { + if (visitor is LinkingTypeVisitor<R>) { + var visitor2 = visitor as LinkingTypeVisitor<R>; + return visitor2.visitRecordTypeBuilder(this); + } else { + throw StateError('Should not happen outside linking.'); + } + } @override RecordTypeImpl build() { @@ -35,15 +71,14 @@ } if (_isBuilding) { - return _type = buildType( - node, - dynamicType: typeSystem.typeProvider.dynamicType, + return _type = _buildRecordType( + recursionFound: true, ); } _isBuilding = true; try { - return _type = buildType(node); + return _type = _buildRecordType(); } finally { _isBuilding = false; } @@ -54,22 +89,52 @@ return node.toSource(); } - /// If [dynamicType] is not `null`, we found a cycle, and recovering by - /// using the same shape, but replacing all field types with `dynamic`. - static RecordTypeImpl buildType( - RecordTypeAnnotationImpl node, { - DartType? dynamicType, + RecordTypeImpl _buildRecordType({ + bool recursionFound = false, }) { + var fieldTypeIndex = 0; + + DartType nextFieldType() { + if (recursionFound) { + return typeSystem.typeProvider.dynamicType; + } else { + final type = fieldTypes[fieldTypeIndex++]; + return _buildType(type); + } + } + final positionalFields = node.positionalFields.map((field) { return RecordTypePositionalFieldImpl( - type: dynamicType ?? _buildFieldType(field), + type: nextFieldType(), ); }).toList(); final namedFields = node.namedFields?.fields.map((field) { return RecordTypeNamedFieldImpl( name: field.name.lexeme, - type: dynamicType ?? _buildFieldType(field), + type: nextFieldType(), + ); + }).toList(); + + return node.type = RecordTypeImpl( + positionalFields: positionalFields, + namedFields: namedFields ?? const [], + nullabilitySuffix: nullabilitySuffix, + ); + } + + /// TODO(scheglov) Move to [RecordTypeAnnotationResolver]. + static RecordTypeImpl buildType(RecordTypeAnnotationImpl node) { + final positionalFields = node.positionalFields.map((field) { + return RecordTypePositionalFieldImpl( + type: field.type.typeOrThrow, + ); + }).toList(); + + final namedFields = node.namedFields?.fields.map((field) { + return RecordTypeNamedFieldImpl( + name: field.name.lexeme, + type: field.type.typeOrThrow, ); }).toList(); @@ -82,10 +147,6 @@ ); } - static DartType _buildFieldType(RecordTypeAnnotationField field) { - return _buildType(field.type.typeOrThrow); - } - /// If the [type] is a [TypeBuilder], build it; otherwise return as is. static DartType _buildType(DartType type) { if (type is TypeBuilder) {
diff --git a/pkg/analyzer/lib/src/summary2/reference_resolver.dart b/pkg/analyzer/lib/src/summary2/reference_resolver.dart index 9e58ef9d..98de463 100644 --- a/pkg/analyzer/lib/src/summary2/reference_resolver.dart +++ b/pkg/analyzer/lib/src/summary2/reference_resolver.dart
@@ -390,12 +390,12 @@ @override void visitRecordTypeAnnotation(covariant RecordTypeAnnotationImpl node) { - final builder = RecordTypeBuilder(_typeSystem, node); - node.type = builder; - nodesToBuildType.addTypeBuilder(builder); - node.positionalFields.accept(this); node.namedFields?.accept(this); + + final builder = RecordTypeBuilder.of(_typeSystem, node); + node.type = builder; + nodesToBuildType.addTypeBuilder(builder); } @override
diff --git a/pkg/analyzer/test/src/fasta/ast_builder_test.dart b/pkg/analyzer/test/src/fasta/ast_builder_test.dart index 0c045af..0f777af 100644 --- a/pkg/analyzer/test/src/fasta/ast_builder_test.dart +++ b/pkg/analyzer/test/src/fasta/ast_builder_test.dart
@@ -948,6 +948,29 @@ '''); } + void test_recordTypeAnnotation_topFunction_returnType_withTypeParameter() { + var parseResult = parseStringWithErrors(r''' +(int, T) f<T>() {} +'''); + parseResult.assertNoErrors(); + + var node = parseResult.findNode.recordTypeAnnotation('(int'); + assertParsedNodeText(node, r''' +RecordTypeAnnotation + leftParenthesis: ( + positionalFields + RecordTypeAnnotationPositionalField + type: NamedType + name: SimpleIdentifier + token: int + RecordTypeAnnotationPositionalField + type: NamedType + name: SimpleIdentifier + token: T + rightParenthesis: ) +'''); + } + void test_superFormalParameter() { var parseResult = parseStringWithErrors(r''' class A {
diff --git a/pkg/analyzer/test/src/summary/elements_test.dart b/pkg/analyzer/test/src/summary/elements_test.dart index 947fb99..5aa3ce5 100644 --- a/pkg/analyzer/test/src/summary/elements_test.dart +++ b/pkg/analyzer/test/src/summary/elements_test.dart
@@ -34859,6 +34859,69 @@ '''); } + test_recordType_class_typeParameter_bound() async { + var library = await buildLibrary(''' +class A<T extends (int, String)> {} +'''); + checkElementText(library, r''' +library + definingUnit + classes + class A @6 + typeParameters + covariant T @8 + bound: (int, String) + defaultType: (int, String) + constructors + synthetic @-1 +'''); + } + + test_recordType_extension_onType() async { + var library = await buildLibrary(''' +extension IntStringExtension on (int, String) {} +'''); + checkElementText(library, r''' +library + definingUnit + extensions + IntStringExtension @10 + extendedType: (int, String) +'''); + } + + test_recordType_functionType_formalParameter() async { + var library = await buildLibrary(''' +void f(void Function((int, String) a) b) {} +'''); + checkElementText(library, r''' +library + definingUnit + functions + f @5 + parameters + requiredPositional b @38 + type: void Function((int, String)) + returnType: void +'''); + } + + test_recordType_functionType_returnType() async { + var library = await buildLibrary(''' +void f((int, String) Function() a) {} +'''); + checkElementText(library, r''' +library + definingUnit + functions + f @5 + parameters + requiredPositional a @32 + type: (int, String) Function() + returnType: void +'''); + } + test_recordType_topFunction_formalParameter() async { var library = await buildLibrary(''' void f((int, String) a) {} @@ -34875,6 +34938,21 @@ '''); } + test_recordType_topFunction_returnType_generic() async { + var library = await buildLibrary(''' +(int, T) f<T>() {} +'''); + checkElementText(library, r''' +library + definingUnit + functions + f @9 + typeParameters + covariant T @11 + returnType: (int, T) +'''); + } + test_recordType_topFunction_returnType_mixed() async { var library = await buildLibrary(''' (int, String, {bool c}) f() {} @@ -34901,6 +34979,19 @@ '''); } + test_recordType_topFunction_returnType_nested() async { + var library = await buildLibrary(''' +((int, String), (bool, double)) f() {} +'''); + checkElementText(library, r''' +library + definingUnit + functions + f @32 + returnType: ((int, String), (bool, double)) +'''); + } + test_recordType_topFunction_returnType_nullable() async { var library = await buildLibrary(''' (int, String)? f() {} @@ -39467,7 +39558,7 @@ definingUnit typeAliases notSimplyBounded F @8 - aliasedType: (dynamic, dynamic) Function() + aliasedType: (dynamic, int) Function() aliasedElement: GenericFunctionTypeElement returnType: (dynamic, int) ''');