[dart2wasm] Build type rules for interface type arguments.

Change-Id: I4017d14ff9b32b2a556592f5074cdd3401c7d961
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243960
Reviewed-by: Aske Simon Christensen <askesc@google.com>
Commit-Queue: Joshua Litt <joshualitt@google.com>
This commit is contained in:
Joshua Litt 2022-06-16 18:50:59 +00:00 committed by Commit Bot
parent d35f5c1ac1
commit 05dfe9047d
7 changed files with 403 additions and 94 deletions

View file

@ -422,8 +422,8 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
StringLiteral(expected),
translator
.translateType(translator.coreTypes.stringNonNullableRawType));
_call(translator.stackTraceCurrent.reference);
_call(translator.throwWasmRefError.reference);
call(translator.stackTraceCurrent.reference);
call(translator.throwWasmRefError.reference);
b.unreachable();
}
@ -436,7 +436,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
return expectedType;
}
w.ValueType _call(Reference target) {
w.ValueType call(Reference target) {
w.BaseFunction targetFunction = translator.functions.getFunction(target);
if (translator.shouldInline(target)) {
List<w.Local> inlinedLocals =
@ -492,7 +492,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
this, TypeParameterType(typeParam, Nullability.nonNullable));
}
_visitArguments(node.arguments, node.targetReference, 1);
_call(node.targetReference);
call(node.targetReference);
}
@override
@ -511,7 +511,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
}
_visitArguments(node.arguments, node.targetReference,
1 + supertype.typeArguments.length);
_call(node.targetReference);
call(node.targetReference);
}
@override
@ -910,7 +910,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
nonNullableType =
translator.classInfo[translator.stringBaseClass]!.nonNullableType;
nullableType = nonNullableType.withNullability(true);
compare = () => _call(translator.stringEquals.reference);
compare = () => call(translator.stringEquals.reference);
} else {
// Object switch
assert(check<InvalidExpression, InstanceConstant>());
@ -1108,7 +1108,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
b.ref_as_non_null();
}
_visitArguments(node.arguments, node.targetReference, 1);
_call(node.targetReference);
call(node.targetReference);
if (expectedType != voidMarker) {
b.local_get(temp);
return temp.type;
@ -1124,7 +1124,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
if (intrinsicResult != null) return intrinsicResult;
_visitArguments(node.arguments, node.targetReference, 0);
return _call(node.targetReference);
return call(node.targetReference);
}
Member _lookupSuperTarget(Member interfaceTarget, {required bool setter}) {
@ -1143,7 +1143,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
w.ValueType thisType = visitThis(receiverType);
translator.convertType(function, thisType, receiverType);
_visitArguments(node.arguments, target, 1);
return _call(target);
return call(target);
}
@override
@ -1182,7 +1182,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
translator.functions.getFunction(singleTarget.reference);
wrap(node.receiver, targetFunction.type.inputs.first);
_visitArguments(node.arguments, node.interfaceTargetReference, 1);
return _call(singleTarget.reference);
return call(singleTarget.reference);
}
return _virtualCall(node, target,
(signature) => wrap(node.receiver, signature.inputs.first), (_) {
@ -1258,7 +1258,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
if (singleTarget != null) {
left();
right();
_call(singleTarget.reference);
call(singleTarget.reference);
} else {
_virtualCall(node, node.interfaceTarget, left, right,
getter: false, setter: false);
@ -1307,7 +1307,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
assert(selector.targetCount <= 1);
if (selector.targetCount == 1) {
pushArguments(selector.signature);
return _call(selector.singularTarget!);
return call(selector.singularTarget!);
} else {
b.comment("Virtual call of ${selector.name} with no targets"
" at ${node.location}");
@ -1367,7 +1367,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
b.i32_const(id);
b.i32_eq();
b.if_(selector.signature.inputs, selector.signature.inputs);
_call(target);
call(target);
b.br(block);
b.end();
implementations.remove(id);
@ -1387,7 +1387,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
b.i32_const(pivotId);
b.i32_lt_u();
b.if_(selector.signature.inputs, selector.signature.inputs);
_call(target);
call(target);
b.br(block);
b.end();
for (int id in sorted) {
@ -1398,7 +1398,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
}
// Call remaining implementation.
Reference target = implementations.values.first;
_call(target);
call(target);
b.end();
}
@ -1468,7 +1468,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
if (target is Field) {
return translator.globals.readGlobal(b, target);
} else {
return _call(target.reference);
return call(target.reference);
}
}
@ -1502,7 +1502,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
temp = addLocal(translateType(dartTypeOf(node.value)));
b.local_tee(temp);
}
_call(target.reference);
call(target.reference);
if (preserved) {
b.local_get(temp!);
return temp.type;
@ -1622,7 +1622,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
w.BaseFunction targetFunction =
translator.functions.getFunction(target.reference);
wrap(receiver, targetFunction.type.inputs.single);
return _call(target.reference);
return call(target.reference);
}
}
@ -1688,7 +1688,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
b.local_tee(temp);
translator.convertType(function, temp.type, paramType);
}
_call(target.reference);
call(target.reference);
}
if (preserved) {
b.local_get(temp!);
@ -1853,8 +1853,8 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
// We lower a null check to a br_on_non_null, throwing a [TypeError] in the
// null case.
b.br_on_non_null(nullCheckBlock);
_call(translator.stackTraceCurrent.reference);
_call(translator.throwNullCheckError.reference);
call(translator.stackTraceCurrent.reference);
call(translator.throwNullCheckError.reference);
b.unreachable();
b.end();
return nonNullOperandType;
@ -1905,13 +1905,13 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
StringConcatenation node, w.ValueType expectedType) {
makeList(node.expressions, translator.fixedLengthListClass,
InterfaceType(translator.stringBaseClass, Nullability.nonNullable));
return _call(translator.stringInterpolate.reference);
return call(translator.stringInterpolate.reference);
}
@override
w.ValueType visitThrow(Throw node, w.ValueType expectedType) {
wrap(node.expression, translator.topInfo.nonNullableType);
_call(translator.stackTraceCurrent.reference);
call(translator.stackTraceCurrent.reference);
// At this point, we have the exception and the current stack trace on the
// stack, so just throw them using the exception tag.
@ -2101,8 +2101,8 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
b.br_if(asCheckBlock);
b.local_get(operand);
types.makeType(this, node.type);
_call(translator.stackTraceCurrent.reference);
_call(translator.throwAsCheckError.reference);
call(translator.stackTraceCurrent.reference);
call(translator.throwAsCheckError.reference);
b.unreachable();
b.end();
b.local_get(operand);

View file

@ -775,7 +775,6 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?> {
@override
ConstantInfo? visitTypeLiteralConstant(TypeLiteralConstant constant) {
DartType type = constant.type;
assert(type is! TypeParameterType);
ClassInfo info = translator.classInfo[types.classForType(type)]!;
translator.functions.allocateClass(info.classId);
@ -796,6 +795,18 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?> {
} else {
return _makeFunctionType(constant, type, info);
}
} else if (type is TypeParameterType) {
// TODO(joshualitt): Handle generic function types.
assert(!types.isGenericFunctionTypeParameter(type));
int environmentIndex =
types.interfaceTypeEnvironment.lookup(type.parameter);
return createConstant(constant, info.nonNullableType, (function, b) {
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
types.encodeNullability(b, type);
b.i32_const(environmentIndex);
translator.struct_new(b, info);
});
} else {
assert(type is VoidType ||
type is NeverType ||

View file

@ -639,8 +639,10 @@ class Intrinsifier {
codeGen.wrap(stackTrace, stackTraceType);
b.throw_(translator.exceptionTag);
return codeGen.voidMarker;
case "_getSubtypeMap":
return translator.types.makeSubtypeMap(b);
case "_getTypeRulesSupers":
return translator.types.makeTypeRulesSupers(b);
case "_getTypeRulesSubstitutions":
return translator.types.makeTypeRulesSubstitutions(b);
}
}

View file

@ -97,6 +97,8 @@ class Translator {
late final Class interfaceTypeClass;
late final Class functionTypeClass;
late final Class genericFunctionTypeClass;
late final Class interfaceTypeParameterTypeClass;
late final Class genericFunctionTypeParameterTypeClass;
late final Class namedParameterClass;
late final Class stackTraceClass;
late final Class ffiCompoundClass;
@ -119,7 +121,6 @@ class Translator {
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;
@ -213,6 +214,9 @@ class Translator {
interfaceTypeClass = lookupCore("_InterfaceType");
functionTypeClass = lookupCore("_FunctionType");
genericFunctionTypeClass = lookupCore("_GenericFunctionType");
interfaceTypeParameterTypeClass = lookupCore("_InterfaceTypeParameterType");
genericFunctionTypeParameterTypeClass =
lookupCore("_GenericFunctionTypeParameterType");
namedParameterClass = lookupCore("_NamedParameter");
stackTraceClass = lookupCore("StackTrace");
typeUniverseClass = lookupCore("_TypeUniverse");

View file

@ -7,9 +7,27 @@ import 'package:dart2wasm/code_generator.dart';
import 'package:dart2wasm/translator.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'package:wasm_builder/wasm_builder.dart' as w;
class InterfaceTypeEnvironment {
final Map<TypeParameter, int> typeOffsets = {};
void _add(InterfaceType type) {
Class cls = type.classNode;
if (typeOffsets.containsKey(cls)) {
return;
}
int i = 0;
for (TypeParameter typeParameter in cls.typeParameters) {
typeOffsets[typeParameter] = i++;
}
}
int lookup(TypeParameter typeParameter) => typeOffsets[typeParameter]!;
}
/// Helper class for building runtime types.
class Types {
final Translator translator;
@ -19,6 +37,32 @@ class Types {
late final w.ValueType namedParametersExpectedType = classAndFieldToType(
translator.functionTypeClass, FieldIndex.functionTypeNamedParameters);
/// A mapping from concrete subclass `classID` to [Map]s of superclass
/// `classID` and the necessary substitutions which must be performed to test
/// for a valid subtyping relationship.
late final Map<int, Map<int, List<DartType>>> typeRules = _buildTypeRules();
/// We will build the [interfaceTypeEnvironment] when building the
/// [typeRules].
final InterfaceTypeEnvironment interfaceTypeEnvironment =
InterfaceTypeEnvironment();
/// Because we can't currently support [Map]s in our `TypeUniverse`, we have
/// to decompose [typeRules] into two [Map]s based on [List]s.
///
/// [typeRulesSupers] is a [List] where the index in the list is a subclasses'
/// `classID` and the value at that index is a [List] of superclass
/// `classID`s.
late final List<List<int>> typeRulesSupers = _buildTypeRulesSupers();
/// [typeRulesSubstitutions] is a [List] where the index in the list is a
/// subclasses' `classID` and the value at that index is a [List] indexed by
/// the index of the superclasses' `classID` in [typeRulesSuper] and the value
/// at that index is a [List] of [DartType]s which must be substituted for the
/// subtyping relationship to be valid.
late final List<List<List<DartType>>> typeRulesSubstitutions =
_buildTypeRulesSubstitutions();
Types(this.translator);
w.ValueType classAndFieldToType(Class cls, int fieldIndex) =>
@ -34,46 +78,135 @@ class Types {
InterfaceType get namedParameterType =>
InterfaceType(translator.namedParameterClass, Nullability.nonNullable);
/// 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();
CoreTypes get coreTypes => translator.coreTypes;
if (classIds.isEmpty) continue;
subtypeMap[classInfo.classId] = classIds;
/// Builds a [Map<int, Map<int, List<DartType>>>] to store subtype
/// information. The first key is the class id of a subtype. This returns a
/// map where each key is the class id of a transitively implemented super
/// type and each value is a list of the necessary type substitutions required
/// for the subtyping relationship to be valid.
Map<int, Map<int, List<DartType>>> _buildTypeRules() {
List<ClassInfo> classes = translator.classes;
Map<int, Map<int, List<DartType>>> subtypeMap = {};
for (ClassInfo classInfo in classes) {
ClassInfo superclassInfo = classInfo;
// We don't need type rules for any class without a superclass, or for
// classes whose supertype is [Object]. The latter case will be handled
// directly in the subtype checking algorithm.
if (superclassInfo.cls == null ||
superclassInfo.cls == coreTypes.objectClass) continue;
Class superclass = superclassInfo.cls!;
Iterable<Class> subclasses =
_getConcreteSubtypes(superclass).where((cls) => cls != superclass);
Iterable<InterfaceType> subtypes = subclasses.map(
(Class cls) => cls.getThisType(coreTypes, Nullability.nonNullable));
for (InterfaceType subtype in subtypes) {
interfaceTypeEnvironment._add(subtype);
List<DartType>? typeArguments = translator.hierarchy
.getTypeArgumentsAsInstanceOf(subtype, superclass);
ClassInfo subclassInfo = translator.classInfo[subtype.classNode]!;
Map<int, List<DartType>> substitutionMap =
subtypeMap[subclassInfo.classId] ??= {};
substitutionMap[superclassInfo.classId] = typeArguments ?? const [];
}
}
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);
List<List<int>> _buildTypeRulesSupers() {
List<List<int>> typeRulesSupers = [];
for (int i = 0; i < translator.classInfoCollector.nextClassId; i++) {
List<int>? superclassIds = typeRules[i]?.keys.toList();
if (superclassIds == null) {
typeRulesSupers.add(const []);
} else {
superclassIds.sort();
typeRulesSupers.add(superclassIds);
}
}
return typeRulesSupers;
}
List<List<List<DartType>>> _buildTypeRulesSubstitutions() {
List<List<List<DartType>>> typeRulesSubstitutions = [];
for (int i = 0; i < translator.classInfoCollector.nextClassId; i++) {
List<int> supers = typeRulesSupers[i];
typeRulesSubstitutions.add(supers.isEmpty ? const [] : []);
for (int j = 0; j < supers.length; j++) {
int superId = supers[j];
typeRulesSubstitutions.last.add(typeRules[i]![superId]!);
}
}
return typeRulesSubstitutions;
}
/// Builds a map of subclasses to the transitive set of superclasses they
/// implement.
/// TODO(joshualitt): This implementation is just temporary. Eventually we
/// should move to a data structure more closely resembling [typeRules].
w.ValueType makeTypeRulesSupers(w.Instructions b) {
w.ValueType expectedType =
translator.classInfo[translator.immutableListClass]!.nonNullableType;
DartType listIntType = InterfaceType(translator.immutableListClass,
Nullability.nonNullable, [translator.coreTypes.intNonNullableRawType]);
List<ListConstant> listIntConstant = [];
for (List<int> supers in typeRulesSupers) {
listIntConstant.add(ListConstant(
listIntType, supers.map((i) => IntConstant(i)).toList()));
}
DartType listListIntType = InterfaceType(
translator.immutableListClass, Nullability.nonNullable, [listIntType]);
translator.constants.instantiateConstant(
null, b, ListConstant(listListIntType, listIntConstant), expectedType);
return expectedType;
}
/// Similar to the above, but provides the substitutions required for each
/// supertype.
/// TODO(joshualitt): Like [makeTypeRulesSupers], this is just temporary.
w.ValueType makeTypeRulesSubstitutions(w.Instructions b) {
w.ValueType expectedType =
translator.classInfo[translator.immutableListClass]!.nonNullableType;
DartType listTypeType = InterfaceType(
translator.immutableListClass,
Nullability.nonNullable,
[translator.typeClass.getThisType(coreTypes, Nullability.nonNullable)]);
DartType listListTypeType = InterfaceType(
translator.immutableListClass, Nullability.nonNullable, [listTypeType]);
DartType listListListTypeType = InterfaceType(translator.immutableListClass,
Nullability.nonNullable, [listListTypeType]);
List<ListConstant> substitutionsConstantL0 = [];
for (List<List<DartType>> substitutionsL1 in typeRulesSubstitutions) {
List<ListConstant> substitutionsConstantL1 = [];
for (List<DartType> substitutionsL2 in substitutionsL1) {
substitutionsConstantL1.add(ListConstant(
listTypeType,
substitutionsL2.map((t) {
// TODO(joshualitt): implement generic functions
if (t is FunctionType && isGenericFunction(t)) {
return TypeLiteralConstant(DynamicType());
} else {
return TypeLiteralConstant(t);
}
}).toList()));
}
substitutionsConstantL0
.add(ListConstant(listListTypeType, substitutionsConstantL1));
}
translator.constants.instantiateConstant(
null,
b,
ListConstant(listListListTypeType, substitutionsConstantL0),
expectedType);
return expectedType;
}
bool isGenericFunction(FunctionType type) => type.typeParameters.isNotEmpty;
bool isGenericFunctionTypeParameter(TypeParameterType type) =>
type.parameter.parent == null;
bool _isTypeConstant(DartType type) {
return type is DynamicType ||
type is VoidType ||
@ -113,6 +246,12 @@ class Types {
} else {
return translator.functionTypeClass;
}
} else if (type is TypeParameterType) {
if (isGenericFunctionTypeParameter(type)) {
return translator.genericFunctionTypeParameterTypeClass;
} else {
return translator.interfaceTypeParameterTypeClass;
}
}
throw "Unexpected DartType: $type";
}
@ -251,7 +390,9 @@ class Types {
TreeNode node) {
w.Instructions b = codeGen.b;
if (type is! InterfaceType) {
// TODO(askesc): Implement type test for remaining types
// TODO(joshualitt): We can enable this after fixing `.runtimeType`.
// makeType(codeGen, type);
// codeGen.call(translator.isSubtype.reference);
print("Not implemented: Type test with non-interface type $type"
" at ${node.location}");
b.drop();
@ -284,7 +425,7 @@ class Types {
}
}
List<Class> concrete = _getConcreteSubtypes(type.classNode).toList();
if (type.classNode == translator.coreTypes.functionClass) {
if (type.classNode == coreTypes.functionClass) {
ClassInfo functionInfo = translator.classInfo[translator.functionClass]!;
translator.ref_test(b, functionInfo);
} else if (concrete.isEmpty) {

View file

@ -38,6 +38,10 @@ class ClassID {
external static int get cidFunctionType;
@pragma("wasm:class-id", "dart.core#_GenericFunctionType")
external static int get cidGenericFunctionType;
@pragma("wasm:class-id", "dart.core#_GenericFunctionTypeParameterType")
external static int get cidGenericFunctionTypeParameterType;
@pragma("wasm:class-id", "dart.core#_InterfaceTypeParameterType")
external static int get cidInterfaceTypeParameterType;
// Dummy, only used by VM-specific hash table code.
static final int numPredefinedCids = 1;

View file

@ -26,14 +26,18 @@ abstract class _Type implements Type {
bool get isNull => _testID(ClassID.cidNullType);
bool get isFutureOr => _testID(ClassID.cidFutureOrType);
bool get isInterface => _testID(ClassID.cidInterfaceType);
bool get isInterfaceTypeParameterType =>
_testID(ClassID.cidInterfaceTypeParameterType);
bool get isFunction => _testID(ClassID.cidFunctionType);
bool get isGenericFunction => _testID(ClassID.cidGenericFunctionType);
T as<T>() => unsafeCast<T>(this);
_Type get asNonNullable => isNullable ? _asNonNullable : this;
_Type get asNullable => isNullable ? this : _asNullable;
_Type get _asNonNullable;
_Type get _asNullable;
@override
bool operator ==(Object other) => ClassID.getID(this) == ClassID.getID(other);
@ -49,6 +53,10 @@ class _NeverType extends _Type {
@override
_Type get _asNonNullable => this;
/// Never? normalizes to Null.
@override
_Type get _asNullable => const _NullType();
@override
String toString() => 'Never';
}
@ -60,6 +68,9 @@ class _DynamicType extends _Type {
@override
_Type get _asNonNullable => throw '`dynamic` type is always nullable.';
@override
_Type get _asNullable => this;
@override
String toString() => 'dynamic';
}
@ -71,6 +82,9 @@ class _VoidType extends _Type {
@override
_Type get _asNonNullable => throw '`void` type is always nullable.';
@override
_Type get _asNullable => this;
@override
String toString() => 'void';
}
@ -82,10 +96,53 @@ class _NullType extends _Type {
@override
_Type get _asNonNullable => const _NeverType();
@override
_Type get _asNullable => this;
@override
String toString() => 'Null';
}
/// Because Interface type parameters are fundamentally different from Generic
/// function type parameters, we are keeping these classes separate for the time
/// being.
@pragma("wasm:entry-point")
class _InterfaceTypeParameterType extends _Type {
final int environmentIndex;
const _InterfaceTypeParameterType(super.isNullable, this.environmentIndex);
@override
_Type get _asNonNullable =>
throw 'Type parameter should have been substituted already.';
@override
_Type get _asNullable =>
throw 'Type parameter should have been substituted already.';
@override
String toString() => '$environmentIndex';
}
@pragma("wasm:entry-point")
class _GenericFunctionTypeParameterType extends _Type {
final int environmentIndex;
const _GenericFunctionTypeParameterType(
super.isNullable, this.environmentIndex);
@override
_Type get _asNonNullable =>
throw 'Type parameter should have been substituted already..';
@override
_Type get _asNullable =>
throw 'Type parameter should have been substituted already.';
@override
String toString() => '$environmentIndex';
}
@pragma("wasm:entry-point")
class _FutureOrType extends _Type {
final _Type typeArgument;
@ -102,6 +159,9 @@ class _FutureOrType extends _Type {
throw '`$this` cannot be non nullable.';
}
@override
_Type get _asNullable => _FutureOrType(true, typeArgument);
@override
bool operator ==(Object o) {
if (!(super == o)) return false;
@ -141,6 +201,9 @@ class _InterfaceType extends _Type {
@override
_Type get _asNonNullable => _InterfaceType(classId, false, typeArguments);
@override
_Type get _asNullable => _InterfaceType(classId, true, typeArguments);
@override
bool operator ==(Object o) {
if (!(super == o)) return false;
@ -234,6 +297,10 @@ class _FunctionType extends _Type {
_Type get _asNonNullable => _FunctionType(returnType, positionalParameters,
requiredParameterCount, namedParameters, false);
@override
_Type get _asNullable => _FunctionType(returnType, positionalParameters,
requiredParameterCount, namedParameters, true);
bool operator ==(Object o) {
if (!(super == o)) return false;
_FunctionType other = unsafeCast<_FunctionType>(o);
@ -304,20 +371,64 @@ class _GenericFunctionType extends _Type {
@override
_Type get _asNonNullable => throw 'unimplemented';
@override
_Type get _asNullable => throw 'unimplemented';
@override
String toString() => 'GenericFunctionType';
}
external Map<int, List<int>> _getSubtypeMap();
external List<List<int>> _getTypeRulesSupers();
external List<List<List<_Type>>> _getTypeRulesSubstitutions();
class _Environment {
List<List<_Type>> scopes = [];
_Environment();
factory _Environment.from(List<_Type> initialScope) {
final env = _Environment();
env.push(initialScope);
return env;
}
void push(List<_Type> scope) => scopes.add(scope);
void pop() => scopes.removeLast();
_Type _substituteTypeParameter(bool declaredNullable, _Type type) {
// If the type parameter is non-nullable, or the substitution type is
// nullable, then just return the substitution type. Otherwise, we return
// [type] as nullable.
// Note: This will throw if the required nullability is impossible to
// generate.
if (!declaredNullable || type.isNullable) {
return type;
}
return type.asNullable;
}
_Type lookup(_InterfaceTypeParameterType typeParameter) {
// Lookup `InterfaceType` parameters in the top environment.
// TODO(joshualitt): When we implement generic functions be sure to keep the
// environments distinct.
return _substituteTypeParameter(
typeParameter.isNullable, scopes.last[typeParameter.environmentIndex]);
}
}
class _TypeUniverse {
/// 'Map' of classId to range of subclasses.
final Map<int, List<int>> _subtypeMap;
/// 'Map' of classId to the transitive set of super classes it implements.
final List<List<int>> typeRulesSupers;
const _TypeUniverse._(this._subtypeMap);
/// 'Map' of classId, and super offset(from [typeRulesSupers]) to a list of
/// type substitutions.
final List<List<List<_Type>>> typeRulesSubstitutions;
const _TypeUniverse._(this.typeRulesSupers, this.typeRulesSubstitutions);
factory _TypeUniverse.create() {
return _TypeUniverse._(_getSubtypeMap());
return _TypeUniverse._(_getTypeRulesSupers(), _getTypeRulesSubstitutions());
}
bool isSpecificInterfaceType(_Type t, int classId) {
@ -341,27 +452,55 @@ class _TypeUniverse {
bool isFunctionType(_Type t) =>
isSpecificInterfaceType(t, ClassID.cidFunction);
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;
}
bool areTypeArgumentsSubtypes(List<_Type> sArgs, _Environment? sEnv,
List<_Type> tArgs, _Environment? tEnv) {
assert(sArgs.length == tArgs.length);
for (int i = 0; i < sArgs.length; i++) {
if (!isSubtype(sArgs[i], sEnv, tArgs[i], tEnv)) {
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;
}
bool isFunctionSubtype(_FunctionType s, _FunctionType t) {
if (!isSubtype(s.returnType, t.returnType)) return false;
bool isInterfaceSubtype(_InterfaceType s, _Environment? sEnv,
_InterfaceType t, _Environment? tEnv) {
int sId = s.classId;
int tId = t.classId;
// If we have the same class, simply compare type arguments.
if (sId == tId) {
return areTypeArgumentsSubtypes(
s.typeArguments, sEnv, t.typeArguments, tEnv);
}
// Otherwise, check if [s] is a subtype of [t], and if it is then compare
// [s]'s type substitutions with [t]'s type arguments.
List<int> sSupers = typeRulesSupers[sId];
if (sSupers.isEmpty) return false;
int sSuperIndexOfT = sSupers.indexOf(tId);
if (sSuperIndexOfT == -1) return false;
assert(sSuperIndexOfT < typeRulesSubstitutions[sId].length);
List<_Type> substitutions = typeRulesSubstitutions[sId][sSuperIndexOfT];
// If [sEnv] is null, then create a new environment. Otherwise, we are doing
// a recursive type check, so extend the existing environment with [s]'s
// type arguments.
if (sEnv == null) {
sEnv = _Environment.from(s.typeArguments);
} else {
sEnv.push(s.typeArguments);
}
bool result =
areTypeArgumentsSubtypes(substitutions, sEnv, t.typeArguments, tEnv);
sEnv.pop();
return result;
}
bool isFunctionSubtype(_FunctionType s, _Environment? sEnv, _FunctionType t,
_Environment? tEnv) {
if (!isSubtype(s.returnType, sEnv, t.returnType, tEnv)) return false;
// Check [s] does not have more required positional arguments than [t].
int sRequiredCount = s.requiredParameterCount;
@ -385,7 +524,7 @@ class _TypeUniverse {
for (int i = 0; i < tPositionalLength; i++) {
_Type sParameter = sPositional[i];
_Type tParameter = tPositional[i];
if (!isSubtype(tParameter, sParameter)) {
if (!isSubtype(tParameter, tEnv, sParameter, sEnv)) {
return false;
}
}
@ -414,7 +553,8 @@ class _TypeUniverse {
}
bool tIsRequired = tNamedParameter.isRequired;
if (sIsRequired && !tIsRequired) return false;
if (!isSubtype(tNamedParameter.type, sNamedParameter.type)) {
if (!isSubtype(
tNamedParameter.type, tEnv, sNamedParameter.type, sEnv)) {
return false;
}
break;
@ -428,7 +568,7 @@ class _TypeUniverse {
// 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) {
bool isSubtype(_Type s, _Environment? sEnv, _Type t, _Environment? tEnv) {
// Reflexivity:
if (identical(s, t)) return true;
@ -442,7 +582,11 @@ class _TypeUniverse {
if (isBottomType(s)) return true;
// Left Type Variable Bound 1:
// TODO(joshualitt): Implement.
// TODO(joshualitt): Implement for generic function type parameters.
if (s.isInterfaceTypeParameterType) {
return isSubtype(
sEnv!.lookup(s.as<_InterfaceTypeParameterType>()), sEnv, t, tEnv);
}
// Left Null:
// TODO(joshualitt): Combine with 'Right Null', and this can just be:
@ -459,7 +603,7 @@ class _TypeUniverse {
// Left FutureOr:
if (s.isFutureOr) {
_FutureOrType sFutureOr = s.as<_FutureOrType>();
if (!isSubtype(sFutureOr.typeArgument, t)) {
if (!isSubtype(sFutureOr.typeArgument, sEnv, t, tEnv)) {
return false;
}
return _isSubtype(sFutureOr.asFuture, t);
@ -467,7 +611,7 @@ class _TypeUniverse {
// Left Nullable:
if (s.isNullable) {
return t.isNullable && isSubtype(s.asNonNullable, t);
return t.isNullable && isSubtype(s.asNonNullable, sEnv, t, tEnv);
}
// Type Variable Reflexivity 1 is subsumed by Reflexivity and therefore
@ -478,15 +622,15 @@ class _TypeUniverse {
// Right FutureOr:
if (t.isFutureOr) {
_FutureOrType tFutureOr = t.as<_FutureOrType>();
if (isSubtype(s, tFutureOr.typeArgument)) {
if (isSubtype(s, sEnv, tFutureOr.typeArgument, tEnv)) {
return true;
}
return isSubtype(s, tFutureOr.asFuture);
return isSubtype(s, sEnv, tFutureOr.asFuture, tEnv);
}
// Right Nullable:
if (t.isNullable) {
return isSubtype(s, t.asNonNullable);
return isSubtype(s, sEnv, t.asNonNullable, tEnv);
}
// Left Promoted Variable does not apply at runtime.
@ -506,13 +650,15 @@ class _TypeUniverse {
}
if (s.isFunction && t.isFunction) {
return isFunctionSubtype(s.as<_FunctionType>(), t.as<_FunctionType>());
return isFunctionSubtype(
s.as<_FunctionType>(), sEnv, t.as<_FunctionType>(), tEnv);
}
// Interface Compositionality + Super-Interface:
if (s.isInterface &&
t.isInterface &&
isInterfaceSubtype(s.as<_InterfaceType>(), t.as<_InterfaceType>())) {
isInterfaceSubtype(
s.as<_InterfaceType>(), sEnv, t.as<_InterfaceType>(), tEnv)) {
return true;
}
return false;
@ -523,5 +669,6 @@ _TypeUniverse _typeUniverse = _TypeUniverse.create();
@pragma("wasm:entry-point")
bool _isSubtype(Object? s, _Type t) {
return _typeUniverse.isSubtype(unsafeCast<_Type>(s.runtimeType), t);
return _typeUniverse.isSubtype(
unsafeCast<_Type>(s.runtimeType), null, t, null);
}