Support for resolving RecordType fields through type parameters bound with it.

Bug: https://github.com/dart-lang/sdk/issues/50006
Change-Id: I8b9fcff2c7bb560a764a336c8dceccbfdd06cdaa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/260420
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/dart/resolver/lexical_lookup.dart b/pkg/analyzer/lib/src/dart/resolver/lexical_lookup.dart
index 5cd5b33..201311c 100644
--- a/pkg/analyzer/lib/src/dart/resolver/lexical_lookup.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/lexical_lookup.dart
@@ -62,9 +62,13 @@
   /// The [FunctionType] referenced with `call`.
   final FunctionType? callFunctionType;
 
+  /// The field referenced in a [RecordType].
+  final RecordTypeField? recordField;
+
   LexicalLookupResult({
     this.requested,
     this.recovery,
     this.callFunctionType,
+    this.recordField,
   });
 }
diff --git a/pkg/analyzer/lib/src/dart/resolver/prefixed_identifier_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/prefixed_identifier_resolver.dart
index 60e06a3..7bdbdd1 100644
--- a/pkg/analyzer/lib/src/dart/resolver/prefixed_identifier_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/prefixed_identifier_resolver.dart
@@ -34,14 +34,18 @@
     if (prefixElement is! PrefixElement) {
       final prefixType = node.prefix.staticType;
       // TODO(scheglov) It would be nice to rewrite all such cases.
-      if (prefixType is RecordType) {
-        final propertyAccess = PropertyAccessImpl(
-          node.prefix,
-          node.period,
-          node.identifier,
-        );
-        NodeReplacer.replace(node, propertyAccess);
-        return propertyAccess;
+      if (prefixType != null) {
+        final prefixTypeResolved =
+            _resolver.typeSystem.resolveToBound(prefixType);
+        if (prefixTypeResolved is RecordType) {
+          final propertyAccess = PropertyAccessImpl(
+            node.prefix,
+            node.period,
+            node.identifier,
+          );
+          NodeReplacer.replace(node, propertyAccess);
+          return propertyAccess;
+        }
       }
     }
 
diff --git a/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
index 2da4373..f7e53ac 100644
--- a/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
@@ -238,12 +238,21 @@
     if (hasRead) {
       var readLookup = LexicalLookup.resolveGetter(scopeLookupResult) ??
           _resolver.thisLookupGetter(node);
+
       final callFunctionType = readLookup?.callFunctionType;
       if (callFunctionType != null) {
         return PropertyElementResolverResult(
           functionTypeCallType: callFunctionType,
         );
       }
+
+      final recordField = readLookup?.recordField;
+      if (recordField != null) {
+        return PropertyElementResolverResult(
+          recordField: recordField,
+        );
+      }
+
       readElementRequested = _resolver.toLegacyElement(readLookup?.requested);
       if (readElementRequested is PropertyAccessorElement &&
           !readElementRequested.isStatic) {
@@ -450,24 +459,6 @@
       return PropertyElementResolverResult();
     }
 
-    if (targetType is RecordType) {
-      final name = propertyName.name;
-      final field = targetType.fieldByName(name);
-      if (field != null) {
-        if (hasWrite) {
-          AssignmentVerifier(_definingLibrary, errorReporter).verify(
-            node: propertyName,
-            requested: null,
-            recovery: null,
-            receiverType: targetType,
-          );
-        }
-        return PropertyElementResolverResult(
-          recordField: field,
-        );
-      }
-    }
-
     var result = _resolver.typePropertyResolver.resolve(
       receiver: target,
       receiverType: targetType,
@@ -511,6 +502,7 @@
       readElementRecovery: result.setter,
       writeElementRequested: result.setter,
       writeElementRecovery: result.getter,
+      recordField: result.recordField,
     );
   }
 
diff --git a/pkg/analyzer/lib/src/dart/resolver/resolution_result.dart b/pkg/analyzer/lib/src/dart/resolver/resolution_result.dart
index 52653d4..7c7cdc7 100644
--- a/pkg/analyzer/lib/src/dart/resolver/resolution_result.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/resolution_result.dart
@@ -47,6 +47,9 @@
   /// The [FunctionType] referenced with `call`.
   final FunctionType? callFunctionType;
 
+  /// The field referenced in a [RecordType].
+  final RecordTypeField? recordField;
+
   /// Initialize a newly created result to represent resolving a single
   /// reading and / or writing result.
   ResolutionResult({
@@ -55,6 +58,7 @@
     this.setter,
     this.needsSetterError = true,
     this.callFunctionType,
+    this.recordField,
   }) : state = _ResolutionResultState.single;
 
   /// Initialize a newly created result with no elements and the given [state].
@@ -63,7 +67,8 @@
         needsGetterError = true,
         setter = null,
         needsSetterError = true,
-        callFunctionType = null;
+        callFunctionType = null,
+        recordField = null;
 
   /// Return `true` if this result represents the case where multiple ambiguous
   /// elements were found.
diff --git a/pkg/analyzer/lib/src/dart/resolver/simple_identifier_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/simple_identifier_resolver.dart
index 5df5896..2136d81 100644
--- a/pkg/analyzer/lib/src/dart/resolver/simple_identifier_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/simple_identifier_resolver.dart
@@ -9,7 +9,6 @@
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
 import 'package:analyzer/src/dart/element/element.dart';
-import 'package:analyzer/src/dart/element/extensions.dart';
 import 'package:analyzer/src/dart/element/type.dart';
 import 'package:analyzer/src/dart/element/type_provider.dart';
 import 'package:analyzer/src/dart/resolver/invocation_inference_helper.dart';
@@ -38,18 +37,6 @@
       return;
     }
 
-    final thisType = _resolver.thisType;
-    if (thisType is RecordType) {
-      final wasResolved = _resolveOfThisRecordType(
-        node: node,
-        recordType: thisType,
-        contextType: contextType,
-      );
-      if (wasResolved) {
-        return;
-      }
-    }
-
     _resolver.checkUnreachableNode(node);
 
     _resolver.checkReadOfNotAssignedLocalVariable(node, node.staticElement);
@@ -194,6 +181,14 @@
       return;
     }
 
+    final recordField = result.recordField;
+    if (recordField != null) {
+      _inferenceHelper.recordStaticType(node, recordField.type,
+          contextType: contextType);
+      _currentAlreadyResolved = true;
+      return;
+    }
+
     var element = hasRead ? result.readElement : result.writeElement;
 
     var enclosingClass = _resolver.enclosingClass;
@@ -294,22 +289,6 @@
         contextType: contextType);
   }
 
-  /// This can happen only inside an extension.
-  bool _resolveOfThisRecordType({
-    required SimpleIdentifierImpl node,
-    required RecordType recordType,
-    required DartType? contextType,
-  }) {
-    final field = recordType.fieldByName(node.name);
-    if (field != null) {
-      _inferenceHelper.recordStaticType(node, field.type,
-          contextType: contextType);
-      return true;
-    } else {
-      return false;
-    }
-  }
-
   /// TODO(scheglov) this is duplicate
   void _setExtensionIdentifierType(IdentifierImpl node) {
     if (node is SimpleIdentifierImpl && node.inDeclarationContext()) {
diff --git a/pkg/analyzer/lib/src/dart/resolver/this_lookup.dart b/pkg/analyzer/lib/src/dart/resolver/this_lookup.dart
index 4102c69..8b79e08 100644
--- a/pkg/analyzer/lib/src/dart/resolver/this_lookup.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/this_lookup.dart
@@ -40,6 +40,13 @@
       );
     }
 
+    final recordField = propertyResult.recordField;
+    if (recordField != null) {
+      return LexicalLookupResult(
+        recordField: recordField,
+      );
+    }
+
     var getterElement = propertyResult.getter;
     if (getterElement != null) {
       return LexicalLookupResult(requested: getterElement);
diff --git a/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
index adf83b5..f220df5 100644
--- a/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
@@ -8,6 +8,7 @@
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/diagnostic/diagnostic.dart';
 import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/extensions.dart';
 import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
 import 'package:analyzer/src/dart/element/type_provider.dart';
 import 'package:analyzer/src/dart/element/type_system.dart';
@@ -203,6 +204,13 @@
       }
 
       if (receiverTypeResolved is RecordType) {
+        final field = receiverTypeResolved.fieldByName(name);
+        if (field != null) {
+          return ResolutionResult(
+            recordField: field,
+            needsGetterError: false,
+          );
+        }
         _needsGetterError = true;
         _needsSetterError = true;
       }
diff --git a/pkg/analyzer/test/src/dart/resolution/property_access_test.dart b/pkg/analyzer/test/src/dart/resolution/property_access_test.dart
index b28c322..bb6816e 100644
--- a/pkg/analyzer/test/src/dart/resolution/property_access_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/property_access_test.dart
@@ -399,6 +399,29 @@
 ''');
   }
 
+  test_ofRecordType_namedField_ofTypeParameter() async {
+    await assertNoErrorsInCode(r'''
+void f<T extends ({int foo})>(T r) {
+  r.foo;
+}
+''');
+
+    final node = findNode.propertyAccess(r'foo;');
+    assertResolvedNodeText(node, r'''
+PropertyAccess
+  target: SimpleIdentifier
+    token: r
+    staticElement: self::@function::f::@parameter::r
+    staticType: T
+  operator: .
+  propertyName: SimpleIdentifier
+    token: foo
+    staticElement: <null>
+    staticType: int
+  staticType: int
+''');
+  }
+
   test_ofRecordType_Object_hashCode() async {
     await assertNoErrorsInCode('''
 void f(({int foo}) r) {
@@ -622,6 +645,29 @@
 ''');
   }
 
+  test_ofRecordType_positionalField_ofTypeParameter() async {
+    await assertNoErrorsInCode(r'''
+void f<T extends (int, String)>(T r) {
+  r.$0;
+}
+''');
+
+    final node = findNode.propertyAccess(r'$0;');
+    assertResolvedNodeText(node, r'''
+PropertyAccess
+  target: SimpleIdentifier
+    token: r
+    staticElement: self::@function::f::@parameter::r
+    staticType: T
+  operator: .
+  propertyName: SimpleIdentifier
+    token: $0
+    staticElement: <null>
+    staticType: int
+  staticType: int
+''');
+  }
+
   test_ofRecordType_unresolved() async {
     await assertErrorsInCode('''
 void f(({int foo}) r) {
diff --git a/pkg/analyzer/test/src/dart/resolution/simple_identifier_test.dart b/pkg/analyzer/test/src/dart/resolution/simple_identifier_test.dart
index e899108..85fdc46 100644
--- a/pkg/analyzer/test/src/dart/resolution/simple_identifier_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/simple_identifier_test.dart
@@ -116,6 +116,42 @@
 ''');
   }
 
+  test_inExtension_onRecordType_fromTypeParameterBound_named() async {
+    await assertNoErrorsInCode('''
+extension E<T extends ({int foo})> on T {
+  void f() {
+    foo;
+  }
+}
+''');
+
+    final node = findNode.simple('foo;');
+    assertResolvedNodeText(node, r'''
+SimpleIdentifier
+  token: foo
+  staticElement: <null>
+  staticType: int
+''');
+  }
+
+  test_inExtension_onRecordType_fromTypeParameterBound_positional() async {
+    await assertNoErrorsInCode(r'''
+extension E<T extends (int, String)> on T {
+  void f() {
+    $0;
+  }
+}
+''');
+
+    final node = findNode.simple(r'$0;');
+    assertResolvedNodeText(node, r'''
+SimpleIdentifier
+  token: $0
+  staticElement: <null>
+  staticType: int
+''');
+  }
+
   test_inExtension_onRecordType_named() async {
     await assertNoErrorsInCode('''
 extension E on ({int foo}) {