diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart index f39e8709f7f..f99a6faced0 100644 --- a/pkg/dart2wasm/lib/class_info.dart +++ b/pkg/dart2wasm/lib/class_info.dart @@ -118,6 +118,9 @@ class ClassInfo { /// follow the Dart class hierarchy. final ClassInfo? superInfo; + /// The class that this class masquerades as via `runtimeType`, if any. + ClassInfo? masquerade = null; + /// For every type parameter which is directly mapped to a type parameter in /// the superclass, this contains the corresponding superclass type /// parameter. These will reuse the corresponding type parameter field of @@ -186,6 +189,36 @@ class ClassInfoCollector { /// shape class with that many fields. final Map _recordStructs = {}; + /// Masquerades for implementation classes. For each entry of the map, all + /// subtypes of the key masquerade as the value. + late final Map _masquerades = { + translator.coreTypes.boolClass: translator.coreTypes.boolClass, + translator.coreTypes.intClass: translator.coreTypes.intClass, + translator.coreTypes.doubleClass: translator.coreTypes.doubleClass, + translator.coreTypes.stringClass: translator.coreTypes.stringClass, + translator.index.getClass("dart:core", "_Type"): + translator.coreTypes.typeClass, + translator.index.getClass("dart:core", "_ListBase"): + translator.coreTypes.listClass, + for (Class cls in const [ + "Int8List", + "Uint8List", + "Uint8ClampedList", + "Int16List", + "Uint16List", + "Int32List", + "Uint32List", + "Int64List", + "Uint64List", + "Float32List", + "Float64List", + "Int32x4List", + "Float32x4List", + "Float64x2List", + ].map((name) => translator.index.getClass("dart:typed_data", name))) + cls: cls + }; + /// Wasm field type for fields with type [_Type]. Fields of this type are /// added to classes for type parameters. /// @@ -292,6 +325,26 @@ class ClassInfoCollector { translator.classes.add(info); translator.classInfo[cls] = info; translator.classForHeapType.putIfAbsent(info.struct, () => info!); + + ClassInfo? computeMasquerade() { + if (info!.superInfo?.masquerade != null) { + return info.superInfo!.masquerade; + } + for (Supertype implemented in cls.implementedTypes) { + ClassInfo? implementedMasquerade = + translator.classInfo[implemented.classNode]!.masquerade; + if (implementedMasquerade != null) { + return implementedMasquerade; + } + } + Class? selfMasquerade = _masquerades[cls]; + if (selfMasquerade != null) { + return translator.classInfo[selfMasquerade]!; + } + return null; + } + + info.masquerade = computeMasquerade(); } void _initializeRecordClass(Class cls) { @@ -399,6 +452,11 @@ class ClassInfoCollector { // parameters. _initialize(translator.typeClass); + // Initialize masquerade classes to make sure they have low class IDs. + for (Class cls in _masquerades.values) { + _initialize(cls); + } + // Initialize the record base class if we have record classes. if (translator.recordClasses.isNotEmpty) { _initialize(translator.coreTypes.recordClass); diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index 29afd8cc029..7449dfb0fb9 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -2156,7 +2156,6 @@ class CodeGenerator extends ExpressionVisitor1 b.i64_const(2011); break; case "runtimeType": - case "_runtimeType": wrap(ConstantExpression(TypeLiteralConstant(NullType())), resultType); break; default: diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index 929bab8ef85..044d29cb2de 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -694,25 +694,6 @@ class Intrinsifier { return translator.types.makeTypeRulesSubstitutions(b); case "_getTypeNames": return translator.types.makeTypeNames(b); - case "_getFunctionTypeRuntimeType": - Expression object = node.arguments.positional[0]; - w.StructType closureBase = - translator.closureLayouter.closureBaseStruct; - codeGen.wrap(object, w.RefType.def(closureBase, nullable: false)); - b.struct_get(closureBase, FieldIndex.closureRuntimeType); - return translator.types.typeClassInfo.nonNullableType; - case "_getInterfaceTypeRuntimeType": - Expression object = node.arguments.positional[0]; - Expression typeArguments = node.arguments.positional[1]; - ClassInfo info = translator.classInfo[translator.interfaceTypeClass]!; - b.i32_const(info.classId); - b.i32_const(initialIdentityHash); - // Runtime types are never nullable. - b.i32_const(0); - getID(object); - codeGen.wrap(typeArguments, translator.types.typeListExpectedType); - b.struct_new(info.struct); - return info.nonNullableType; } } @@ -1148,16 +1129,89 @@ class Intrinsifier { // Object.runtimeType if (member.enclosingClass == translator.coreTypes.objectClass && name == "runtimeType") { - // Simple redirect to `_runtimeType`. This is done to keep - // `Object.runtimeType` external, which seems to be necessary for the TFA. - // If we don't do this, then the TFA assumes things like - // `null.runtimeType` are impossible and inserts a throw. + // Simple redirect to `_getMasqueradedRuntimeType`. This is done to keep + // `Object.runtimeType` external. If `Object.runtimeType` is implemented + // in Dart, the TFA will conclude that `null.runtimeType` never returns, + // since it dispatches to `Object.runtimeType`, which uses the receiver + // as non-nullable. w.Local receiver = paramLocals[0]; b.local_get(receiver); - codeGen.call(translator.objectRuntimeType.reference); + codeGen.call(translator.getMasqueradedRuntimeType.reference); return true; } + // _getActualRuntimeType and _getMasqueradedRuntimeType + if (member.enclosingLibrary == translator.coreTypes.coreLibrary && + (name == "_getActualRuntimeType" || + name == "_getMasqueradedRuntimeType")) { + final bool masqueraded = name == "_getMasqueradedRuntimeType"; + + final w.Local object = paramLocals[0]; + final w.Local classId = function.addLocal(w.NumType.i32); + final w.Local resultClassId = function.addLocal(w.NumType.i32); + + w.Label interfaceType = b.block(); + w.Label notMasqueraded = b.block(); + w.Label recordType = b.block(); + w.Label functionType = b.block(); + w.Label abstractClass = b.block(); + + // Look up the type category by class ID and switch on it. + b.global_get(translator.types.typeCategoryTable); + b.local_get(object); + b.struct_get(translator.topInfo.struct, FieldIndex.classId); + b.local_tee(classId); + b.array_get_u((translator.types.typeCategoryTable.type.type as w.RefType) + .heapType as w.ArrayType); + b.local_tee(resultClassId); + b.br_table([ + abstractClass, + functionType, + recordType, + if (masqueraded) notMasqueraded + ], masqueraded ? interfaceType : notMasqueraded); + + b.end(); // abstractClass + // We should never see class IDs for abstract types. + b.unreachable(); + + b.end(); // functionType + w.StructType closureBase = translator.closureLayouter.closureBaseStruct; + b.local_get(object); + b.ref_cast(w.RefType.def(closureBase, nullable: false)); + b.struct_get(closureBase, FieldIndex.closureRuntimeType); + b.return_(); + + b.end(); // recordType + b.local_get(object); + translator.convertType( + function, + object.type, + translator.classInfo[translator.coreTypes.recordClass]!.repr + .nonNullableType); + codeGen.call(translator.recordGetRecordRuntimeType.reference); + b.return_(); + + b.end(); // notMasqueraded + b.local_get(classId); + b.local_set(resultClassId); + + b.end(); // interfaceType + ClassInfo info = translator.classInfo[translator.interfaceTypeClass]!; + b.i32_const(info.classId); + b.i32_const(initialIdentityHash); + // Runtime types are never nullable. + b.i32_const(0); + // Set class ID of interface type. + b.local_get(resultClassId); + b.i64_extend_i32_u(); + // Call _typeArguments to get the list of type arguments. + b.local_get(object); + codeGen.call(translator.objectGetTypeArguments.reference); + b.struct_new(info.struct); + b.return_(); + } + // identical if (member == translator.coreTypes.identicalProcedure) { w.Local first = paramLocals[0]; diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart index 59caf8e8b0e..dfa1484c35a 100644 --- a/pkg/dart2wasm/lib/kernel_nodes.dart +++ b/pkg/dart2wasm/lib/kernel_nodes.dart @@ -162,12 +162,14 @@ mixin KernelNodes { // dart:core various procedures late final Procedure objectNoSuchMethod = index.getProcedure("dart:core", "Object", "noSuchMethod"); - late final Procedure objectRuntimeType = - index.getProcedure("dart:core", "Object", "get:_runtimeType"); + late final Procedure objectGetTypeArguments = + index.getProcedure("dart:core", "Object", "_getTypeArguments"); late final Procedure nullToString = index.getProcedure("dart:core", "Object", "_nullToString"); late final Procedure nullNoSuchMethod = index.getProcedure("dart:core", "Object", "_nullNoSuchMethod"); + late final Procedure recordGetRecordRuntimeType = + index.getProcedure("dart:core", "Record", "_getRecordRuntimeType"); late final Procedure stringEquals = index.getProcedure("dart:core", "_StringBase", "=="); late final Procedure stringInterpolate = @@ -212,6 +214,10 @@ mixin KernelNodes { index.getProcedure("dart:core", "Error", "_throw"); // dart:core type procedures + late final Procedure getActualRuntimeType = + index.getTopLevelProcedure("dart:core", "_getActualRuntimeType"); + late final Procedure getMasqueradedRuntimeType = + index.getTopLevelProcedure("dart:core", "_getMasqueradedRuntimeType"); late final Procedure isSubtype = index.getTopLevelProcedure("dart:core", "_isSubtype"); late final Procedure isTypeSubtype = diff --git a/pkg/dart2wasm/lib/record_class_generator.dart b/pkg/dart2wasm/lib/record_class_generator.dart index 0d7ecbb06b1..6749c492741 100644 --- a/pkg/dart2wasm/lib/record_class_generator.dart +++ b/pkg/dart2wasm/lib/record_class_generator.dart @@ -30,14 +30,14 @@ import 'package:dart2wasm/records.dart'; /// Record_2_a(this.$1, this.$2, this.a); /// /// @pragma('wasm:entry-point') -/// _Type get _runtimeType => -/// // Uses of `runtimeType` below will be fixed with #51134. +/// _Type get _recordRuntimeType => /// _RecordType( /// const ["a"], -/// [$1.runtimeType, $2.runtimeType, a.runtimeType]); -/// -/// @pragma('wasm:entry-point') -/// Type get runtimeType => _runtimeType; +/// [ +/// _getMasqueradedRuntimeTypeNullable($1), +/// _getMasqueradedRuntimeTypeNullable($2), +/// _getMasqueradedRuntimeTypeNullable(a) +/// ]); /// /// @pragma('wasm:entry-point') /// String toString() => @@ -73,9 +73,6 @@ class _RecordClassGenerator { late final Class recordRuntimeTypeClass = coreTypes.index.getClass('dart:core', '_RecordType'); - late final Class internalRuntimeTypeClass = - coreTypes.index.getClass('dart:core', '_Type'); - late final Constructor recordRuntimeTypeConstructor = recordRuntimeTypeClass.constructors.single; @@ -85,9 +82,6 @@ class _RecordClassGenerator { late final Procedure objectHashAllProcedure = coreTypes.index.getProcedure('dart:core', 'Object', 'hashAll'); - late final Procedure objectRuntimeTypeProcedure = - coreTypes.index.getProcedure('dart:core', 'Object', 'get:runtimeType'); - late final Procedure objectToStringProcedure = coreTypes.index.getProcedure('dart:core', 'Object', 'toString'); @@ -96,10 +90,11 @@ class _RecordClassGenerator { late final Procedure stringPlusProcedure = coreTypes.index.getProcedure('dart:core', 'String', '+'); - DartType get nullableObjectType => coreTypes.objectNullableRawType; + late final Procedure getMasqueradedRuntimeTypeNullableProcedure = coreTypes + .index + .getTopLevelProcedure('dart:core', '_getMasqueradedRuntimeTypeNullable'); - DartType get internalRuntimeTypeType => - InterfaceType(internalRuntimeTypeClass, Nullability.nonNullable); + DartType get nullableObjectType => coreTypes.objectNullableRawType; DartType get nonNullableStringType => coreTypes.stringNonNullableRawType; @@ -150,13 +145,7 @@ class _RecordClassGenerator { )); library.addClass(cls); cls.addProcedure(_generateEquals(shape, fields, cls)); - final internalRuntimeType = _generateInternalRuntimeType(shape, fields); - // With `_runtimeType` we also need to override `runtimeType`, as - // `Object.runtimeType` is implemented as a direct call to - // `Object._runtimeType` instead of a virtual call. - final runtimeType = _generateRuntimeType(internalRuntimeType); - cls.addProcedure(internalRuntimeType); - cls.addProcedure(runtimeType); + cls.addProcedure(_generateRecordRuntimeType(shape, fields)); return cls; } @@ -352,8 +341,7 @@ class _RecordClassGenerator { } /// Generate `_Type get _runtimeType` member. - Procedure _generateInternalRuntimeType( - RecordShape shape, List fields) { + Procedure _generateRecordRuntimeType(RecordShape shape, List fields) { final List statements = []; // const ["name1", "name2", ...] @@ -361,16 +349,12 @@ class _RecordClassGenerator { nonNullableStringType, shape.names.map((name) => StringConstant(name)).toList())); - // Generate `this.field.runtimeType` for a given field. - // TODO(51134): We shouldn't use user-provided runtimeType below. - Expression fieldRuntimeTypeExpr(Field field) => InstanceGet( - InstanceAccessKind.Object, + Expression fieldRuntimeTypeExpr(Field field) => StaticInvocation( + getMasqueradedRuntimeTypeNullableProcedure, + Arguments([ InstanceGet(InstanceAccessKind.Instance, ThisExpression(), field.name, - interfaceTarget: field, resultType: nullableObjectType), - objectRuntimeTypeProcedure.name, - interfaceTarget: objectRuntimeTypeProcedure, - resultType: runtimeTypeType, - ); + interfaceTarget: field, resultType: nullableObjectType) + ])); // [this.$1.runtimeType, this.x.runtimeType, ...] final fieldTypesList = ListLiteral( @@ -394,26 +378,7 @@ class _RecordClassGenerator { ); return _addWasmEntryPointPragma(Procedure( - Name('_runtimeType', library), - ProcedureKind.Getter, - function, - fileUri: library.fileUri, - )); - } - - /// Generate `Type get runtimeType member = _runtimeType;`. - Procedure _generateRuntimeType(Procedure internalRuntimeType) { - final FunctionNode function = FunctionNode( - ReturnStatement(InstanceGet(InstanceAccessKind.Instance, ThisExpression(), - internalRuntimeType.name, - interfaceTarget: internalRuntimeType, - resultType: internalRuntimeTypeType)), - positionalParameters: [], - returnType: runtimeTypeType, - ); - - return _addWasmEntryPointPragma(Procedure( - Name('runtimeType', library), + Name('_recordRuntimeType', library), ProcedureKind.Getter, function, fileUri: library.fileUri, diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index a7423de7ba4..f6adff1c11b 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -571,9 +571,12 @@ class Translator with KernelNodes { translateStorageType(type), type.toText(defaultAstTextStrategy)); } - w.ArrayType wasmArrayType(w.StorageType type, String name) { - return arrayTypeCache.putIfAbsent(type, - () => m.addArrayType("Array<$name>", elementType: w.FieldType(type))); + w.ArrayType wasmArrayType(w.StorageType type, String name, + {bool mutable = true}) { + return arrayTypeCache.putIfAbsent( + type, + () => m.addArrayType("Array<$name>", + elementType: w.FieldType(type, mutable: mutable))); } /// Translate a Dart type as it should appear on parameters and returns of diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart index 3d49413badc..a4402547365 100644 --- a/pkg/dart2wasm/lib/types.dart +++ b/pkg/dart2wasm/lib/types.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:math' show max; +import 'dart:typed_data' show Uint8List; import 'package:dart2wasm/class_info.dart'; import 'package:dart2wasm/code_generator.dart'; @@ -13,6 +14,17 @@ import 'package:kernel/core_types.dart'; import 'package:wasm_builder/wasm_builder.dart' as w; +/// Values for the type category table. Entries for masqueraded classes contain +/// the class ID of the masquerade. +class TypeCategory { + static const abstractClass = 0; + static const function = 1; + static const record = 2; + static const notMasqueraded = 3; + static const minMasqueradeClassId = 4; + static const maxMasqueradeClassId = 63; // Leaves 2 unused bits for future use +} + class InterfaceTypeEnvironment { final Map _typeOffsets = {}; @@ -87,6 +99,9 @@ class Types { /// parameter index range of their corresponding function type. Map functionTypeParameterIndex = Map.identity(); + /// An `i8` array of type category values, indexed by class ID. + late final w.Global typeCategoryTable = _buildTypeCategoryTable(); + Types(this.translator); w.ValueType classAndFieldToType(Class cls, int fieldIndex) => @@ -256,6 +271,53 @@ class Types { return expectedType; } + /// Build a global array of byte values used to categorize runtime types. + w.Global _buildTypeCategoryTable() { + Set recordClasses = Set.from(translator.recordClasses.values); + Uint8List table = Uint8List(translator.classes.length); + for (int i = 0; i < translator.classes.length; i++) { + ClassInfo info = translator.classes[i]; + ClassInfo? masquerade = info.masquerade; + Class? cls = info.cls; + int category; + if (cls == null || cls.isAbstract) { + category = TypeCategory.abstractClass; + } else if (cls == translator.closureClass) { + category = TypeCategory.function; + } else if (recordClasses.contains(cls)) { + category = TypeCategory.record; + } else if (masquerade == null || masquerade.classId == i) { + category = TypeCategory.notMasqueraded; + } else { + // Masqueraded class + assert(cls.enclosingLibrary.importUri.scheme == "dart"); + assert(cls.name.startsWith('_')); + assert(masquerade.classId >= TypeCategory.minMasqueradeClassId); + assert(masquerade.classId <= TypeCategory.maxMasqueradeClassId); + category = masquerade.classId; + } + table[i] = category; + } + + w.DataSegment segment = translator.m.addDataSegment(table); + w.ArrayType arrayType = + translator.wasmArrayType(w.PackedType.i8, "const i8", mutable: false); + w.DefinedGlobal global = translator.m + .addGlobal(w.GlobalType(w.RefType.def(arrayType, nullable: false))); + // Initialize the global to a dummy array, since `array.new_data` is not + // a constant instruction and thus can't be used in the initializer. + global.initializer.array_new_fixed(arrayType, 0); + global.initializer.end(); + // Create the actual table in the init function. + w.Instructions b = translator.initFunction.body; + b.i32_const(0); + b.i32_const(table.length); + b.array_new_data(arrayType, segment); + b.global_set(global); + + return global; + } + bool isFunctionTypeParameter(TypeParameterType type) => type.parameter.parent == null; diff --git a/sdk/lib/_internal/wasm/lib/bool.dart b/sdk/lib/_internal/wasm/lib/bool.dart index aa2c78bd3e3..1c32642969f 100644 --- a/sdk/lib/_internal/wasm/lib/bool.dart +++ b/sdk/lib/_internal/wasm/lib/bool.dart @@ -4,18 +4,6 @@ part of "core_patch.dart"; -@patch -class bool { - // Note: this needs to be in `bool`, cannot be overridden in `_BoxedBool`. I - // suspect the problem is there's an assumption in the front-end that `bool` - // has one implementation class (unlike `double`, `int`, `String`) which is - // the `bool` class itself. So when `runtimeType` is not overridden in - // `bool`, in code like `x.runtimeType` where `x` is `bool`, direct call - // metadata says that the member is `Object.runtimeType`. - @override - Type get runtimeType => bool; -} - @pragma("wasm:entry-point") final class _BoxedBool extends bool { // A boxed bool contains an unboxed bool. diff --git a/sdk/lib/_internal/wasm/lib/double.dart b/sdk/lib/_internal/wasm/lib/double.dart index 03705070c45..c0d0255f4f3 100644 --- a/sdk/lib/_internal/wasm/lib/double.dart +++ b/sdk/lib/_internal/wasm/lib/double.dart @@ -60,9 +60,6 @@ final class _BoxedDouble extends double { /// Dummy factory to silence error about missing superclass constructor. external factory _BoxedDouble(); - @override - Type get runtimeType => double; - static const int _mantissaBits = 52; static const int _exponentBits = 11; static const int _exponentBias = 1023; diff --git a/sdk/lib/_internal/wasm/lib/errors_patch.dart b/sdk/lib/_internal/wasm/lib/errors_patch.dart index ba30d82f0ff..af07caa106b 100644 --- a/sdk/lib/_internal/wasm/lib/errors_patch.dart +++ b/sdk/lib/_internal/wasm/lib/errors_patch.dart @@ -7,9 +7,7 @@ part of 'core_patch.dart'; @patch class Error { @patch - static String _objectToString(Object object) { - return "Instance of '${object._runtimeType}'"; - } + static String _objectToString(Object object) => Object._toString(object); @patch static String _stringToSafeString(String string) { diff --git a/sdk/lib/_internal/wasm/lib/int.dart b/sdk/lib/_internal/wasm/lib/int.dart index 359361af3ed..c5543fb2a54 100644 --- a/sdk/lib/_internal/wasm/lib/int.dart +++ b/sdk/lib/_internal/wasm/lib/int.dart @@ -34,9 +34,6 @@ final class _BoxedInt extends int { /// Dummy factory to silence error about missing superclass constructor. external factory _BoxedInt(); - @override - Type get runtimeType => int; - external num operator +(num other); external num operator -(num other); external num operator *(num other); diff --git a/sdk/lib/_internal/wasm/lib/object_patch.dart b/sdk/lib/_internal/wasm/lib/object_patch.dart index 706467d9223..e971efb5132 100644 --- a/sdk/lib/_internal/wasm/lib/object_patch.dart +++ b/sdk/lib/_internal/wasm/lib/object_patch.dart @@ -8,11 +8,6 @@ part of "core_patch.dart"; external int _getHash(Object obj); external void _setHash(Object obj, int hash); -external _Type _getInterfaceTypeRuntimeType( - Object object, List typeArguments); - -external _Type _getFunctionTypeRuntimeType(Object object); - @patch class Object { @patch @@ -43,24 +38,21 @@ class Object { /// which return their type arguments. List<_Type> get _typeArguments => const []; - /// We use [_runtimeType] for internal type testing, because objects can - /// override [runtimeType]. + // An instance member needs a call from Dart code to be properly included in + // the dispatch table. Hence we use an inlined static wrapper as entry point. + @pragma("wasm:entry-point") + @pragma("wasm:prefer-inline") + static List<_Type> _getTypeArguments(Object object) => object._typeArguments; + @patch external Type get runtimeType; - @pragma("wasm:entry-point") - _Type get _runtimeType { - if (ClassID.getID(this) == ClassID.cid_Closure) { - return _getFunctionTypeRuntimeType(this); - } else { - return _getInterfaceTypeRuntimeType(this, _typeArguments); - } - } - @patch String toString() => _toString(this); // A statically dispatched version of Object.toString. - static String _toString(obj) => "Instance of '${obj.runtimeType}'"; + static String _toString(Object obj) { + return "Instance of '${_getMasqueradedRuntimeType(obj)}'"; + } @patch @pragma("wasm:entry-point") diff --git a/sdk/lib/_internal/wasm/lib/record_patch.dart b/sdk/lib/_internal/wasm/lib/record_patch.dart index 7b784746be0..c33283133e2 100644 --- a/sdk/lib/_internal/wasm/lib/record_patch.dart +++ b/sdk/lib/_internal/wasm/lib/record_patch.dart @@ -7,4 +7,13 @@ part of "core_patch.dart"; // `entry-point` needed to make sure the class will be in the class hierarchy // in programs without records. @pragma('wasm:entry-point') -abstract class Record {} +abstract class Record { + _Type get _recordRuntimeType; + + // An instance member needs a call from Dart code to be properly included in + // the dispatch table. Hence we use an inlined static wrapper as entry point. + @pragma("wasm:entry-point") + @pragma("wasm:prefer-inline") + static _Type _getRecordRuntimeType(Record record) => + record._recordRuntimeType; +} diff --git a/sdk/lib/_internal/wasm/lib/simd_patch.dart b/sdk/lib/_internal/wasm/lib/simd_patch.dart index bba4c3700eb..ca7515f055c 100644 --- a/sdk/lib/_internal/wasm/lib/simd_patch.dart +++ b/sdk/lib/_internal/wasm/lib/simd_patch.dart @@ -177,9 +177,6 @@ final class _NaiveFloat32x4List extends Object _NaiveFloat32x4List(int length) : _storage = Float32List(length * 4); - @override - Type get runtimeType => Float32x4List; - _NaiveFloat32x4List._externalStorage(this._storage); _NaiveFloat32x4List._slowFromList(List list) @@ -243,9 +240,6 @@ final class _NaiveFloat64x2List extends Object _NaiveFloat64x2List(int length) : _storage = Float64List(length * 2); - @override - Type get runtimeType => Float64x2List; - _NaiveFloat64x2List._externalStorage(this._storage); _NaiveFloat64x2List._slowFromList(List list) @@ -338,9 +332,6 @@ final class _NaiveFloat32x4 implements Float32x4 { _NaiveFloat32x4._truncated(this.x, this.y, this.z, this.w); - @override - Type get runtimeType => Float32x4List; - @override String toString() { return '[${x.toStringAsFixed(6)}, ' diff --git a/sdk/lib/_internal/wasm/lib/string_patch.dart b/sdk/lib/_internal/wasm/lib/string_patch.dart index 810f177f8fb..4fc78b0c42b 100644 --- a/sdk/lib/_internal/wasm/lib/string_patch.dart +++ b/sdk/lib/_internal/wasm/lib/string_patch.dart @@ -95,9 +95,6 @@ abstract final class _StringBase implements String { _StringBase._(); - @override - Type get runtimeType => String; - int get hashCode { int hash = _getHash(this); if (hash != 0) return hash; diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart index 2dd1c5f16b0..75aeae564ec 100644 --- a/sdk/lib/_internal/wasm/lib/type.dart +++ b/sdk/lib/_internal/wasm/lib/type.dart @@ -1026,7 +1026,8 @@ _TypeUniverse _typeUniverse = _TypeUniverse.create(); @pragma("wasm:entry-point") bool _isSubtype(Object? s, _Type t) { - return _typeUniverse.isSubtype(s._runtimeType, null, t, null); + return _typeUniverse.isSubtype( + _getActualRuntimeTypeNullable(s), null, t, null); } @pragma("wasm:entry-point") @@ -1166,3 +1167,17 @@ void _checkClosureType(_FunctionType functionType, List<_Type> typeArguments, } } } + +@pragma("wasm:entry-point") +external _Type _getActualRuntimeType(Object object); + +@pragma("wasm:prefer-inline") +_Type _getActualRuntimeTypeNullable(Object? object) => + object == null ? const _NullType() : _getActualRuntimeType(object); + +@pragma("wasm:entry-point") +external _Type _getMasqueradedRuntimeType(Object object); + +@pragma("wasm:prefer-inline") +_Type _getMasqueradedRuntimeTypeNullable(Object? object) => + object == null ? const _NullType() : _getMasqueradedRuntimeType(object); diff --git a/sdk/lib/_internal/wasm/lib/typed_data_patch.dart b/sdk/lib/_internal/wasm/lib/typed_data_patch.dart index 900091623de..9794aa4d809 100644 --- a/sdk/lib/_internal/wasm/lib/typed_data_patch.dart +++ b/sdk/lib/_internal/wasm/lib/typed_data_patch.dart @@ -100,69 +100,3 @@ abstract class _TypedList extends _TypedListBase { data.setFloat64(offsetInBytes + 1 * 8, value.y, Endian.host); } } - -@patch -class _Int8List { - @override - Type get runtimeType => Int8List; -} - -@patch -class _Uint8List { - @override - Type get runtimeType => Uint8List; -} - -@patch -class _Uint8ClampedList { - @override - Type get runtimeType => Uint8ClampedList; -} - -@patch -class _Int16List { - @override - Type get runtimeType => Int16List; -} - -@patch -class _Uint16List { - @override - Type get runtimeType => Uint16List; -} - -@patch -class _Int32List { - @override - Type get runtimeType => Int32List; -} - -@patch -class _Uint32List { - @override - Type get runtimeType => Uint32List; -} - -@patch -class _Int64List { - @override - Type get runtimeType => Int64List; -} - -@patch -class _Uint64List { - @override - Type get runtimeType => Uint64List; -} - -@patch -class _Float32List { - @override - Type get runtimeType => Float32List; -} - -@patch -class _Float64List { - @override - Type get runtimeType => Float64List; -} diff --git a/tests/language/records/simple/overridden_runtimetype_test.dart b/tests/language/records/simple/overridden_runtimetype_test.dart new file mode 100644 index 00000000000..108f636d53a --- /dev/null +++ b/tests/language/records/simple/overridden_runtimetype_test.dart @@ -0,0 +1,40 @@ +// 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 + +import "package:expect/expect.dart"; + +class A { + @override + Type get runtimeType => B; +} + +class B extends A { + @override + Type get runtimeType => A; +} + +Type typeObject() => T; + +main() { + final a = A(); + final b = B(); + + Expect.isTrue(a is A); + Expect.isFalse(a is B); + Expect.isTrue(b is A); + Expect.isTrue(b is B); + + Expect.isTrue((a,) is (A,)); + Expect.isFalse((a,) is (B,)); + Expect.isTrue((b,) is (A,)); + Expect.isTrue((b,) is (B,)); + + Expect.equals(typeObject(), a.runtimeType); + Expect.equals(typeObject(), b.runtimeType); + + Expect.equals(typeObject<(A,)>(), (a,).runtimeType); + Expect.equals(typeObject<(B,)>(), (b,).runtimeType); +}