[dart2wasm] Categorize and masquerade runtime types by lookup table

This avoids virtual calls for `runtimeType` (unless the user overrides
it) and `_runtimeType` (since the internal categorization is now done
using the table instead), which saves a huge amount of space in the
global dispatch table.

It also fixes record runtime types, which now use the masqueraded
types for its fields, rather than the (possibly user-overridden)
`runtimeType`.

A masquerade case was missing for `Type`, which has been added.

Fixes https://github.com/dart-lang/sdk/issues/51134

Tested: ci + new test for overridden runtimeType
Change-Id: I1909c665ae78eb07b9c0eb22b6e8836e27495d70
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/285684
Reviewed-by: Ömer Ağacan <omersa@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
Aske Simon Christensen 2023-03-09 22:08:19 +00:00 committed by Commit Queue
parent 9ba4957a31
commit c19720245c
18 changed files with 306 additions and 201 deletions

View file

@ -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<int, w.StructType> _recordStructs = {};
/// Masquerades for implementation classes. For each entry of the map, all
/// subtypes of the key masquerade as the value.
late final Map<Class, Class> _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 <String>[
"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);

View file

@ -2156,7 +2156,6 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
b.i64_const(2011);
break;
case "runtimeType":
case "_runtimeType":
wrap(ConstantExpression(TypeLiteralConstant(NullType())), resultType);
break;
default:

View file

@ -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];

View file

@ -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 =

View file

@ -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<Field> fields) {
Procedure _generateRecordRuntimeType(RecordShape shape, List<Field> fields) {
final List<Statement> 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,

View file

@ -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

View file

@ -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<TypeParameter, int> _typeOffsets = {};
@ -87,6 +99,9 @@ class Types {
/// parameter index range of their corresponding function type.
Map<TypeParameter, int> 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<Class> 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;

View file

@ -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.

View file

@ -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;

View file

@ -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) {

View file

@ -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);

View file

@ -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<Type> 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")

View file

@ -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;
}

View file

@ -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<Float32x4> 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<Float64x2> 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)}, '

View file

@ -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;

View file

@ -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);

View file

@ -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;
}

View file

@ -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>() => 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<B>(), a.runtimeType);
Expect.equals(typeObject<A>(), b.runtimeType);
Expect.equals(typeObject<(A,)>(), (a,).runtimeType);
Expect.equals(typeObject<(B,)>(), (b,).runtimeType);
}