[dart2js] Basic records

Records can be constructed, returned, and field accessed in non-dynamic code. Type checks don't work, so records cannot be added to generic collections like `List<(int,int>)`.

- Add runtime for base classes for records of various arities.

- Use impacts to collect record static types used.

- Create record classes for each record shape. Record classes have
  extra metadata properties on the prototype. This allows slower,
  general operations at the root of the hierarchy that can be
  overridden with specialized operations lower in the hierarchy.



Change-Id: Ic1b38a5076c2d05f2ecff0a9ed3255b43645386e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/278699
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
This commit is contained in:
Stephen Adams 2023-01-12 04:44:16 +00:00 committed by Commit Queue
parent 052a6b4227
commit ef6ce1e88a
27 changed files with 1056 additions and 11 deletions

View file

@ -629,6 +629,23 @@ abstract class CommonElements {
// impacts + constant emitter accordingly.
late final ClassEntity constSetLiteralClass = unmodifiableSetClass;
/// Base class for all records.
late final ClassEntity recordBaseClass = _findHelperClass('_Record');
/// A function that is used to model the back-end impacts of record lowering
/// in the front-end.
late final FunctionEntity recordImpactModel =
_findHelperFunction('_RecordImpactModel');
/// Base class for records with N fields. Can be a fixed-arity class or a
/// general class that works for any arity.
ClassEntity recordArityClass(int n) {
return _findClassOrNull(jsHelperLibrary, '_Record$n') ??
recordGeneralBaseClass;
}
late final ClassEntity recordGeneralBaseClass = _findHelperClass('_RecordN');
late final ClassEntity jsInvocationMirrorClass =
_findHelperClass('JSInvocationMirror');

View file

@ -125,6 +125,11 @@ class ImpactBuilder extends StaticTypeVisitor implements ImpactRegistry {
isConst: node.isConst, isEmpty: node.entries.isEmpty);
}
@override
void handleRecordLiteral(ir.RecordLiteral node) {
registerRecordLiteral(node.recordType, isConst: node.isConst);
}
@override
void handleStaticGet(
ir.Expression node, ir.Member target, ir.DartType resultType) {

View file

@ -1410,6 +1410,18 @@ abstract class StaticTypeVisitor extends StaticTypeBase {
return const ir.VoidType();
}
void handleRecordLiteral(ir.RecordLiteral node) {}
@override
ir.DartType visitRecordLiteral(ir.RecordLiteral node) {
visitNodes(node.positional);
for (final namedExpression in node.named) {
visitNode(namedExpression.value);
}
handleRecordLiteral(node);
return super.visitRecordLiteral(node);
}
void handleFunctionExpression(ir.FunctionExpression node) {}
@override

View file

@ -634,4 +634,10 @@ class BackendImpacts {
_commonElements.throwLateFieldADI,
],
);
late final BackendImpact recordInstantiation = BackendImpact(
globalUses: [
_commonElements.recordImpactModel,
],
);
}

View file

@ -1943,6 +1943,10 @@ abstract class ModularNamer {
return instanceFieldPropertyName(_commonElements.rtiAsField);
case JsGetName.RTI_FIELD_IS:
return instanceFieldPropertyName(_commonElements.rtiIsField);
case JsGetName.RECORD_SHAPE_TAG_PROPERTY:
return asName(fixedNames.recordShapeTag);
case JsGetName.RECORD_SHAPE_TYPE_PROPERTY:
return asName(fixedNames.recordShapeRecipe);
default:
throw failedAt(spannable ?? CURRENT_ELEMENT_SPANNABLE,
'Error: Namer has no name for "$name".');
@ -2147,6 +2151,9 @@ class FixedNames {
String get operatorSignature => r'$signature';
String get requiredParameterField => r'$requiredArgCount';
String get rtiName => r'$ti';
String get recordShapeRecipe => r'$recipe';
String get recordShapeTag => r'$shape';
}
/// Minified version of the fixed names usage by the namer.
@ -2171,6 +2178,11 @@ class MinifiedFixedNames extends FixedNames {
String get defaultValuesField => r'$D';
@override
String get operatorSignature => r'$S';
@override
String get recordShapeRecipe => r'$r';
@override
String get recordShapeTag => r'$s';
}
String? operatorNameToIdentifier(String? name) {

View file

@ -119,6 +119,7 @@ class CodeEmitterTask extends CompilerTask {
this,
closedWorld,
closedWorld.fieldAnalysis,
closedWorld.recordData,
inferredData,
_backendStrategy.sourceInformationStrategy,
closedWorld.sorter,

View file

@ -210,6 +210,9 @@ class ClassTypeData {
check.cls == commonElements.objectClass || check.cls == element);
}
// TODO(sra): There are a lot of fields here that apply in limited cases
// (e.g. isClosureBaseClass is true for one class). Can we refactor the special
// case information, for example, into a subclass, or an extension object?
class Class {
/// The element should only be used during the transition to the new model.
/// Uses indicate missing information in the model.
@ -255,6 +258,9 @@ class Class {
// metadata table, then this field contains the index into that field.
final js.Expression? functionTypeIndex;
final int? recordShapeTag;
final js.Expression? recordShapeRecipe;
/// Whether the class must be evaluated eagerly.
bool isEager = false;
@ -286,7 +292,9 @@ class Class {
required this.isNative,
required this.isClosureBaseClass,
this.sharedClosureApplyMetadata,
required this.isMixinApplicationWithMembers});
required this.isMixinApplicationWithMembers,
this.recordShapeRecipe,
this.recordShapeTag});
bool get isSimpleMixinApplication => false;

View file

@ -32,6 +32,7 @@ import '../../js_backend/runtime_types_resolution.dart' show RuntimeTypesNeed;
import '../../js_model/elements.dart'
show JField, JGeneratorBody, JSignatureMethod;
import '../../js_model/js_world.dart';
import '../../js_model/records.dart' show RecordData, RecordRepresentation;
import '../../js_model/type_recipe.dart'
show FullTypeEnvironmentStructure, TypeExpressionRecipe;
import '../../native/enqueue.dart' show NativeCodegenEnqueuer;
@ -77,6 +78,7 @@ class ProgramBuilder {
final CodeEmitterTask _task;
final JClosedWorld _closedWorld;
final JFieldAnalysis _fieldAnalysis;
final RecordData _recordData;
final InferredData _inferredData;
final SourceInformationStrategy _sourceInformationStrategy;
@ -123,6 +125,7 @@ class ProgramBuilder {
this._task,
this._closedWorld,
this._fieldAnalysis,
this._recordData,
this._inferredData,
this._sourceInformationStrategy,
this._sorter,
@ -571,6 +574,21 @@ class ProgramBuilder {
sharedClosureApplyMetadata = 2;
}
int? recordShapeTag;
js.Expression? recordShapeRecipe;
RecordRepresentation? record = _recordData.representationForClass(cls);
if (record != null && record.definesShape) {
recordShapeTag = record.shapeTag;
// A top-type for a record shape. We choose `dynamic` as the top-type for
// the fields since is encodes concisely.
// TODO(50081): Move this somewhere to improve consistency with Rti
// runtime.
final recordTop = _dartTypes.recordType(record.shape,
List.filled(record.shape.fieldCount, _dartTypes.dynamicType()));
recordShapeRecipe = _rtiRecipeEncoder.encodeGroundRecipe(
_task.emitter, TypeExpressionRecipe(recordTop));
}
List<Method> methods = [];
List<StubMethod> callStubs = [];
@ -719,6 +737,8 @@ class ProgramBuilder {
assert(methods.isEmpty);
assert(!isClosureBaseClass);
assert(sharedClosureApplyMetadata == null);
assert(recordShapeTag == null);
assert(recordShapeRecipe == null);
result = MixinApplication(cls, typeData, name, instanceFields, callStubs,
checkedSetters, gettersSetters, isChecks, typeTests.functionTypeIndex,
@ -746,7 +766,9 @@ class ProgramBuilder {
isNative: _nativeData.isNativeClass(cls),
isClosureBaseClass: isClosureBaseClass,
sharedClosureApplyMetadata: sharedClosureApplyMetadata,
isMixinApplicationWithMembers: isMixinApplicationWithMembers);
isMixinApplicationWithMembers: isMixinApplicationWithMembers,
recordShapeTag: recordShapeTag,
recordShapeRecipe: recordShapeRecipe);
}
_classes[cls] = result;
return result;

View file

@ -1107,6 +1107,16 @@ class FragmentEmitter {
js.string(_namer.fixedNames.defaultValuesField), js.LiteralNull()));
}
// `prototype` properties for record classes.
if (cls.recordShapeRecipe != null) {
properties.add(js.Property(js.string(_namer.fixedNames.recordShapeRecipe),
cls.recordShapeRecipe!));
}
if (cls.recordShapeTag != null) {
properties.add(js.Property(js.string(_namer.fixedNames.recordShapeTag),
js.number(cls.recordShapeTag!)));
}
return js.ObjectInitializer(properties);
}

View file

@ -508,6 +508,43 @@ class ClosureMemberDefinition implements MemberDefinition {
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 {
/// Tag used for identifying serialized [RecordMemberDefinition] objects in a
/// debugging data stream.
static const String tag = 'record-member-definition';
@override
final SourceSpan location;
@override
final MemberKind kind;
@override
ir.TreeNode get node => throw UnsupportedError('RecordMemberDefinition.node');
RecordMemberDefinition(this.location, this.kind);
factory RecordMemberDefinition.readFromDataSource(
DataSourceReader source, MemberKind kind) {
source.begin(tag);
SourceSpan location = source.readSourceSpan();
source.end(tag);
return RecordMemberDefinition(location, kind);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(kind);
sink.begin(tag);
sink.writeSourceSpan(location);
sink.end(tag);
}
@override
String toString() => 'RecordMemberDefinition(kind:$kind,location:$location)';
}
void forEachOrderedParameterByFunctionNode(
ir.FunctionNode node,
ParameterStructure parameterStructure,
@ -588,6 +625,7 @@ enum ClassKind {
// TODO(efortuna, johnniwinther): Context is not a class, but is
// masquerading as one currently for consistency with the old element model.
context,
record,
}
/// Definition information for a [ClassEntity].
@ -612,6 +650,8 @@ abstract class ClassDefinition {
return ClosureClassDefinition.readFromDataSource(source);
case ClassKind.context:
return ContextContainerDefinition.readFromDataSource(source);
case ClassKind.record:
return RecordClassDefinition.readFromDataSource(source);
}
}
@ -729,3 +769,39 @@ class ContextContainerDefinition implements ClassDefinition {
String toString() =>
'ContextContainerDefinition(kind:$kind,location:$location)';
}
class RecordClassDefinition implements ClassDefinition {
/// Tag used for identifying serialized [RecordClassDefinition] objects in a
/// debugging data stream.
static const String tag = 'record-class-definition';
@override
final SourceSpan location;
RecordClassDefinition(this.location);
factory RecordClassDefinition.readFromDataSource(DataSourceReader source) {
source.begin(tag);
SourceSpan location = source.readSourceSpan();
source.end(tag);
return RecordClassDefinition(location);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(ClassKind.record);
sink.begin(tag);
sink.writeSourceSpan(location);
sink.end(tag);
}
@override
ClassKind get kind => ClassKind.record;
@override
ir.Node get node =>
throw UnsupportedError('RecordClassDefinition.node for $location');
@override
String toString() => 'RecordClassDefinition(kind:$kind,location:$location)';
}

View file

@ -47,6 +47,7 @@ import '../ordered_typeset.dart';
import '../serialization/serialization.dart';
import '../universe/call_structure.dart';
import '../universe/member_usage.dart';
import '../universe/record_shape.dart';
import '../universe/selector.dart';
import 'closure.dart';
@ -54,6 +55,7 @@ import 'elements.dart';
import 'element_map.dart';
import 'env.dart';
import 'locals.dart';
import 'records.dart' show JRecordClass, RecordClassData;
class JsKernelToElementMap implements JsToElementMap, IrToElementMap {
/// Tag used for identifying serialized [JsKernelToElementMap] objects in a
@ -365,7 +367,7 @@ class JsKernelToElementMap implements JsToElementMap, IrToElementMap {
if (env.cls != null) {
classMap[env.cls!] = cls;
}
if (cls is! JContext && cls is! JClosureClass) {
if (cls is! JContext && cls is! JClosureClass && cls is! JRecordClass) {
// Synthesized classes are not part of the library environment.
libraries.getEnv(cls.library).registerClass(cls.name, env);
}
@ -2166,6 +2168,53 @@ class JsKernelToElementMap implements JsToElementMap, IrToElementMap {
}
return generatorBody;
}
String _nameForShape(RecordShape shape) {
final sb = StringBuffer();
sb.write('_Record_');
sb.write(shape.fieldCount);
for (String name in shape.fieldNames) {
sb.write('_');
// 'hex' escape to remove `$` and `_`.
// `send_$_bux` --> `sendx5Fx24x5Fbux78`.
sb.write(name
.replaceAll(r'x', r'x78')
.replaceAll(r'$', r'x24')
.replaceAll(r'_', r'x5F'));
}
return sb.toString();
}
IndexedClass generateRecordShapeClass(
RecordShape shape, InterfaceType supertype) {
JLibrary library = supertype.element.library as JLibrary;
String name = _nameForShape(shape);
SourceSpan location = SourceSpan.unknown(); // TODO(50081): What to use?
Map<Name, IndexedMember> memberMap = {};
IndexedClass 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>[]);
RecordClassData recordData = RecordClassData(
RecordClassDefinition(location),
thisType,
supertype,
getOrderedTypeSet(supertype.element as IndexedClass)
.extendClass(types, thisType));
classes.register(classEntity, recordData, RecordClassEnv(memberMap));
// TODO(49718): Implement `==` specialized to the shape.
return classEntity;
}
}
class JsElementEnvironment extends ElementEnvironment

View file

@ -12,6 +12,7 @@ import '../elements/types.dart';
import '../serialization/serialization.dart';
import '../universe/class_set.dart' show ClassHierarchyNodesMapKey;
import 'closure.dart';
import 'records.dart' show JRecordClass;
const String jsElementPrefix = 'j:';
@ -53,7 +54,7 @@ class JLibrary extends IndexedLibrary {
}
/// Enum used for identifying [JClass] subclasses in serialization.
enum JClassKind { node, closure, context }
enum JClassKind { node, closure, context, record }
class JClass extends IndexedClass with ClassHierarchyNodesMapKey {
/// Tag used for identifying serialized [JClass] objects in a
@ -85,6 +86,8 @@ class JClass extends IndexedClass with ClassHierarchyNodesMapKey {
return JClosureClass.readFromDataSource(source);
case JClassKind.context:
return JContext.readFromDataSource(source);
case JClassKind.record:
return JRecordClass.readFromDataSource(source);
}
}

View file

@ -32,6 +32,7 @@ import 'element_map.dart'
forEachOrderedParameterByFunctionNode;
import 'element_map_impl.dart';
import 'elements.dart';
import 'records.dart' show RecordClassData;
/// Environment for fast lookup of component libraries.
class JProgramEnv {
@ -179,7 +180,7 @@ class JLibraryData {
}
/// Enum used for identifying [JClassEnv] subclasses in serialization.
enum JClassEnvKind { node, closure, context }
enum JClassEnvKind { node, closure, context, record }
/// Member data for a class.
abstract class JClassEnv {
@ -193,6 +194,8 @@ abstract class JClassEnv {
return ClosureClassEnv.readFromDataSource(source);
case JClassEnvKind.context:
return ContextEnv.readFromDataSource(source);
case JClassEnvKind.record:
return RecordClassEnv.readFromDataSource(source);
}
}
@ -404,8 +407,71 @@ class ClosureClassEnv extends ContextEnv {
}
}
class RecordClassEnv implements JClassEnv {
/// Tag used for identifying serialized [ContextEnv] objects in a debugging
/// data stream.
static const String tag = 'record-env';
final Map<Name, IndexedMember> _memberMap;
RecordClassEnv(this._memberMap);
factory RecordClassEnv.readFromDataSource(DataSourceReader source) {
source.begin(tag);
Map<Name, IndexedMember> _memberMap =
source.readNameMap(() => source.readMember() as IndexedMember)!;
source.end(tag);
return RecordClassEnv(_memberMap);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(JClassEnvKind.record);
sink.begin(tag);
sink.writeNameMap(
_memberMap, (IndexedMember member) => sink.writeMember(member));
sink.end(tag);
}
@override
void forEachConstructorBody(void f(ConstructorBodyEntity constructor)) {
// We do not create constructor bodies for containers.
}
@override
void forEachConstructor(
IrToElementMap elementMap, void f(ConstructorEntity constructor)) {
// We do not create constructors for containers.
}
@override
ConstructorEntity? lookupConstructor(IrToElementMap elementMap, String name) {
// We do not create constructors for containers.
return null;
}
@override
void forEachMember(IrToElementMap elementMap, void f(MemberEntity member)) {
_memberMap.values.forEach(f);
}
@override
MemberEntity? lookupMember(IrToElementMap elementMap, Name name) {
return _memberMap[name];
}
@override
bool get isUnnamedMixinApplication => false;
@override
bool get isMixinApplicationWithMembers => false;
@override
ir.Class? get cls => null;
}
/// Enum used for identifying [JClassData] subclasses in serialization.
enum JClassDataKind { node, closure, context }
enum JClassDataKind { node, closure, context, record }
abstract class JClassData {
/// Deserializes a [JClassData] object from [source].
@ -418,6 +484,8 @@ abstract class JClassData {
return ClosureClassData.readFromDataSource(source);
case JClassDataKind.context:
return ContextClassData.readFromDataSource(source);
case JClassDataKind.record:
return RecordClassData.readFromDataSource(source);
}
}

View file

@ -73,6 +73,7 @@ import 'element_map_impl.dart';
import 'js_world.dart';
import 'js_world_builder.dart' show JClosedWorldBuilder;
import 'locals.dart';
import 'records.dart' show RecordDataBuilder;
/// JS Strategy pattern that defines the element model used in type inference
/// and code generation.
@ -170,9 +171,12 @@ class JsBackendStrategy {
closedWorld.annotationsData);
ClosureDataBuilder closureDataBuilder = ClosureDataBuilder(
_compiler.reporter, _elementMap, closedWorld.annotationsData);
RecordDataBuilder recordDataBuilder = RecordDataBuilder(
_compiler.reporter, _elementMap, closedWorld.annotationsData);
JClosedWorldBuilder closedWorldBuilder = JClosedWorldBuilder(
_elementMap,
closureDataBuilder,
recordDataBuilder,
_compiler.options,
_compiler.reporter,
_compiler.abstractValueStrategy);

View file

@ -31,6 +31,10 @@ abstract class JsToFrontendMap {
ConstantValue? toBackendConstant(ConstantValue? value,
{bool allowNull = false});
Set<DartType> toBackendTypeSet(Iterable<DartType> set) {
return {for (final type in set.map(toBackendType)) type!};
}
Set<LibraryEntity> toBackendLibrarySet(Iterable<LibraryEntity> set) {
return set.map(toBackendLibrary).toSet();
}

View file

@ -40,6 +40,7 @@ import '../world.dart';
import 'element_map.dart';
import 'element_map_impl.dart';
import 'locals.dart';
import 'records.dart' show RecordData;
class JClosedWorld implements World {
static const String tag = 'closed-world';
@ -109,6 +110,7 @@ class JClosedWorld implements World {
final JFieldAnalysis fieldAnalysis;
final AnnotationsData annotationsData;
final ClosureData closureDataLookup;
final RecordData recordData;
final OutputUnitData outputUnitData;
/// The [Sorter] used for sorting elements in the generated code.
@ -137,6 +139,7 @@ class JClosedWorld implements World {
AbstractValueStrategy abstractValueStrategy,
this.annotationsData,
this.closureDataLookup,
this.recordData,
this.outputUnitData,
this.memberAccess) {
_abstractValueDomain = abstractValueStrategy.createDomain(this);
@ -188,6 +191,7 @@ class JClosedWorld implements World {
ClosureData closureData =
ClosureData.readFromDataSource(elementMap, source);
RecordData recordData = RecordData.readFromDataSource(elementMap, source);
OutputUnitData outputUnitData = OutputUnitData.readFromDataSource(source);
elementMap.lateOutputUnitDataBuilder =
@ -219,6 +223,7 @@ class JClosedWorld implements World {
abstractValueStrategy,
annotationsData,
closureData,
recordData,
outputUnitData,
memberAccess);
}
@ -247,6 +252,7 @@ class JClosedWorld implements World {
(Set<ClassEntity> set) => sink.writeClasses(set));
annotationsData.writeToDataSink(sink);
closureDataLookup.writeToDataSink(sink);
recordData.writeToDataSink(sink);
outputUnitData.writeToDataSink(sink);
sink.writeMemberMap(
memberAccess,

View file

@ -29,12 +29,14 @@ import '../universe/class_hierarchy.dart';
import '../universe/class_set.dart';
import '../universe/feature.dart';
import '../universe/member_usage.dart';
import '../universe/record_shape.dart';
import '../universe/selector.dart';
import 'closure.dart';
import 'elements.dart';
import 'element_map_impl.dart';
import 'js_to_frontend_map.dart';
import 'js_world.dart';
import 'records.dart';
class JClosedWorldBuilder {
final JsKernelToElementMap _elementMap;
@ -42,12 +44,18 @@ class JClosedWorldBuilder {
ClassHierarchyNodesMap();
final Map<ClassEntity, ClassSet> _classSets = <ClassEntity, ClassSet>{};
final ClosureDataBuilder _closureDataBuilder;
final RecordDataBuilder _recordDataBuilder;
final CompilerOptions _options;
final DiagnosticReporter _reporter;
final AbstractValueStrategy _abstractValueStrategy;
JClosedWorldBuilder(this._elementMap, this._closureDataBuilder, this._options,
this._reporter, this._abstractValueStrategy);
JClosedWorldBuilder(
this._elementMap,
this._closureDataBuilder,
this._recordDataBuilder,
this._options,
this._reporter,
this._abstractValueStrategy);
ElementEnvironment get _elementEnvironment => _elementMap.elementEnvironment;
CommonElements get _commonElements => _elementMap.commonElements;
@ -136,6 +144,11 @@ class JClosedWorldBuilder {
List<FunctionEntity> callMethods = <FunctionEntity>[];
ClosureData closureData;
RecordData recordData;
final recordTypes = Set<RecordType>.from(
map.toBackendTypeSet(closedWorld.instantiatedRecordTypes));
if (_options.disableRtiOptimization) {
rtiNeed = TrivialRuntimeTypesNeed(_elementMap.elementEnvironment);
closureData = _closureDataBuilder.createClosureEntities(
@ -143,6 +156,7 @@ class JClosedWorldBuilder {
map.toBackendMemberMap(closureModels, identity),
const TrivialClosureRtiNeed(),
callMethods);
recordData = _recordDataBuilder.createRecordData(this, recordTypes);
} else {
RuntimeTypesNeedImpl kernelRtiNeed =
closedWorld.rtiNeed as RuntimeTypesNeedImpl;
@ -170,6 +184,8 @@ class JClosedWorldBuilder {
localFunctionsNodesNeedingSignature),
callMethods);
recordData = _recordDataBuilder.createRecordData(this, recordTypes);
List<FunctionEntity> callMethodsNeedingSignature = <FunctionEntity>[];
for (ir.LocalFunction node in localFunctionsNodesNeedingSignature) {
callMethodsNeedingSignature
@ -188,6 +204,7 @@ class JClosedWorldBuilder {
}
(map as JsToFrontendMapImpl)._registerClosureData(closureData);
//(map as JsToFrontendMapImpl)._registerRecordData(recordData);
BackendUsage backendUsage =
_convertBackendUsage(map, closedWorld.backendUsage as BackendUsageImpl);
@ -239,6 +256,7 @@ class JClosedWorldBuilder {
_abstractValueStrategy,
annotationsData,
closureData,
recordData,
outputUnitData,
memberAccess);
}
@ -365,6 +383,27 @@ class JClosedWorldBuilder {
return _commonElements.closureClass;
}
/// Called once per [shape]. The class can be used for a record with the
/// specified shape, or subclassed to provide specialized methods.
ClassEntity buildRecordShapeClass(RecordShape shape) {
ClassEntity superclass = _commonElements.recordArityClass(shape.fieldCount);
IndexedClass recordClass = _elementMap.generateRecordShapeClass(
shape,
_dartTypes.interfaceType(superclass, const []),
);
// Tell the hierarchy about the superclass so we can use
// .getSupertypes(class)
ClassHierarchyNode parentNode = _classHierarchyNodes[superclass]!;
ClassHierarchyNode node = ClassHierarchyNode(
parentNode, recordClass, parentNode.hierarchyDepth + 1);
_classHierarchyNodes[recordClass] = node;
_classSets[recordClass] = ClassSet(node);
node.isDirectlyInstantiated = true;
return recordClass;
}
OutputUnitData _convertOutputUnitData(JsToFrontendMapImpl map,
OutputUnitData data, ClosureData closureDataLookup) {
// Convert front-end maps containing K-class and K-local function keys to a

View file

@ -0,0 +1,336 @@
// 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.
/// Records are implemented as classes introduced in the K- to J- lowering.
///
/// The record classes are arranged in a hierarchy, with single base class. The
/// base class has subclasses for each 'basic' shape, the number of fields. This
/// is called the 'arity' class. The arity class has subclasses for each actual
/// record shape. There can be further subclasses of the shape class to allow
/// specialization on the basis of the value stored in the field.
///
/// Example
///
/// _Record - base class
///
/// _Record2 - class for Record arity (number of fields)
///
/// _Record_2_end_start - class for shape `(start:, end:)`
///
/// _Record_2_end_start__int_int - class for specialization within
/// shape when the field are known to in `int`s. This allows more
/// efficient `==` and `.hashCode` operations.
///
/// RecordDataBuilder creates the new classes. The arity classes exist as Dart
/// code in `js_runtime/lib/records.dart`. RecordDataBuilder creates shape
/// classes and specialization classes.
///
/// (Specialization classes have not yet been implemented).
library dart2js.js_model.records;
import '../common.dart';
import '../elements/entities.dart';
import '../elements/names.dart';
import '../elements/types.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 'elements.dart';
import 'env.dart';
import 'js_world_builder.dart' show JClosedWorldBuilder;
class RecordData {
/// Tag used for identifying serialized [RecordData] objects in a
/// debugging data stream.
static const String tag = 'record-data';
final JsToElementMap _elementMap;
final List<RecordRepresentation> _representations;
final Map<ClassEntity, RecordRepresentation> _classToRepresentation = {};
final Map<RecordShape, RecordRepresentation> _shapeToRepresentation = {};
RecordData._(this._elementMap, this._representations) {
// Unpack representations into lookup maps.
for (final info in _representations) {
_classToRepresentation[info.cls] = info;
if (info.definesShape) _shapeToRepresentation[info.shape] = info;
}
}
factory RecordData.readFromDataSource(
JsToElementMap elementMap, DataSourceReader source) {
source.begin(tag);
List<RecordRepresentation> representations =
source.readList(() => RecordRepresentation.readFromDataSource(source));
source.end(tag);
return RecordData._(elementMap, representations);
}
/// Serializes this [RecordData] to [sink].
void writeToDataSink(DataSinkWriter sink) {
sink.begin(tag);
sink.writeList<RecordRepresentation>(
_representations, (info) => info.writeToDataSink(sink));
sink.end(tag);
}
RecordRepresentation representationForShape(RecordShape shape) {
return _shapeToRepresentation[shape] ??
(throw StateError('representationForShape $shape'));
}
RecordRepresentation representationForStaticType(RecordType type) {
// TODO(49718): Implement specialization when fields have types that allow
// better code for `==` and `.hashCode`.
return representationForShape(type.shape);
}
/// Returns `null` if [cls] is not a record representation.
RecordRepresentation? representationForClass(ClassEntity cls) {
return _classToRepresentation[cls];
}
/// Returns field and possibly index for accessing into a shape.
RecordAccessPath pathForAccess(RecordShape shape, int indexInShape) {
// TODO(sra): Cache lookup.
final representation = representationForShape(shape);
final cls = representation.cls;
if (representation.usesList) {
final field = _elementMap.elementEnvironment
.lookupClassMember(cls, Name('_values', cls.library.canonicalUri));
return RecordAccessPath(field as FieldEntity, indexInShape);
} else {
final field = _elementMap.elementEnvironment.lookupClassMember(
cls, Name('_$indexInShape', cls.library.canonicalUri));
return RecordAccessPath(field as FieldEntity, null);
}
}
}
/// How to access a field of a record. Currently there are two forms, a single
/// field acccess (e.g. `r._2`), used for small records, or a field access
/// followed by an indexing, used for records that hold the values in a JSArray
/// (e.g. `r._values[2]`).
class RecordAccessPath {
final FieldEntity field;
final int? index; // `null` for single field access.
RecordAccessPath(this.field, this.index);
}
class RecordRepresentation {
static const String tag = 'record-class-info';
/// There is one [RecordRepresentation] per class.
final ClassEntity cls;
/// The record shape of [cls]. There can be many classes defining records of
/// the same shape, for example, when there are specializations of a record
/// shape.
final RecordShape shape;
/// [definesShape] is `true` if this record class is a shape class. There may
/// be subclasses of this class which share the same shape. In this case
/// [definesShape] is `false`, as the subclasses can inherit shape metadata.
///
/// A shape class defines some metadata properties on the prototype:
///
/// (1) The shapeTag, a small integer (see below).
/// (2) The the top-type recipe for the shape. The recipe is a function of the
/// [shape]. e.g. `"+end,start(@,@)"`.
final bool definesShape;
/// `true` if this class is based on the general record class that uses a
/// `List` to store the fields.
final bool usesList;
/// [shapeTag] is a small integer that is a function of the shape. The
/// shapeTag can be used as an index into runtime computed derived data.
final int shapeTag;
/// This is non-null for a specialization subclass of a shape class.
// TODO(50081): This is a placeholder for the specialization key. We might do
// something like interceptors, where 'i' means 'int', 's' means 'string', so
// they key 's_i_is' would be a specialization for a record where the fields
// are {int}, {string} and {int,string}. Operator `==` could be specialized to
// use `===` for each field, but `.hashCode` would need a dispatch for the
// last field. Or we could do something completely different like have a
// `List` of inferred types.
final String? _specializationKey;
RecordRepresentation._(this.cls, this.shape, this.definesShape, this.usesList,
this.shapeTag, this._specializationKey);
factory RecordRepresentation.readFromDataSource(DataSourceReader source) {
source.begin(tag);
final cls = source.readClass();
final shape = RecordShape.readFromDataSource(source);
final definesShape = source.readBool();
final usesList = source.readBool();
final shapeTag = source.readInt();
final specializationKey = source.readStringOrNull();
source.end(tag);
return RecordRepresentation._(
cls, shape, definesShape, usesList, shapeTag, specializationKey);
}
/// Serializes this [RecordData] to [sink].
void writeToDataSink(DataSinkWriter sink) {
sink.begin(tag);
sink.writeClass(cls);
shape.writeToDataSink(sink);
sink.writeBool(definesShape);
sink.writeBool(usesList);
sink.writeInt(shapeTag);
sink.writeStringOrNull(_specializationKey);
sink.end(tag);
}
}
/// Conversion of records to classes.
class RecordDataBuilder {
final DiagnosticReporter _reporter;
final JsToElementMap _elementMap;
final AnnotationsData _annotationsData;
RecordDataBuilder(this._reporter, this._elementMap, this._annotationsData);
RecordData createRecordData(JClosedWorldBuilder closedWorldBuilder,
Iterable<RecordType> recordTypes) {
_reporter;
_annotationsData;
// Sorted shapes lead to a more consistent class ordering in the generated
// code.
final shapes = recordTypes.map((type) => type.shape).toSet().toList()
..sort(RecordShape.compare);
List<RecordRepresentation> representations = [];
for (int i = 0; i < shapes.length; i++) {
final shape = shapes[i];
final cls = closedWorldBuilder.buildRecordShapeClass(shape);
int shapeTag = i;
bool usesList = _computeUsesGeneralClass(cls);
final info =
RecordRepresentation._(cls, shape, true, usesList, shapeTag, null);
representations.add(info);
}
return RecordData._(
_elementMap,
representations,
);
}
bool _computeUsesGeneralClass(ClassEntity? cls) {
while (cls != null) {
if (cls == _elementMap.commonElements.recordGeneralBaseClass) return true;
cls = _elementMap.elementEnvironment.getSuperClass(cls);
}
return false;
}
}
// TODO(sra): Use a regular JClass with a different Definition?
class JRecordClass extends JClass {
/// Tag used for identifying serialized [JRecordClass] objects in a
/// debugging data stream.
static const String tag = 'record-class';
JRecordClass(super.library, super.name, {required super.isAbstract});
factory JRecordClass.readFromDataSource(DataSourceReader source) {
source.begin(tag);
JLibrary library = source.readLibrary() as JLibrary;
String name = source.readString();
bool isAbstract = source.readBool();
source.end(tag);
return JRecordClass(library, name, isAbstract: isAbstract);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(JClassKind.record);
sink.begin(tag);
sink.writeLibrary(library);
sink.writeString(name);
sink.writeBool(isAbstract);
sink.end(tag);
}
@override
String toString() => '${jsElementPrefix}record_class($name)';
}
class RecordClassData implements JClassData {
/// Tag used for identifying serialized [RecordClassData] objects in a
/// debugging data stream.
static const String tag = 'record-class-data';
@override
final ClassDefinition definition;
@override
final InterfaceType? thisType;
@override
final OrderedTypeSet orderedTypeSet;
@override
final InterfaceType? supertype;
RecordClassData(
this.definition, this.thisType, this.supertype, this.orderedTypeSet);
factory RecordClassData.readFromDataSource(DataSourceReader source) {
source.begin(tag);
ClassDefinition definition = ClassDefinition.readFromDataSource(source);
InterfaceType thisType = source.readDartType() as InterfaceType;
InterfaceType supertype = source.readDartType() as InterfaceType;
OrderedTypeSet orderedTypeSet = OrderedTypeSet.readFromDataSource(source);
source.end(tag);
return RecordClassData(definition, thisType, supertype, orderedTypeSet);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(JClassDataKind.record);
sink.begin(tag);
definition.writeToDataSink(sink);
sink.writeDartType(thisType!);
sink.writeDartType(supertype!);
orderedTypeSet.writeToDataSink(sink);
sink.end(tag);
}
@override
bool get isMixinApplication => false;
@override
bool get isEnumClass => false;
@override
FunctionType? get callType => null;
@override
List<InterfaceType> get interfaces => const <InterfaceType>[];
@override
InterfaceType? get mixedInType => null;
@override
InterfaceType? get jsInteropType => thisType;
@override
InterfaceType? get rawType => thisType;
@override
InterfaceType? get instantiationToBounds => thisType;
@override
List<Variance> getVariances() => [];
}

View file

@ -119,7 +119,8 @@ class Dart2jsTarget extends Target {
(uri.path == 'core' ||
uri.path == 'typed_data' ||
uri.path == '_interceptors' ||
uri.path == '_native_typed_data');
uri.path == '_native_typed_data' ||
uri.path == '_js_helper');
@override
bool allowPlatformPrivateLibraryAccess(Uri importer, Uri imported) =>

View file

@ -307,6 +307,7 @@ class KernelImpactConverter implements ImpactRegistry {
@override
void registerRecordLiteral(ir.RecordType recordType,
{required bool isConst}) {
registerBackendImpact(_impacts.recordInstantiation);
final type = elementMap.getDartType(recordType) as RecordType;
impactBuilder.registerTypeUse(TypeUse.recordInstantiation(type));
}

View file

@ -240,7 +240,8 @@ class ObjectsInMemorySerializationStrategy
DataSourceIndices indices,
List<Object> globalTypeInferenceResultsData) {
DataSourceReader globalTypeInferenceResultsSource = DataSourceReader(
ObjectDataSource(globalTypeInferenceResultsData), options);
ObjectDataSource(globalTypeInferenceResultsData), options,
useDataKinds: useDataKinds);
return DataAndIndices(
deserializeGlobalTypeInferenceResultsFromSource(
options,

View file

@ -44,6 +44,7 @@ import '../js_model/js_strategy.dart';
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 '../kernel/invocation_mirror_constants.dart';
import '../native/behavior.dart';
import '../native/js.dart';
@ -51,6 +52,7 @@ import '../options.dart';
import '../tracer.dart';
import '../universe/call_structure.dart';
import '../universe/feature.dart';
import '../universe/record_shape.dart';
import '../universe/selector.dart';
import '../universe/target_checks.dart' show TargetChecks;
import '../universe/use.dart' show ConstantUse, StaticUse, TypeUse;
@ -122,6 +124,7 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
final JClosedWorld closedWorld;
final CodegenRegistry registry;
final ClosureData _closureDataLookup;
final RecordData _recordData;
final Tracer _tracer;
/// A stack of [InterfaceType]s that have been seen during inlining of
@ -180,6 +183,7 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
this._inlineDataCache)
: this.targetElement = _effectiveTargetElementFor(_initialTargetElement),
this._closureDataLookup = closedWorld.closureDataLookup,
this._recordData = closedWorld.recordData,
_memberContextNode =
_elementMap.getMemberContextNode(_initialTargetElement) {
_enterFrame(targetElement, null);
@ -1733,7 +1737,7 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
@override
void defaultNode(ir.Node node) {
throw UnsupportedError("Unhandled node $node (${node.runtimeType})");
throw UnsupportedError('Unhandled node $node (${node.runtimeType})');
}
/// Returns the current source element. This is used by the type builder.
@ -3385,6 +3389,99 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
'ir.MapEntry should be handled in visitMapLiteral');
}
@override
void visitRecordLiteral(ir.RecordLiteral node) {
SourceInformation? sourceInformation =
_sourceInformationBuilder.buildCreate(node);
assert(!node.isConst);
List<HInstruction> inputs = [];
for (ir.Expression expression in node.positional) {
expression.accept(this);
inputs.add(pop());
}
for (ir.NamedExpression namedExpression in node.named) {
namedExpression.value.accept(this);
inputs.add(pop());
}
// TODO(50701): Choose class depending in inferred type of record fields
// which might be better than the static type.
RecordType dartType =
_elementMap.getDartType(node.recordType) as RecordType;
if (dartType.containsTypeVariables) {
dartType = localsHandler.substInContext(dartType) as RecordType;
}
final recordRepresentation =
_recordData.representationForStaticType(dartType);
ClassEntity recordClass = recordRepresentation.cls;
if (recordRepresentation.usesList) {
// TODO(50081): Can we use `.constListType`?
push(HLiteralList(inputs, _abstractValueDomain.fixedListType));
inputs = [pop()];
}
AbstractValue type = _abstractValueDomain.createNonNullExact(recordClass);
final allocation = HCreate(recordClass, inputs, type, sourceInformation);
// TODO(50701): With traced record types there might be a better type.
// AbstractValue type =
// _typeInferenceMap.typeOfRecordLiteral(node, _abstractValueDomain);
// if (_abstractValueDomain.containsAll(type).isDefinitelyFalse) {
// allocation.instructionType = type;
// }
push(allocation);
}
@override
void visitRecordIndexGet(ir.RecordIndexGet node) {
final shape = recordShapeOfRecordType(node.receiverType);
return _handleRecordFieldGet(node, node.receiver, shape, node.index);
}
@override
void visitRecordNameGet(ir.RecordNameGet node) {
final shape = recordShapeOfRecordType(node.receiverType);
int index = shape.indexOfName(node.name);
return _handleRecordFieldGet(node, node.receiver, shape, index);
}
void _handleRecordFieldGet(ir.Expression node, ir.TreeNode receiverNode,
RecordShape shape, int indexInShape) {
receiverNode.accept(this);
HInstruction receiver = pop();
SourceInformation? sourceInformation =
_sourceInformationBuilder.buildGet(node);
// TODO(50701): Type inference should improve on the static type.
// AbstractValue type =
// _typeInferenceMap.typeOfRecordGet(node, _abstractValueDomain);
StaticType staticType = _getStaticType(node);
AbstractValue type = _abstractValueDomain
.createFromStaticType(staticType.type,
classRelation: staticType.relation, nullable: true)
.abstractValue;
final path = _recordData.pathForAccess(shape, indexInShape);
if (path.index == null) {
HFieldGet fieldGet = HFieldGet(
path.field, receiver, type, 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(indexInShape, closedWorld), type));
}
}
@override
void visitTypeLiteral(ir.TypeLiteral node) {
final sourceInformation = _sourceInformationBuilder.buildGet(node);

View file

@ -33,6 +33,7 @@ import '../js_backend/type_reference.dart' show TypeReference;
import '../js_emitter/js_emitter.dart' show ModularEmitter;
import '../js_model/elements.dart' show JGeneratorBody;
import '../js_model/js_world.dart' show JClosedWorld;
import '../js_model/records.dart' show JRecordClass;
import '../js_model/type_recipe.dart';
import '../native/behavior.dart';
import '../options.dart';
@ -2414,6 +2415,11 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
// ignore:deprecated_member_use_from_same_package
.registerInstantiatedClass(node.element);
}
if (node.element is JRecordClass) {
_registry
// ignore:deprecated_member_use_from_same_package
.registerInstantiatedClass(node.element);
}
node.instantiatedTypes?.forEach(_registry.registerInstantiation);
final callMethod = node.callMethod;
if (callMethod != null) {

View file

@ -65,6 +65,12 @@ enum JsGetName {
/// Property name for Rti._is field.
RTI_FIELD_IS,
/// Property name for shape tag property in record class prototype.
RECORD_SHAPE_TAG_PROPERTY,
/// Property name for shape recipe property in record class prototype.
RECORD_SHAPE_TYPE_PROPERTY,
}
enum JsBuiltin {

View file

@ -77,6 +77,7 @@ part 'native_helper.dart';
part 'regexp_helper.dart';
part 'string_helper.dart';
part 'linked_hash_map.dart';
part 'records.dart';
/// Marks the internal map in dart2js, so that internal libraries can is-check
/// them.

View file

@ -0,0 +1,248 @@
// 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.
part of _js_helper;
/// Base class for all records.
abstract class _Record implements Record {
const _Record();
int get _shapeTag => JS('JSUInt31', '#[#]', this,
JS_GET_NAME(JsGetName.RECORD_SHAPE_TAG_PROPERTY));
bool _sameShape(_Record other) => _shapeTag == other._shapeTag;
/// Field values in canonical order.
// TODO(50081): Replace with a Map view.
List<Object?> _getFieldValues();
@override
String toString() {
final keys = _fieldKeys();
final values = _getFieldValues();
assert(keys.length == values.length);
final sb = StringBuffer();
String separator = '';
sb.write('(');
for (int i = 0; i < keys.length; i++) {
sb.write(separator);
Object key = keys[i];
if (key is String) {
sb.write(key);
sb.write(': ');
}
sb.write(values[i]);
separator = ', ';
}
sb.write(')');
return sb.toString();
}
/// Returns a list of integers and strings corresponding to the indexed and
/// named fields of this record.
List<Object> _fieldKeys() {
int shapeTag = _shapeTag;
while (_computedFieldKeys.length <= shapeTag) _computedFieldKeys.add(null);
return _computedFieldKeys[shapeTag] ??= _computeFieldKeys();
}
List<Object> _computeFieldKeys() {
String recipe =
JS('', '#[#]', this, JS_GET_NAME(JsGetName.RECORD_SHAPE_TYPE_PROPERTY));
// TODO(50081): The Rti recipe format is agnostic to what the record shape
// key is. We happen to use a comma-separated list of the names for the
// named arguments. `"+a,b(@,@,@,@)"` is the 4-record with two named fields
// `a` and `b`. We should refactor the code so that rti.dart returns the
// arity and the Rti's shape key which are interpreted here.
String joinedNames =
JS('', '#.substring(1, #.indexOf(#))', recipe, recipe, '(');
String atSigns = JS('', '#.replace(/[^@]/g, "")', recipe);
int arity = atSigns.length;
List<Object> result = List.generate(arity, (i) => i);
if (joinedNames != '') {
List<String> names = joinedNames.split(',');
int last = arity;
int i = names.length;
while (i > 0) result[--last] = names[--i];
}
return List.unmodifiable(result);
}
static final List<List<Object>?> _computedFieldKeys = [];
}
/// The empty record.
class _EmptyRecord extends _Record {
const _EmptyRecord();
@override
List<Object?> _getFieldValues() => const [];
@override
String toString() => '()';
@override
bool operator ==(Object other) => identical(other, const _EmptyRecord());
@override
int get hashCode => 43 * 67;
}
/// Base class for all records with two fields.
// TODO(49718): Generate this class.
class _Record2 extends _Record {
final Object? _0;
final Object? _1;
_Record2(this._0, this._1);
@override
List<Object?> _getFieldValues() => [_0, _1];
bool _equalFields(_Record2 other) {
return _0 == other._0 && _1 == other._1;
}
@override
// TODO(49718): Add specializations in shape class that combines is-check with
// shape check.
//
// TODO(49718): Add specializations in type specialization that that combines
// is-check with shape check and inlines and specializes `_equalFields`.
bool operator ==(Object other) {
return other is _Record2 && _sameShape(other) && _equalFields(other);
}
@override
int get hashCode => Object.hash(_shapeTag, _0, _1);
}
class _Record1 extends _Record {
final Object? _0;
_Record1(this._0);
@override
List<Object?> _getFieldValues() => [_0];
bool _equalFields(_Record1 other) {
return _0 == other._0;
}
@override
// TODO(49718): Same as _Record2.
bool operator ==(Object other) {
return other is _Record1 && _sameShape(other) && _equalFields(other);
}
@override
int get hashCode => Object.hash(_shapeTag, _0);
}
class _Record3 extends _Record {
final Object? _0;
final Object? _1;
final Object? _2;
_Record3(this._0, this._1, this._2);
@override
List<Object?> _getFieldValues() => [_0, _1, _2];
bool _equalFields(_Record3 other) {
return _0 == other._0 && _1 == other._1 && _2 == other._2;
}
@override
// TODO(49718): Same as _Record2.
bool operator ==(Object other) {
return other is _Record3 && _sameShape(other) && _equalFields(other);
}
@override
// TODO(49718): Incorporate shape in `hashCode`.
int get hashCode => Object.hash(_shapeTag, _0, _1, _2);
}
class _RecordN extends _Record {
final JSArray _values;
_RecordN(this._values);
@override
List<Object?> _getFieldValues() => _values;
bool _equalFields(_RecordN other) => _equalValues(_values, other._values);
static bool _equalValues(JSArray a, JSArray b) {
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
@override
bool operator ==(Object other) {
return other is _RecordN && _sameShape(other) && _equalFields(other);
}
@override
int get hashCode => Object.hash(_shapeTag, Object.hashAll(_values));
}
/// This function models the use of `_Record` and its subclasses. In the
/// resolution phase this function is assumed to be called in order to add
/// impacts for all the uses in code injected in lowering from K-world to
/// J-world.
void _RecordImpactModel() {
// Record classes are instantiated.
Object? anything() => _inscrutable(0);
final r0 = const _EmptyRecord();
final r1 = _Record1(anything());
final r2 = _Record2(anything(), anything());
final r3 = _Record3(anything(), anything(), anything());
final rN = _RecordN(anything() as JSArray);
// Assume the `==` methods are called.
r0 == anything();
r1 == anything();
r2 == anything();
r3 == anything();
rN == anything();
}
// TODO(50081): Can this be `external`?
@pragma('dart2js:assumeDynamic')
Object? _inscrutable(Object? x) => x;
// /// Class for all records with two unnamed fields.
// // TODO(49718): Generate this class.
// class _Record2$ extends _Record2 {
// _Record2$(super._0, super._1);
//
// bool operator ==(Object other) => other is _Record2$ && _equalFields(other);
//
// // Dynamic getters. Static field access does not use these getters.
// Object? get $0 => _0;
// Object? get $1 => _1;
// }
//
// /// Class for all records with two unnamed fields containing `int`s.
// // TODO(49718): Generate this class.
// class _Record2$_int_int extends _Record2 {
// _Record2$_int_int(super._0, super._1);
//
// @pragma('dart2js:as:trust')
// bool _equalFields(_Record2 other) =>
// _0 as int == other._0 && _1 as int == other._1;
//
// // Dynamic getters. Static field access does not use these getters.
// @pragma('dart2js:as:trust')
// int get $0 => _0 as int;
// @pragma('dart2js:as:trust')
// int get $1 => _1 as int;
// }

View file

@ -65,6 +65,12 @@ enum JsGetName {
/// Property name for Rti._is field.
RTI_FIELD_IS,
/// Property name for shape tag property in record class prototype.
RECORD_SHAPE_TAG_PROPERTY,
/// Property name for shape recipe property in record class prototype.
RECORD_SHAPE_TYPE_PROPERTY,
}
enum JsBuiltin {