| // 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. |
| // TODO(joshualitt): We can cache the results of a number of functions in this |
| // file: |
| // * [_Type.asNonNullable] |
| // * [_FutureOrType.asFuture]. |
| abstract class _Type implements Type { |
| final bool isNullable; |
| |
| const _Type(this.isNullable); |
| |
| 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 isNull => _testID(ClassID.cidNullType); |
| bool get isFutureOr => _testID(ClassID.cidFutureOrType); |
| bool get isInterface => _testID(ClassID.cidInterfaceType); |
| bool get isFunction => _testID(ClassID.cidFunctionType); |
| bool get isGenericFunction => _testID(ClassID.cidGenericFunctionType); |
| |
| T as<T>() => unsafeCast<T>(this); |
| |
| _Type get asNonNullable; |
| |
| @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 { |
| const _NeverType() : super(false); |
| |
| @override |
| _Type get asNonNullable => this; |
| |
| @override |
| String toString() => 'Never'; |
| } |
| |
| @pragma("wasm:entry-point") |
| class _DynamicType extends _Type { |
| const _DynamicType() : super(true); |
| |
| @override |
| _Type get asNonNullable => throw '`dynamic` type is always nullable.'; |
| |
| @override |
| String toString() => 'dynamic'; |
| } |
| |
| @pragma("wasm:entry-point") |
| class _VoidType extends _Type { |
| const _VoidType() : super(true); |
| |
| @override |
| _Type get asNonNullable => throw '`void` type is always nullable.'; |
| |
| @override |
| String toString() => 'void'; |
| } |
| |
| @pragma("wasm:entry-point") |
| class _NullType extends _Type { |
| const _NullType() : super(true); |
| |
| @override |
| _Type get asNonNullable => const _NeverType(); |
| |
| @override |
| String toString() => 'Null'; |
| } |
| |
| @pragma("wasm:entry-point") |
| class _FutureOrType extends _Type { |
| final _Type typeArgument; |
| |
| @pragma("wasm:entry-point") |
| const _FutureOrType(bool isNullable, this.typeArgument) : super(isNullable); |
| |
| _InterfaceType get asFuture => |
| _InterfaceType(ClassID.cidFuture, isNullable, [typeArgument]); |
| |
| @override |
| _Type get asNonNullable { |
| if (!isNullable) return this; |
| if (!typeArgument.isNullable) return _FutureOrType(false, typeArgument); |
| throw '`$this` cannot be non nullable.'; |
| } |
| |
| @override |
| bool operator ==(Object o) { |
| if (!(super == o)) return false; |
| _FutureOrType other = unsafeCast<_FutureOrType>(o); |
| if (isNullable != other.isNullable) return false; |
| return typeArgument == other.typeArgument; |
| } |
| |
| @override |
| int get hashCode { |
| int hash = super.hashCode; |
| hash = mix64(hash ^ (isNullable ? 1 : 0)); |
| return mix64(hash ^ typeArgument.hashCode); |
| } |
| |
| @override |
| String toString() { |
| StringBuffer s = StringBuffer(); |
| s.write("FutureOr"); |
| s.write("<"); |
| s.write(typeArgument); |
| s.write(">"); |
| if (isNullable) s.write("?"); |
| return s.toString(); |
| } |
| } |
| |
| class _InterfaceType extends _Type { |
| final int classId; |
| final List<_Type> typeArguments; |
| |
| @pragma("wasm:entry-point") |
| const _InterfaceType(this.classId, bool isNullable, |
| [this.typeArguments = const []]) |
| : super(isNullable); |
| |
| @override |
| _Type get asNonNullable => _InterfaceType(classId, false, typeArguments); |
| |
| @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(); |
| } |
| } |
| |
| // TODO(joshualitt): Implement. |
| @pragma("wasm:entry-point") |
| class _FunctionType extends _Type { |
| const _FunctionType(bool isNullable) : super(isNullable); |
| |
| @override |
| _Type get asNonNullable => throw 'unimplemented'; |
| |
| @override |
| String toString() => 'FunctionType'; |
| } |
| |
| // TODO(joshualitt): Implement. |
| @pragma("wasm:entry-point") |
| class _GenericFunctionType extends _FunctionType { |
| const _GenericFunctionType(bool isNullable) : super(isNullable); |
| |
| @override |
| _Type get asNonNullable => throw 'unimplemented'; |
| |
| @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) => isObjectType(t) && t.isNullable; |
| |
| bool isObjectType(_Type t) { |
| if (!t.isInterface) return false; |
| _InterfaceType type = t.as<_InterfaceType>(); |
| return type.classId == ClassID.cidObject; |
| } |
| |
| 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; |
| |
| // Left Type Variable Bound 1: |
| // TODO(joshualitt): Implement. |
| |
| // Left Null: |
| // TODO(joshualitt): Combine with 'Right Null', and this can just be: |
| // `if (s.isNullable && !t.isNullable) return false` |
| if (s.isNull) { |
| return t.isNullable; |
| } |
| |
| // Right Object: |
| if (isObjectType(t)) { |
| return !s.isNullable; |
| } |
| |
| // Left FutureOr: |
| if (s.isFutureOr) { |
| _FutureOrType sFutureOr = s.as<_FutureOrType>(); |
| if (!isSubtype(sFutureOr.typeArgument, t)) { |
| return false; |
| } |
| return _isSubtype(sFutureOr.asFuture, t); |
| } |
| |
| // Left Nullable: |
| if (s.isNullable) { |
| return t.isNullable && isSubtype(s.asNonNullable, t); |
| } |
| |
| // Type Variable Reflexivity 1 is subsumed by Reflexivity and therefore |
| // elided. |
| // Type Variable Reflexivity 2 does not apply at runtime. |
| // Right Promoted Variable does not apply at runtime. |
| |
| // Right FutureOr: |
| if (t.isFutureOr) { |
| _FutureOrType tFutureOr = t.as<_FutureOrType>(); |
| if (isSubtype(s, tFutureOr.typeArgument)) { |
| return true; |
| } |
| return isSubtype(s, tFutureOr.asFuture); |
| } |
| |
| // Right Nullable: |
| if (t.isNullable) { |
| return isSubtype(s, const _NullType()) || isSubtype(s, t.asNonNullable); |
| } |
| |
| // Left Promoted Variable does not apply at runtime. |
| |
| // Left Type Variable Bound 2: |
| // TODO(joshualitt): Implement case. |
| |
| // Function Type / Function: |
| // TODO(joshualitt): Implement case. |
| |
| // Positional Function Types + Named Function Types: |
| // TODO(joshualitt): Implement case. |
| |
| // 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); |
| } |