[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)); +}