blob: f775a22ad2344f9685debba4a9ca27f9d7690cd8 [file] [log] [blame]
// Copyright (c) 2022, 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.
//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 _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 _InterfaceType(this.classId, this.declaredNullable,
[this.typeArguments = const []]);
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 = 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("Interface");
s.write(classId);
if (typeArguments.isNotEmpty) {
s.write("<");
for (int i = 0; i < typeArguments.length; i++) {
if (i > 0) s.write(",");
s.write(typeArguments[i]);
}
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);
}