[dart2js] Dynamic calls to record getters.

Added synthetic getters for records that are attached to the shape class.

It is a bit clunky since there are assumptions that a method came from something defined in Kernel.

- There is no source information for the synthetic method
- There are assumptions that some information can be derived by reference to Kernel definitions.

We do get a lot for free doing it this way, e.g. tree-shaking of unused getters and call-through stubs of getters used as a dynamic method invocation.

Added a test that (1) uses names that require the interceptor calling convention and (2) requires dart2js to generate call-through stubs.

Issue: #49718

Change-Id: I682c1f005e87765b0e3bf7e6550c945d30b56b8d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/280960
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Mayank Patke <fishythefish@google.com>
diff --git a/pkg/compiler/lib/src/inferrer/engine.dart b/pkg/compiler/lib/src/inferrer/engine.dart
index db37f22..75e72e1d 100644
--- a/pkg/compiler/lib/src/inferrer/engine.dart
+++ b/pkg/compiler/lib/src/inferrer/engine.dart
@@ -540,6 +540,7 @@
       case MemberKind.closureField:
       case MemberKind.signature:
       case MemberKind.generatorBody:
+      case MemberKind.recordGetter:
         break;
     }
     failedAt(member, 'Unexpected member definition: $definition.');
diff --git a/pkg/compiler/lib/src/inferrer_experimental/engine.dart b/pkg/compiler/lib/src/inferrer_experimental/engine.dart
index 7320c57..a20d71d 100644
--- a/pkg/compiler/lib/src/inferrer_experimental/engine.dart
+++ b/pkg/compiler/lib/src/inferrer_experimental/engine.dart
@@ -556,6 +556,7 @@
       case MemberKind.closureField:
       case MemberKind.signature:
       case MemberKind.generatorBody:
+      case MemberKind.recordGetter:
         break;
     }
     failedAt(member, 'Unexpected member definition: $definition.');
diff --git a/pkg/compiler/lib/src/io/kernel_source_information.dart b/pkg/compiler/lib/src/io/kernel_source_information.dart
index 16dda87..b265a53 100644
--- a/pkg/compiler/lib/src/io/kernel_source_information.dart
+++ b/pkg/compiler/lib/src/io/kernel_source_information.dart
@@ -161,7 +161,7 @@
   ///
   /// This is used function declarations and return expressions which both point
   /// to the end of the member as the closing position.
-  SourceInformation _buildFunctionEnd(MemberEntity member,
+  SourceInformation? _buildFunctionEnd(MemberEntity member,
       [ir.TreeNode? base]) {
     MemberDefinition definition = _elementMap.getMemberDefinition(member);
     String? name = computeKernelElementNameForSourceMaps(_elementMap, member);
@@ -179,6 +179,8 @@
       case MemberKind.closureCall:
         final node = definition.node as ir.LocalFunction;
         return _buildFunction(name, base ?? node, node.function);
+      case MemberKind.recordGetter:
+        return null;
       // TODO(sra): generatorBody
       default:
     }
@@ -219,7 +221,8 @@
   }
 
   /// Creates source information for the body of the current member.
-  SourceInformation _buildMemberBody() {
+  // TODO(51310): Remove nullable return type.
+  SourceInformation? _buildMemberBody() {
     MemberDefinition definition = _elementMap.getMemberDefinition(_member);
     switch (definition.kind) {
       case MemberKind.regular:
@@ -250,6 +253,15 @@
           return _buildBody(node, node.function!.body);
         }
         break;
+      case MemberKind.recordGetter:
+        // This is a completely synthetic element. Perhaps we can use
+        // definition.location, but that is often 'nowhere'.
+
+        // TODO(51310): Perhaps we should not end up in
+        // [KernelSourceInformationBuilder] for synthetic elements that are not
+        // defined by Kernel ASTs.
+        return null;
+
       default:
     }
     return _buildTreeNode(definition.node as ir.TreeNode);
@@ -372,7 +384,7 @@
   SourceInformation? buildForeignCode(ir.Node node) => null;
 
   @override
-  SourceInformation buildVariableDeclaration() {
+  SourceInformation? buildVariableDeclaration() {
     return _buildMemberBody();
   }
 
@@ -388,7 +400,7 @@
 
   @override
   SourceInformation buildAsyncBody() {
-    return _buildMemberBody();
+    return _buildMemberBody()!;
   }
 
   @override
@@ -448,7 +460,7 @@
 
   @override
   SourceInformation buildReturn(ir.TreeNode node) {
-    return _buildFunctionEnd(_member, node);
+    return _buildFunctionEnd(_member, node)!;
   }
 
   @override
@@ -465,7 +477,7 @@
   SourceInformation? buildGeneric(ir.Node node) => null;
 
   @override
-  SourceInformation buildDeclaration(MemberEntity member) {
+  SourceInformation? buildDeclaration(MemberEntity member) {
     return _buildFunctionEnd(member);
   }
 
diff --git a/pkg/compiler/lib/src/ir/static_type_provider.dart b/pkg/compiler/lib/src/ir/static_type_provider.dart
index 2dc7ace..a466566 100644
--- a/pkg/compiler/lib/src/ir/static_type_provider.dart
+++ b/pkg/compiler/lib/src/ir/static_type_provider.dart
@@ -9,3 +9,18 @@
   ir.DartType getStaticType(ir.Expression node);
   ir.DartType getForInIteratorType(ir.ForInStatement node);
 }
+
+/// A static type provider for a context with no Kernel nodes.
+// TODO(51310): Refactor so that a StaticTypeProvider is not required for
+// synthetic elements.
+class NoStaticTypeProvider implements StaticTypeProvider {
+  @override
+  ir.DartType getStaticType(ir.Expression node) {
+    throw UnsupportedError('NoStaticTypeProvider.getStaticType');
+  }
+
+  @override
+  ir.DartType getForInIteratorType(ir.ForInStatement node) {
+    throw UnsupportedError('NoStaticTypeProvider.getForInIteratorType');
+  }
+}
diff --git a/pkg/compiler/lib/src/js_model/element_map.dart b/pkg/compiler/lib/src/js_model/element_map.dart
index 3ad9880..1fda8a5 100644
--- a/pkg/compiler/lib/src/js_model/element_map.dart
+++ b/pkg/compiler/lib/src/js_model/element_map.dart
@@ -341,6 +341,9 @@
 
   /// A separated body of a generator (sync*/async/async*) function.
   generatorBody,
+
+  /// A dynamic getter for a field of a record.
+  recordGetter,
 }
 
 /// Definition information for a [MemberEntity].
@@ -372,6 +375,8 @@
       case MemberKind.closureCall:
       case MemberKind.closureField:
         return ClosureMemberDefinition.readFromDataSource(source, kind);
+      case MemberKind.recordGetter:
+        return RecordGetterDefinition.readFromDataSource(source);
     }
   }
 
@@ -508,29 +513,28 @@
   String toString() => 'ClosureMemberDefinition(kind:$kind,location:$location)';
 }
 
-/// Definition for a Record member. This is almost useless, since there is no
-/// location or corresponding ir.Node.
-class RecordMemberDefinition implements MemberDefinition {
+/// Definition for a record getter member.
+class RecordGetterDefinition implements MemberDefinition {
   /// Tag used for identifying serialized [RecordMemberDefinition] objects in a
   /// debugging data stream.
-  static const String tag = 'record-member-definition';
+  static const String tag = 'record-getter-definition';
 
   @override
   final SourceSpan location;
-  @override
-  final MemberKind kind;
+
+  final int indexInShape;
 
   @override
-  ir.TreeNode get node => throw UnsupportedError('RecordMemberDefinition.node');
+  ir.TreeNode get node => throw UnsupportedError('RecordGetterDefinition.node');
 
-  RecordMemberDefinition(this.location, this.kind);
+  RecordGetterDefinition(this.location, this.indexInShape);
 
-  factory RecordMemberDefinition.readFromDataSource(
-      DataSourceReader source, MemberKind kind) {
+  factory RecordGetterDefinition.readFromDataSource(DataSourceReader source) {
     source.begin(tag);
     SourceSpan location = source.readSourceSpan();
+    int indexInShape = source.readInt();
     source.end(tag);
-    return RecordMemberDefinition(location, kind);
+    return RecordGetterDefinition(location, indexInShape);
   }
 
   @override
@@ -538,11 +542,16 @@
     sink.writeEnum(kind);
     sink.begin(tag);
     sink.writeSourceSpan(location);
+    sink.writeInt(indexInShape);
     sink.end(tag);
   }
 
   @override
-  String toString() => 'RecordMemberDefinition(kind:$kind,location:$location)';
+  MemberKind get kind => MemberKind.recordGetter;
+
+  @override
+  String toString() =>
+      'RecordGetterDefinition(indexInShape:$indexInShape,location:$location)';
 }
 
 void forEachOrderedParameterByFunctionNode(
diff --git a/pkg/compiler/lib/src/js_model/element_map_impl.dart b/pkg/compiler/lib/src/js_model/element_map_impl.dart
index 49ea492..7e5abe9 100644
--- a/pkg/compiler/lib/src/js_model/element_map_impl.dart
+++ b/pkg/compiler/lib/src/js_model/element_map_impl.dart
@@ -55,7 +55,8 @@
 import 'element_map.dart';
 import 'env.dart';
 import 'locals.dart';
-import 'records.dart' show JRecordClass, RecordClassData;
+import 'records.dart'
+    show JRecordClass, RecordClassData, JRecordGetter, RecordGetterData;
 
 class JsKernelToElementMap implements JsToElementMap, IrToElementMap {
   /// Tag used for identifying serialized [JsKernelToElementMap] objects in a
@@ -1228,6 +1229,11 @@
           node = node.parent;
         }
         break;
+
+      case MemberKind.recordGetter:
+        // TODO(51310): Avoid calling [getStaticTypeProvider] for synthetic
+        // elements that have no Kernel Node context.
+        return NoStaticTypeProvider();
     }
     return CachedStaticType(staticTypeContext, cachedStaticTypes,
         ThisInterfaceType.from(staticTypeContext.thisType));
@@ -1701,6 +1707,8 @@
       case MemberKind.signature:
       case MemberKind.generatorBody:
         return getParentMember(definition.node as ir.TreeNode?);
+      case MemberKind.recordGetter:
+        return null;
     }
   }
 
@@ -2193,16 +2201,12 @@
     SourceSpan location = SourceSpan.unknown(); // TODO(50081): What to use?
 
     Map<Name, IndexedMember> memberMap = {};
-    IndexedClass classEntity = JRecordClass(
-      library,
-      name,
-      isAbstract: false,
-    );
+    final classEntity = JRecordClass(library, name, isAbstract: false);
 
-    // Create a classData and set up the interfaces and subclass
-    // relationships that _ensureSupertypes and _ensureThisAndRawType are doing
-    InterfaceType thisType =
-        types.interfaceType(classEntity, const <DartType>[]);
+    // Create a classData and set up the interfaces and subclass relationships
+    // that for regular classes would be done by _ensureSupertypes and
+    // _ensureThisAndRawType.
+    InterfaceType thisType = types.interfaceType(classEntity, const []);
     RecordClassData recordData = RecordClassData(
         RecordClassDefinition(location),
         thisType,
@@ -2211,6 +2215,32 @@
             .extendClass(types, thisType));
     classes.register(classEntity, recordData, RecordClassEnv(memberMap));
 
+    // Add field getters, which are called only from dynamic getter invocations.
+
+    for (int i = 0; i < shape.fieldCount; i++) {
+      String name = i < shape.positionalFieldCount
+          ? '\$${i + 1}'
+          : shape.fieldNames[i - shape.positionalFieldCount];
+      Name memberName = Name(name, null);
+      final getter = JRecordGetter(classEntity, memberName);
+
+      // The function type of a dynamic getter is a function of no arguments
+      // that returns `dynamic` (any other top would be ok too).
+      FunctionType functionType = commonElements.dartTypes.functionType(
+          commonElements.dartTypes.dynamicType(),
+          const [],
+          const [],
+          const [],
+          const {},
+          const [],
+          const []);
+      final data = RecordGetterData(
+          RecordGetterDefinition(location, i), thisType, functionType);
+
+      members.register<IndexedFunction, FunctionData>(getter, data);
+      memberMap[memberName] = getter;
+    }
+
     // TODO(49718): Implement `==` specialized to the shape.
 
     return classEntity;
diff --git a/pkg/compiler/lib/src/js_model/elements.dart b/pkg/compiler/lib/src/js_model/elements.dart
index 96c9cfb..4b1ce72 100644
--- a/pkg/compiler/lib/src/js_model/elements.dart
+++ b/pkg/compiler/lib/src/js_model/elements.dart
@@ -12,7 +12,7 @@
 import '../serialization/serialization.dart';
 import '../universe/class_set.dart' show ClassHierarchyNodesMapKey;
 import 'closure.dart';
-import 'records.dart' show JRecordClass;
+import 'records.dart' show JRecordClass, JRecordGetter;
 
 const String jsElementPrefix = 'j:';
 
@@ -122,6 +122,7 @@
   generatorBody,
   signatureMethod,
   contextField,
+  recordGetter,
 }
 
 abstract class JMember extends IndexedMember {
@@ -164,6 +165,8 @@
         return JSignatureMethod.readFromDataSource(source);
       case JMemberKind.contextField:
         return JContextField.readFromDataSource(source);
+      case JMemberKind.recordGetter:
+        return JRecordGetter.readFromDataSource(source);
     }
   }
 
diff --git a/pkg/compiler/lib/src/js_model/env.dart b/pkg/compiler/lib/src/js_model/env.dart
index a0f8215..ceda748 100644
--- a/pkg/compiler/lib/src/js_model/env.dart
+++ b/pkg/compiler/lib/src/js_model/env.dart
@@ -32,7 +32,7 @@
         forEachOrderedParameterByFunctionNode;
 import 'element_map_impl.dart';
 import 'elements.dart';
-import 'records.dart' show RecordClassData;
+import 'records.dart' show RecordClassData, RecordGetterData;
 
 /// Environment for fast lookup of component libraries.
 class JProgramEnv {
@@ -581,6 +581,7 @@
   generatorBody,
   closureFunction,
   closureField,
+  recordGetter,
 }
 
 abstract class JMemberData {
@@ -614,6 +615,8 @@
         return ClosureFunctionData.readFromDataSource(source);
       case JMemberDataKind.closureField:
         return ClosureFieldData.readFromDataSource(source);
+      case JMemberDataKind.recordGetter:
+        return RecordGetterData.readFromDataSource(source);
     }
   }
 
diff --git a/pkg/compiler/lib/src/js_model/records.dart b/pkg/compiler/lib/src/js_model/records.dart
index ad44998..e35ec54 100644
--- a/pkg/compiler/lib/src/js_model/records.dart
+++ b/pkg/compiler/lib/src/js_model/records.dart
@@ -30,15 +30,19 @@
 library dart2js.js_model.records;
 
 import '../common.dart';
+import '../constants/values.dart' show ConstantValue;
 import '../elements/entities.dart';
 import '../elements/names.dart';
 import '../elements/types.dart';
-
+import '../ir/element_map.dart' show IrToElementMap;
+import '../ir/static_type_cache.dart';
 import '../js_backend/annotations.dart';
-import '../js_model/element_map.dart';
 import '../ordered_typeset.dart';
 import '../serialization/serialization.dart';
 import '../universe/record_shape.dart';
+import 'class_type_variable_access.dart';
+import 'element_map.dart';
+import 'element_map_impl.dart' show JsKernelToElementMap;
 import 'elements.dart';
 import 'env.dart';
 import 'js_world_builder.dart' show JClosedWorldBuilder;
@@ -358,3 +362,124 @@
   @override
   List<Variance> getVariances() => [];
 }
+
+class JRecordGetter extends JFunction {
+  /// Tag used for identifying serialized [JRecordGetter] objects in a
+  /// debugging data stream.
+  static const String tag = 'record-getter';
+
+  JRecordGetter(JClass enclosingClass, Name name)
+      : super(enclosingClass.library, enclosingClass, name,
+            ParameterStructure.getter, AsyncMarker.SYNC,
+            isStatic: false, isExternal: false);
+
+  factory JRecordGetter.readFromDataSource(DataSourceReader source) {
+    source.begin(tag);
+    JClass enclosingClass = source.readClass() as JClass;
+    Name memberName = source.readMemberName();
+    source.end(tag);
+    return JRecordGetter(enclosingClass, memberName);
+  }
+
+  @override
+  void writeToDataSink(DataSinkWriter sink) {
+    sink.writeEnum(JMemberKind.recordGetter);
+    sink.begin(tag);
+    sink.writeClass(enclosingClass!);
+    sink.writeMemberName(memberName);
+    sink.end(tag);
+  }
+
+  @override
+  bool get isAbstract => false;
+
+  @override
+  bool get isGetter => true;
+
+  @override
+  String toString() => '${jsElementPrefix}record_getter'
+      '(${enclosingClass!.name}.$name)';
+}
+
+abstract class RecordMemberData implements JMemberData {
+  @override
+  final MemberDefinition definition;
+  final InterfaceType? memberThisType;
+
+  RecordMemberData(this.definition, this.memberThisType);
+
+  @override
+  StaticTypeCache get staticTypes {
+    // The cached types are stored in the data for enclosing member.
+    throw UnsupportedError('RecordMemberData.staticTypes');
+  }
+
+  @override
+  InterfaceType? getMemberThisType(covariant JsToElementMap elementMap) {
+    return memberThisType;
+  }
+}
+
+class RecordGetterData extends RecordMemberData implements FunctionData {
+  /// Tag used for identifying serialized [RecordGetterData] objects in a
+  /// debugging data stream.
+  static const String tag = 'record-getter-data';
+
+  final FunctionType functionType;
+
+  RecordGetterData(super.definition, super.memberThisType, this.functionType);
+
+  RecordGetterData._deserialized(
+      super.definition, super.memberThisType, this.functionType);
+
+  factory RecordGetterData.readFromDataSource(DataSourceReader source) {
+    source.begin(tag);
+    MemberDefinition definition = MemberDefinition.readFromDataSource(source);
+    InterfaceType? memberThisType =
+        source.readDartTypeOrNull() as InterfaceType?;
+    FunctionType functionType = source.readDartType() as FunctionType;
+    source.end(tag);
+    return RecordGetterData._deserialized(
+        definition, memberThisType, functionType);
+  }
+
+  @override
+  void writeToDataSink(DataSinkWriter sink) {
+    sink.writeEnum(JMemberDataKind.recordGetter);
+    sink.begin(tag);
+    definition.writeToDataSink(sink);
+    sink.writeDartTypeOrNull(memberThisType);
+    sink.writeDartType(functionType);
+    sink.end(tag);
+  }
+
+  @override
+  ClassTypeVariableAccess get classTypeVariableAccess =>
+      ClassTypeVariableAccess.none;
+
+  @override
+  List<TypeVariableType> getFunctionTypeVariables(
+      covariant JsKernelToElementMap unusedElementMap) {
+    return const <TypeVariableType>[];
+  }
+
+  @override
+  void forEachParameter(
+      JsToElementMap elementMap,
+      ParameterStructure parameterStructure,
+      void f(DartType type, String? name, ConstantValue? defaultValue),
+      {bool isNative = false}) {
+    // This `throw` can be removed if `RecordGetterData.forEachParameter` is
+    // used from general code via `FunctionData.forEachParameter`.
+    throw UnsupportedError('${runtimeType}.forEachParameter');
+  }
+
+  @override
+  // It is a bit of a code-smell here that an synthetic element introduced
+  // during creation of the K-world depends on Kernel. Perhaps it would be
+  // better to compute this type and serialize it for all functions, although
+  // that is redundant with the Kernel IR for most functions.
+  FunctionType getFunctionType(IrToElementMap unusedElementMap) {
+    return functionType;
+  }
+}
diff --git a/pkg/compiler/lib/src/ssa/builder.dart b/pkg/compiler/lib/src/ssa/builder.dart
index b134c1d..133a7ec 100644
--- a/pkg/compiler/lib/src/ssa/builder.dart
+++ b/pkg/compiler/lib/src/ssa/builder.dart
@@ -44,7 +44,7 @@
 import '../js_model/js_world.dart' show JClosedWorld;
 import '../js_model/locals.dart' show GlobalLocalsMap, JumpVisitor;
 import '../js_model/type_recipe.dart';
-import '../js_model/records.dart' show RecordData;
+import '../js_model/records.dart' show RecordData, JRecordGetter;
 import '../kernel/invocation_mirror_constants.dart';
 import '../native/behavior.dart';
 import '../native/js.dart';
@@ -76,7 +76,7 @@
   final Map<ir.VariableDeclaration, HInstruction> letBindings;
   final KernelToTypeInferenceMap typeInferenceMap;
   final SourceInformationBuilder sourceInformationBuilder;
-  final StaticTypeProvider staticTypeProvider;
+  final StaticTypeProvider? staticTypeProvider;
 
   StackFrame(
       this.parent,
@@ -305,6 +305,17 @@
     from.addSuccessor(to);
   }
 
+  void _prepareEntryBlock() {
+    HBasicBlock block = graph.addNewBlock();
+    // Create `graph.entry` as an initially empty block. `graph.entry` is
+    // treated specially (holding parameters, local variables and constants)
+    // but cannot receive constants before it has been closed. By closing it
+    // here, we can use constants in the code that sets up the function.
+    open(graph.entry);
+    close(HGoto(_abstractValueDomain)).addSuccessor(block);
+    open(block);
+  }
+
   bool isAborted() {
     return current == null;
   }
@@ -393,7 +404,7 @@
   StaticType _getStaticType(ir.Expression node) {
     // TODO(johnniwinther): Substitute the type by the this type and type
     // arguments of the current frame.
-    ir.DartType type = _currentFrame!.staticTypeProvider.getStaticType(node);
+    ir.DartType type = _currentFrame!.staticTypeProvider!.getStaticType(node);
     return StaticType(
         _elementMap.getDartType(type), computeClassRelationFromType(type));
   }
@@ -402,7 +413,7 @@
     // TODO(johnniwinther): Substitute the type by the this type and type
     // arguments of the current frame.
     ir.DartType type =
-        _currentFrame!.staticTypeProvider.getForInIteratorType(node);
+        _currentFrame!.staticTypeProvider!.getForInIteratorType(node);
     return StaticType(
         _elementMap.getDartType(type), computeClassRelationFromType(type));
   }
@@ -532,6 +543,10 @@
           _buildGeneratorBody(_initialTargetElement as JGeneratorBody,
               _functionNodeOf(definition.node)!);
           break;
+        case MemberKind.recordGetter:
+          _buildRecordGetter(_initialTargetElement as JRecordGetter,
+              definition as RecordGetterDefinition);
+          break;
       }
       assert(graph.isValid(), "Invalid graph for $_initialTargetElement.");
 
@@ -1231,6 +1246,73 @@
     _closeFunction();
   }
 
+  void _buildRecordGetter(
+      JRecordGetter getter, RecordGetterDefinition definition) {
+    ClassEntity getterClass = getter.enclosingClass!;
+    int indexInShape = definition.indexInShape;
+    final representation = _recordData.representationForClass(getterClass)!;
+    final path = _recordData.pathForAccess(representation.shape, indexInShape);
+
+    // TODO(50081): Attribute all synthetic records code to the nearest class
+    // declared in Dart. Worst case, it can all be attributed to the `Record`
+    // interface.
+    SourceInformation? sourceInformation;
+
+    // Manually set up entry. This does not work...
+    //
+    //     _openFunction(getter, checks: TargetChecks.none);
+    //
+    // ...since we don't have a scope model for the localsHandler. What we
+    // should have is a lightweight localsHandler for synthetic methods.
+    //
+    // TODO(51310): Split [_openFunction] into parts which can be used for
+    // synthetic methods.
+
+    _prepareEntryBlock();
+
+    // Create a 'this' parameter.
+    //
+    // Intercepted getters have two parameters (this, receiver) and other
+    // getters have one (this). Add them at the beginning of the entry block.
+
+    final typeOfThis = _abstractValueDomain.createNonNullSubclass(getterClass);
+    HThis thisInstruction = HThis(null, typeOfThis);
+    graph.thisInstruction = thisInstruction;
+    graph.entry.addAtEntry(thisInstruction);
+    lastAddedParameter = thisInstruction;
+
+    if (_interceptorData.isInterceptedMethod(getter)) {
+      SyntheticLocal parameter = localsHandler.createLocal('receiver');
+      HParameterValue value = HParameterValue(parameter, typeOfThis);
+      graph.explicitReceiverParameter = value;
+      graph.entry.addAfter(thisInstruction, value);
+      lastAddedParameter = value;
+    }
+
+    HInstruction receiver = thisInstruction;
+
+    AbstractValue resultType = _abstractValueDomain.dynamicType;
+
+    if (path.index == null) {
+      HFieldGet fieldGet = HFieldGet(
+          path.field, receiver, resultType, sourceInformation,
+          isAssignable: false);
+      push(fieldGet);
+    } else {
+      HFieldGet fieldGet = HFieldGet(path.field, receiver,
+          _abstractValueDomain.constListType, sourceInformation,
+          isAssignable: false);
+      push(fieldGet);
+      final list = pop();
+      push(HIndex(
+          list, graph.addConstantInt(path.index!, closedWorld), resultType));
+    }
+
+    HInstruction value = pop();
+    _closeAndGotoExit(HReturn(_abstractValueDomain, value, sourceInformation));
+    _closeFunction();
+  }
+
   /// Builds an SSA graph for FunctionNodes, found in FunctionExpressions and
   /// Procedures.
   void _buildFunctionNode(
@@ -1681,14 +1763,7 @@
       _returnType = _elementMap.getDartType(functionNode.returnType);
     }
 
-    HBasicBlock block = graph.addNewBlock();
-    // Create `graph.entry` as an initially empty block. `graph.entry` is
-    // treated specially (holding parameters, local variables and constants)
-    // but cannot receive constants before it has been closed. By closing it
-    // here, we can use constants in the code that sets up the function.
-    open(graph.entry);
-    close(HGoto(_abstractValueDomain)).addSuccessor(block);
-    open(block);
+    _prepareEntryBlock();
 
     localsHandler.startFunction(targetElement, parameterMap, elidedParameterSet,
         _sourceInformationBuilder.buildDeclaration(targetElement),
diff --git a/pkg/compiler/lib/src/ssa/nodes.dart b/pkg/compiler/lib/src/ssa/nodes.dart
index 5e2a7b5..1b27d2c 100644
--- a/pkg/compiler/lib/src/ssa/nodes.dart
+++ b/pkg/compiler/lib/src/ssa/nodes.dart
@@ -255,6 +255,7 @@
   // store it on HInstruction, or maybe this can be computed on demand).
   final Set<HInstruction> allocatedFixedLists = {};
 
+  /// SourceInformation for the 'graph' is the location of the entry
   SourceInformation? sourceInformation;
 
   // We canonicalize all constants used within a graph so we do not
diff --git a/tests/language/records/simple/dynamic_field_access_2_test.dart b/tests/language/records/simple/dynamic_field_access_2_test.dart
new file mode 100644
index 0000000..f3e2906
--- /dev/null
+++ b/tests/language/records/simple/dynamic_field_access_2_test.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// SharedOptions=--enable-experiment=records
+// VMOptions=--lazy-dispatchers
+// VMOptions=--no-lazy-dispatchers
+
+import "package:expect/expect.dart";
+
+// Dynamic accesses to record fields that have the same name as a getter on some
+// other type. Fields used as getters and method calls.  System List type on
+// dart2js an 'intercepted', with a different calling convention.
+
+@pragma('dart2js:never-inline')
+@pragma('m:never-inline')
+int combineFirstLast(dynamic d) {
+  return d.first * 10 + d.last;
+}
+
+@pragma('dart2js:never-inline')
+@pragma('m:never-inline')
+int chainFirstLast(dynamic d, int x) {
+  return d.last(d.first(x));
+}
+
+int mul10(int x) => x * 10;
+int add1(int x) => x + 1;
+
+void main() {
+  Expect.equals(19, combineFirstLast((first: 1, last: 9)));
+  Expect.equals(19, combineFirstLast((last: 9, next: 666, first: 1)));
+  Expect.equals(19, combineFirstLast((4, 5, last: 9, first: 1)));
+  Expect.equals(19, combineFirstLast([1, 9]));
+  Expect.equals(19, combineFirstLast({1, 9}));
+
+  Expect.equals(82, combineFirstLast((first: 8, last: 2)));
+  Expect.equals(82, combineFirstLast((last: 2, next: 666, first: 8)));
+  Expect.equals(82, combineFirstLast((4, last: 2, first: 8, 5)));
+  Expect.equals(82, combineFirstLast([8, 2]));
+  Expect.equals(82, combineFirstLast({8, 2}));
+
+  Expect.equals(70, chainFirstLast((first: add1, last: mul10), 6));
+  Expect.equals(70, chainFirstLast((first: add1, next: null, last: mul10), 6));
+  Expect.equals(70, chainFirstLast((null, last: mul10, null, first: add1), 6));
+  Expect.equals(70, chainFirstLast([add1, mul10], 6));
+  Expect.equals(70, chainFirstLast({add1, mul10}, 6));
+
+  Expect.equals(61, chainFirstLast((first: mul10, last: add1), 6));
+  Expect.equals(61, chainFirstLast([mul10, add1], 6));
+  Expect.equals(61, chainFirstLast({mul10, add1}, 6));
+}