Version 2.18.0-74.0.dev
Merge commit '6055226a21de6c614afd0bc93ae2d2d41b6c367e' into 'dev'
diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart
index 27986fc..ac2fdda 100644
--- a/pkg/dart2wasm/lib/class_info.dart
+++ b/pkg/dart2wasm/lib/class_info.dart
@@ -25,7 +25,7 @@
static const hashBaseData = 4;
static const closureContext = 2;
static const closureFunction = 3;
- static const typeTypeArguments = 3;
+ static const interfaceTypeTypeArguments = 4;
static const typedListBaseLength = 2;
static const typedListArray = 3;
static const typedListViewTypedData = 3;
@@ -51,7 +51,8 @@
check(translator.hashFieldBaseClass, "_index", FieldIndex.hashBaseIndex);
check(translator.hashFieldBaseClass, "_data", FieldIndex.hashBaseData);
check(translator.functionClass, "context", FieldIndex.closureContext);
- check(translator.typeClass, "typeArguments", FieldIndex.typeTypeArguments);
+ check(translator.interfaceTypeClass, "typeArguments",
+ FieldIndex.interfaceTypeTypeArguments);
}
}
diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart
index 412bd0b..6ed2e32 100644
--- a/pkg/dart2wasm/lib/constants.dart
+++ b/pkg/dart2wasm/lib/constants.dart
@@ -7,6 +7,7 @@
import 'package:dart2wasm/class_info.dart';
import 'package:dart2wasm/translator.dart';
+import 'package:dart2wasm/types.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/type_algebra.dart' show substitute;
@@ -367,6 +368,7 @@
ConstantCreator(this.constants);
Translator get translator => constants.translator;
+ Types get types => translator.types;
w.Module get m => constants.m;
bool get lazyConstants => constants.lazyConstants;
@@ -682,36 +684,36 @@
@override
ConstantInfo? visitTypeLiteralConstant(TypeLiteralConstant constant) {
- DartType cType = constant.type;
- assert(cType is! TypeParameterType);
- DartType type = cType is DynamicType ||
- cType is VoidType ||
- cType is NeverType ||
- cType is NullType
- ? translator.coreTypes.objectRawType(Nullability.nullable)
- : cType is FunctionType
- ? InterfaceType(translator.functionClass, cType.declaredNullability)
- : cType;
- if (type is! InterfaceType) throw "Not implemented: $constant";
+ DartType type = constant.type;
+ assert(type is! TypeParameterType);
- ListConstant typeArgs = ListConstant(
- InterfaceType(translator.typeClass, Nullability.nonNullable),
- type.typeArguments.map((t) => TypeLiteralConstant(t)).toList());
- ensureConstant(typeArgs);
-
- ClassInfo info = constants.typeInfo;
+ ClassInfo info = translator.classInfo[types.classForType(type)]!;
translator.functions.allocateClass(info.classId);
- return createConstant(constant, info.nonNullableType, (function, b) {
- ClassInfo typeInfo = translator.classInfo[type.classNode]!;
- w.ValueType typeListExpectedType =
- info.struct.fields[FieldIndex.typeTypeArguments].type.unpacked;
+ if (type is InterfaceType) {
+ ListConstant typeArgs = ListConstant(
+ InterfaceType(translator.typeClass, Nullability.nonNullable),
+ type.typeArguments.map((t) => TypeLiteralConstant(t)).toList());
+ ensureConstant(typeArgs);
+ return createConstant(constant, info.nonNullableType, (function, b) {
+ ClassInfo typeInfo = translator.classInfo[type.classNode]!;
+ w.ValueType typeListExpectedType = info
+ .struct.fields[FieldIndex.interfaceTypeTypeArguments].type.unpacked;
- b.i32_const(info.classId);
- b.i32_const(initialIdentityHash);
- b.i64_const(typeInfo.classId);
- constants.instantiateConstant(
- function, b, typeArgs, typeListExpectedType);
- translator.struct_new(b, info);
- });
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ b.i64_const(typeInfo.classId);
+ b.i32_const(types.isNullable(type) ? 1 : 0);
+ constants.instantiateConstant(
+ function, b, typeArgs, typeListExpectedType);
+ translator.struct_new(b, info);
+ });
+ } else {
+ // TODO(joshualitt): Real implementation for complex types.
+ return createConstant(constant, info.nonNullableType, (function, b) {
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ translator.struct_new(b, info);
+ });
+ }
}
}
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
index 84535f6..755ac27 100644
--- a/pkg/dart2wasm/lib/intrinsics.dart
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -639,6 +639,8 @@
codeGen.wrap(stackTrace, stackTraceType);
b.throw_(translator.exceptionTag);
return codeGen.voidMarker;
+ case "_getSubtypeMap":
+ return translator.types.makeSubtypeMap(b);
}
}
@@ -971,19 +973,23 @@
}
// Object.runtimeType
+ // TODO(joshualitt): Implement this correctly for [FunctionType] and
+ // [InterfaceType].
if (member.enclosingClass == translator.coreTypes.objectClass &&
name == "runtimeType") {
w.Local receiver = paramLocals[0];
- ClassInfo info = translator.classInfo[translator.typeClass]!;
+ ClassInfo info = translator.classInfo[translator.interfaceTypeClass]!;
translator.functions.allocateClass(info.classId);
- w.ValueType typeListExpectedType =
- info.struct.fields[FieldIndex.typeTypeArguments].type.unpacked;
+ w.ValueType typeListExpectedType = info
+ .struct.fields[FieldIndex.interfaceTypeTypeArguments].type.unpacked;
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
b.local_get(receiver);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.i64_extend_i32_u();
+ // Runtime types are never nullable.
+ b.i32_const(0);
// TODO(askesc): Type arguments
b.global_get(translator.constants.emptyTypeList);
translator.convertType(function,
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index fa258cf..e019025 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -89,6 +89,14 @@
late final Class oneByteStringClass;
late final Class twoByteStringClass;
late final Class typeClass;
+ late final Class neverTypeClass;
+ late final Class dynamicTypeClass;
+ late final Class voidTypeClass;
+ late final Class nullTypeClass;
+ late final Class futureOrTypeClass;
+ late final Class interfaceTypeClass;
+ late final Class functionTypeClass;
+ late final Class genericFunctionTypeClass;
late final Class stackTraceClass;
late final Class ffiCompoundClass;
late final Class ffiPointerClass;
@@ -97,6 +105,7 @@
late final Class typedListViewClass;
late final Class byteDataViewClass;
late final Class typeErrorClass;
+ late final Class typeUniverseClass;
late final Procedure wasmFunctionCall;
late final Procedure stackTraceCurrent;
late final Procedure stringEquals;
@@ -109,6 +118,8 @@
late final Procedure setFactory;
late final Procedure setAdd;
late final Procedure hashImmutableIndexNullable;
+ // TODO(joshualitt): Wire up runtime type checks.
+ late final Procedure isSubtype;
late final Map<Class, w.StorageType> builtinTypes;
late final Map<w.ValueType, Class> boxedClasses;
@@ -157,6 +168,7 @@
classInfoCollector = ClassInfoCollector(this);
dispatchTable = DispatchTable(this);
functions = FunctionCollector(this);
+ types = Types(this);
Class Function(String) makeLookup(String libraryName) {
Library library =
@@ -192,7 +204,16 @@
oneByteStringClass = lookupCore("_OneByteString");
twoByteStringClass = lookupCore("_TwoByteString");
typeClass = lookupCore("_Type");
+ neverTypeClass = lookupCore("_NeverType");
+ dynamicTypeClass = lookupCore("_DynamicType");
+ voidTypeClass = lookupCore("_VoidType");
+ nullTypeClass = lookupCore("_NullType");
+ futureOrTypeClass = lookupCore("_FutureOrType");
+ interfaceTypeClass = lookupCore("_InterfaceType");
+ functionTypeClass = lookupCore("_FunctionType");
+ genericFunctionTypeClass = lookupCore("_GenericFunctionType");
stackTraceClass = lookupCore("StackTrace");
+ typeUniverseClass = lookupCore("_TypeUniverse");
ffiCompoundClass = lookupFfi("_Compound");
ffiPointerClass = lookupFfi("Pointer");
typeErrorClass = lookupCore("_TypeError");
@@ -229,6 +250,10 @@
hashImmutableIndexNullable = lookupCollection("_HashAbstractImmutableBase")
.procedures
.firstWhere((p) => p.name.text == "_indexNullable");
+ isSubtype = component.libraries
+ .firstWhere((l) => l.name == "dart.core")
+ .procedures
+ .firstWhere((p) => p.name.text == "_isSubtype");
builtinTypes = {
coreTypes.boolClass: w.NumType.i32,
coreTypes.intClass: w.NumType.i64,
@@ -271,7 +296,6 @@
globals = Globals(this);
constants = Constants(this);
- types = Types(this);
dispatchTable.build();
diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart
index f9ee6a4..f517453 100644
--- a/pkg/dart2wasm/lib/types.dart
+++ b/pkg/dart2wasm/lib/types.dart
@@ -16,10 +16,46 @@
Types(this.translator);
- List<Class> _getConcreteSubtypes(Class cls) => translator.subtypes
- .getSubtypesOf(cls)
- .where((c) => !c.isAbstract)
- .toList();
+ Iterable<Class> _getConcreteSubtypes(Class cls) =>
+ translator.subtypes.getSubtypesOf(cls).where((c) => !c.isAbstract);
+
+ /// Build a [Map<int, List<int>>] to store subtype information.
+ Map<int, List<int>> _buildSubtypeMap() {
+ List<ClassInfo> classes = translator.classes;
+ Map<int, List<int>> subtypeMap = {};
+ for (ClassInfo classInfo in classes) {
+ if (classInfo.cls == null) continue;
+ List<int> classIds = _getConcreteSubtypes(classInfo.cls!)
+ .map((cls) => translator.classInfo[cls]!.classId)
+ .where((classId) => classId != classInfo.classId)
+ .toList();
+
+ if (classIds.isEmpty) continue;
+ subtypeMap[classInfo.classId] = classIds;
+ }
+ return subtypeMap;
+ }
+
+ /// Builds the subtype map and pushes it onto the stack.
+ w.ValueType makeSubtypeMap(w.Instructions b) {
+ // Instantiate subtype map constant.
+ Map<int, List<int>> subtypeMap = _buildSubtypeMap();
+ ClassInfo immutableMapInfo =
+ translator.classInfo[translator.immutableMapClass]!;
+ w.ValueType expectedType = immutableMapInfo.nonNullableType;
+ DartType mapAndSetKeyType = translator.coreTypes.intNonNullableRawType;
+ DartType mapValueType = InterfaceType(translator.immutableListClass,
+ Nullability.nonNullable, [mapAndSetKeyType]);
+ List<ConstantMapEntry> entries = subtypeMap.entries.map((mapEntry) {
+ return ConstantMapEntry(
+ IntConstant(mapEntry.key),
+ ListConstant(mapAndSetKeyType,
+ mapEntry.value.map((i) => IntConstant(i)).toList()));
+ }).toList();
+ translator.constants.instantiateConstant(null, b,
+ MapConstant(mapAndSetKeyType, mapValueType, entries), expectedType);
+ return expectedType;
+ }
bool _isTypeConstant(DartType type) {
return type is DynamicType ||
@@ -30,7 +66,32 @@
type is InterfaceType && type.typeArguments.every(_isTypeConstant);
}
+ Class classForType(DartType type) {
+ if (type is DynamicType) {
+ return translator.dynamicTypeClass;
+ } else if (type is VoidType) {
+ return translator.voidTypeClass;
+ } else if (type is NeverType) {
+ return translator.neverTypeClass;
+ } else if (type is NullType) {
+ return translator.nullTypeClass;
+ } else if (type is FutureOrType) {
+ return translator.futureOrTypeClass;
+ } else if (type is InterfaceType) {
+ return translator.interfaceTypeClass;
+ } else if (type is FunctionType) {
+ if (type.typeParameters.isEmpty) {
+ return translator.functionTypeClass;
+ } else {
+ return translator.genericFunctionTypeClass;
+ }
+ }
+ throw "Unexpected DartType: $type";
+ }
+
/// Makes a `_Type` object on the stack.
+ /// TODO(joshualitt): Refactor this logic to remove the dependency on
+ /// CodeGenerator.
w.ValueType makeType(CodeGenerator codeGen, DartType type, TreeNode node) {
w.ValueType typeType =
translator.classInfo[translator.typeClass]!.nullableType;
@@ -62,23 +123,25 @@
b.struct_get(info.struct, fieldIndex);
return typeType;
}
- ClassInfo info = translator.classInfo[translator.typeClass]!;
+ ClassInfo info = translator.classInfo[classForType(type)]!;
translator.functions.allocateClass(info.classId);
- if (type is FutureOrType) {
- // TODO(askesc): Have an actual representation of FutureOr types
- b.ref_null(info.nullableType.heapType);
- return info.nullableType;
- }
if (type is! InterfaceType) {
- codeGen.unimplemented(node, type, [info.nullableType]);
- return info.nullableType;
+ if (type is FutureOrType || type is FunctionType) {
+ // TODO(joshualitt): Finish RTI.
+ print("Not implemented: RTI ${type}");
+ }
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ translator.struct_new(b, info);
+ return info.nonNullableType;
}
ClassInfo typeInfo = translator.classInfo[type.classNode]!;
w.ValueType typeListExpectedType =
- info.struct.fields[FieldIndex.typeTypeArguments].type.unpacked;
+ info.struct.fields[FieldIndex.interfaceTypeTypeArguments].type.unpacked;
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
b.i64_const(typeInfo.classId);
+ b.i32_const(isNullable(type) ? 1 : 0);
w.DefinedFunction function = codeGen.function;
if (type.typeArguments.isEmpty) {
b.global_get(translator.constants.emptyTypeList);
@@ -99,11 +162,12 @@
translator.convertType(function, listType, typeListExpectedType);
}
translator.struct_new(b, info);
- return info.nullableType;
+ return info.nonNullableType;
}
/// Test value against a Dart type. Expects the value on the stack as a
/// (ref null #Top) and leaves the result on the stack as an i32.
+ /// TODO(joshualitt): Remove dependency on [CodeGenerator]
void emitTypeTest(CodeGenerator codeGen, DartType type, DartType operandType,
TreeNode node) {
w.Instructions b = codeGen.b;
@@ -115,9 +179,9 @@
b.i32_const(1);
return;
}
- bool isNullable = operandType.isPotentiallyNullable;
+ bool isPotentiallyNullable = operandType.isPotentiallyNullable;
w.Label? resultLabel;
- if (isNullable) {
+ if (isPotentiallyNullable) {
// Store operand in a temporary variable, since Binaryen does not support
// block inputs.
w.Local operand = codeGen.addLocal(translator.topInfo.nullableType);
@@ -140,7 +204,7 @@
" at ${node.location}");
}
}
- List<Class> concrete = _getConcreteSubtypes(type.classNode);
+ List<Class> concrete = _getConcreteSubtypes(type.classNode).toList();
if (type.classNode == translator.coreTypes.functionClass) {
ClassInfo functionInfo = translator.classInfo[translator.functionClass]!;
translator.ref_test(b, functionInfo);
@@ -169,11 +233,19 @@
b.i32_const(0);
b.end(); // done
}
- if (isNullable) {
+ if (isPotentiallyNullable) {
b.br(resultLabel!);
b.end(); // nullLabel
- b.i32_const(type.declaredNullability == Nullability.nullable ? 1 : 0);
+ b.i32_const(isNullable(type) ? 1 : 0);
b.end(); // resultLabel
}
}
+
+ bool isNullable(InterfaceType type) {
+ Nullability nullability = type.declaredNullability;
+ // TODO(joshualitt): Enable assert when spurious 'legacy' values are fixed.
+ // assert(nullability == Nullability.nonNullable ||
+ // nullability == Nullability.nullable);
+ return nullability == Nullability.nullable ? true : false;
+ }
}
diff --git a/sdk/lib/_internal/wasm/lib/class_id.dart b/sdk/lib/_internal/wasm/lib/class_id.dart
index be91738..b9535e9 100644
--- a/sdk/lib/_internal/wasm/lib/class_id.dart
+++ b/sdk/lib/_internal/wasm/lib/class_id.dart
@@ -14,6 +14,26 @@
external static int get cidUint8Array;
@pragma("wasm:class-id", "dart.typed_data#_Uint8ArrayView")
external static int get cidUint8ArrayView;
+ @pragma("wasm:class-id", "dart.core#Object")
+ external static int get cidObject;
+
+ // Class IDs for RTI Types.
+ @pragma("wasm:class-id", "dart.core#_NeverType")
+ external static int get cidNeverType;
+ @pragma("wasm:class-id", "dart.core#_DynamicType")
+ external static int get cidDynamicType;
+ @pragma("wasm:class-id", "dart.core#_VoidType")
+ external static int get cidVoidType;
+ @pragma("wasm:class-id", "dart.core#_NullType")
+ external static int get cidNullType;
+ @pragma("wasm:class-id", "dart.core#_FutureOrType")
+ external static int get cidFutureOrType;
+ @pragma("wasm:class-id", "dart.core#_InterfaceType")
+ external static int get cidInterfaceType;
+ @pragma("wasm:class-id", "dart.core#_FunctionType")
+ external static int get cidFunctionType;
+ @pragma("wasm:class-id", "dart.core#_GenericFunctionType")
+ external static int get cidGenericFunctionType;
// Dummy, only used by VM-specific hash table code.
static final int numPredefinedCids = 1;
diff --git a/sdk/lib/_internal/wasm/lib/core_patch.dart b/sdk/lib/_internal/wasm/lib/core_patch.dart
index 64b7d9d..29fe06c 100644
--- a/sdk/lib/_internal/wasm/lib/core_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/core_patch.dart
@@ -9,6 +9,7 @@
allocateOneByteString,
allocateTwoByteString,
CodeUnits,
+ ClassID,
copyRangeFromUint8ListToOneByteString,
doubleToIntBits,
EfficientLengthIterable,
diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart
index 6db370d..f775a22 100644
--- a/sdk/lib/_internal/wasm/lib/type.dart
+++ b/sdk/lib/_internal/wasm/lib/type.dart
@@ -2,37 +2,105 @@
// 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.
-// Representation of runtime types. Can only represent interface types so far,
-// and does not capture nullability.
+//import 'dart:_internal' show ClassID;
+
+// Representation of runtime types. Code in this file should avoid using `is` or
+// `as` entirely to avoid a dependency on any inline type checks.
+
+// TODO(joshualitt): Once we have RTI fully working, we'd like to explore
+// implementing [isSubtype] using inheritance.
+abstract class _Type implements Type {
+ const _Type();
+
+ bool _testID(int value) => ClassID.getID(this) == value;
+ bool get isNever => _testID(ClassID.cidNeverType);
+ bool get isDynamic => _testID(ClassID.cidDynamicType);
+ bool get isVoid => _testID(ClassID.cidVoidType);
+ bool get isFutureOr => _testID(ClassID.cidFutureOrType);
+ bool get isInterface => _testID(ClassID.cidInterfaceType);
+ bool get isFunction => _testID(ClassID.cidFunctionType);
+ bool get isGenericFunctionType => _testID(ClassID.cidGenericFunctionType);
+ bool get isNullable => false;
+
+ T as<T>() => unsafeCast<T>(this);
+
+ @override
+ bool operator ==(Object other) => ClassID.getID(this) == ClassID.getID(other);
+
+ @override
+ int get hashCode => mix64(ClassID.getID(this));
+}
@pragma("wasm:entry-point")
-class _Type implements Type {
+class _NeverType extends _Type {
+ @override
+ String toString() => 'Never';
+}
+
+@pragma("wasm:entry-point")
+class _DynamicType extends _Type {
+ @override
+ String toString() => 'dynamic';
+}
+
+@pragma("wasm:entry-point")
+class _VoidType extends _Type {
+ @override
+ String toString() => 'void';
+}
+
+@pragma("wasm:entry-point")
+class _NullType extends _Type {
+ @override
+ String toString() => 'Null';
+}
+
+@pragma("wasm:entry-point")
+class _FutureOrType extends _Type {
+ // TODO(joshualitt): Implement.
+ @override
+ String toString() => 'FutureOr';
+}
+
+class _InterfaceType extends _Type {
final int classId;
+ final bool declaredNullable;
final List<_Type> typeArguments;
@pragma("wasm:entry-point")
- const _Type(this.classId, [this.typeArguments = const []]);
+ const _InterfaceType(this.classId, this.declaredNullable,
+ [this.typeArguments = const []]);
- bool operator ==(Object other) {
- if (other is! _Type) return false;
+ bool get isNullable => declaredNullable;
+
+ @override
+ bool operator ==(Object o) {
+ if (!(super == (o))) return false;
+ _InterfaceType other = unsafeCast<_InterfaceType>(o);
if (classId != other.classId) return false;
+ if (isNullable != other.isNullable) return false;
+ assert(typeArguments.length == other.typeArguments.length);
for (int i = 0; i < typeArguments.length; i++) {
if (typeArguments[i] != other.typeArguments[i]) return false;
}
return true;
}
+ @override
int get hashCode {
- int hash = mix64(classId);
+ int hash = super.hashCode;
+ hash = mix64(hash ^ classId);
+ hash = mix64(hash ^ (isNullable ? 1 : 0));
for (int i = 0; i < typeArguments.length; i++) {
hash = mix64(hash ^ typeArguments[i].hashCode);
}
return hash;
}
+ @override
String toString() {
StringBuffer s = StringBuffer();
- s.write("Type");
+ s.write("Interface");
s.write(classId);
if (typeArguments.isNotEmpty) {
s.write("<");
@@ -42,6 +110,115 @@
}
s.write(">");
}
+ if (isNullable) s.write("?");
return s.toString();
}
}
+
+@pragma("wasm:entry-point")
+class _FunctionType extends _Type {
+ // TODO(joshualitt): Implement.
+ @override
+ String toString() => 'FunctionType';
+}
+
+@pragma("wasm:entry-point")
+class _GenericFunctionType extends _FunctionType {
+ // TODO(joshualitt): Implement.
+ @override
+ String toString() => 'GenericFunctionType';
+}
+
+external Map<int, List<int>> _getSubtypeMap();
+
+class _TypeUniverse {
+ /// 'Map' of classId to range of subclasses.
+ final Map<int, List<int>> _subtypeMap;
+
+ const _TypeUniverse._(this._subtypeMap);
+
+ factory _TypeUniverse.create() {
+ return _TypeUniverse._(_getSubtypeMap());
+ }
+
+ bool isObjectQuestionType(_Type t) {
+ if (!t.isInterface) return false;
+ _InterfaceType type = t.as<_InterfaceType>();
+ return type.classId == ClassID.cidObject && type.isNullable;
+ }
+
+ bool isTopType(_Type type) {
+ return isObjectQuestionType(type) || type.isDynamic || type.isVoid;
+ }
+
+ bool isBottomType(_Type type) {
+ return type.isNever;
+ }
+
+ bool isInterfaceSubtype(_InterfaceType s, _InterfaceType t) {
+ int sId = s.classId;
+ int tId = t.classId;
+ if (sId == tId) {
+ assert(s.typeArguments.length == t.typeArguments.length);
+ for (int i = 0; i < s.typeArguments.length; i++) {
+ if (!isSubtype(s.typeArguments[i], t.typeArguments[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ List<int>? subtypes = _subtypeMap[tId];
+ if (subtypes == null) return false;
+ if (!subtypes.contains(sId)) return false;
+ // TODO(joshualitt): Compare type arguments.
+ return true;
+ }
+
+ // Subtype check based off of sdk/lib/_internal/js_runtime/lib/rti.dart.
+ // Returns true if [s] is a subtype of [t], false otherwise.
+ bool isSubtype(_Type s, _Type t) {
+ // Reflexivity:
+ if (identical(s, t)) return true;
+
+ // Right Top:
+ if (isTopType(t)) return true;
+
+ // Left Top:
+ if (isTopType(s)) return false;
+
+ // Left Bottom:
+ if (isBottomType(s)) return true;
+
+ // TODO(joshualitt): Implement missing cases.
+ // Left type variable bound 1:
+ // Left Null:
+ // Right Object:
+ // Left FuturOr:
+ // Left Nullable:
+ // Do we need to handle at runtime
+ // Type Variable Reflexivity 1 && 2
+ // Right Promoted Variable
+ // Right FutureOr:
+ // Right Nullable:
+ // Do we need to handle at runtime:
+ // Left Promoted Variable
+ // Left Type Variable Bound 2:
+ // Function Type / Function:
+ // Positional Function Types + Named Function Types:
+
+ // Interface Compositionality + Super-Interface:
+ if (s.isInterface &&
+ t.isInterface &&
+ isInterfaceSubtype(s.as<_InterfaceType>(), t.as<_InterfaceType>())) {
+ return true;
+ }
+ return false;
+ }
+}
+
+_TypeUniverse _typeUniverse = _TypeUniverse.create();
+
+@pragma("wasm:entry-point")
+bool _isSubtype(Object? s, _Type t) {
+ return _typeUniverse.isSubtype(unsafeCast<_Type>(s.runtimeType), t);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 9d184e4..92cff59 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 18
PATCH 0
-PRERELEASE 73
+PRERELEASE 74
PRERELEASE_PATCH 0
\ No newline at end of file