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)
 ''');