blob: 6250aa0e7bb879232276946f586f40e0ad84db3d [file] [log] [blame]
// Copyright (c) 2013, 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.
/// This part contains helpers for supporting runtime type information.
///
/// The helper use a mixture of Dart and JavaScript objects. To indicate which
/// is used where we adopt the scheme of using explicit type annotation for Dart
/// objects and 'var' or omitted return type for JavaScript objects.
///
/// Since bool, int, and String values are represented by the same JavaScript
/// primitives, type annotations are used for these types in all cases.
///
/// Several methods use a common JavaScript encoding of runtime type
/// information. This encoding is referred to as the type representation which
/// is one of these:
/// 1) a JavaScript constructor for a class C: the represented type is the raw
/// type C.
/// 2) a JavaScript array: the first entry is of type 1 and contains the
/// subtyping flags and the substitution of the type and the rest of the
/// array are the type arguments.
/// 3) `null`: the dynamic type.
/// 4) a JavaScript object representing the function type. For instance, it has
/// the form {ret: rti, args: [rti], opt: [rti], named: {name: rti}} for a
/// function with a return type, regular, optional and named arguments.
/// Generic function types have a 'bounds' property.
///
/// To check subtype relations between generic classes we use a JavaScript
/// expression that describes the necessary substitution for type arguments.
/// Such a substitution expression can be:
/// 1) `null`, if no substituted check is necessary, because the
/// type variables are the same or there are no type variables in the class
/// that is checked for.
/// 2) A list expression describing the type arguments to be used in the
/// subtype check, if the type arguments to be used in the check do not
/// depend on the type arguments of the object.
/// 3) A function mapping the type variables of the object to be checked to a
/// list expression. The function may also return null, which is equivalent
/// to an array containing only null values.
part of _js_helper;
/// Called from generated code.
Type createRuntimeType(rti) {
return new TypeImpl(rti);
}
class TypeImpl implements Type {
final dynamic _rti;
String __typeName;
String _unmangledName;
int _hashCode;
TypeImpl(this._rti);
String get _typeName => __typeName ??= runtimeTypeToString(_rti);
String toString() => _typeName;
// TODO(ahe): This is a poor hashCode as it collides with its name.
int get hashCode => _hashCode ??= _typeName.hashCode;
@pragma('dart2js:noInline')
bool operator ==(other) {
return (other is TypeImpl) && _typeName == other._typeName;
}
}
/// Represents a type variable.
///
/// This class holds the information needed when reflecting on generic classes
/// and their members.
class TypeVariable {
final Type owner;
final String name;
final int bound;
const TypeVariable(this.owner, this.name, this.bound);
}
getMangledTypeName(Type t) {
TypeImpl type = t;
return type._typeName;
}
/// Sets the runtime type information on [target]. [rti] is a type
/// representation of type 4 or 5, that is, either a JavaScript array or `null`.
///
/// Called from generated code.
///
/// This is used only for marking JavaScript Arrays (JSArray) with the element
/// type.
// Don't inline. Let the JS engine inline this. The call expression is much
// more compact that the inlined expansion.
@pragma('dart2js:noInline')
Object setRuntimeTypeInfo(Object target, var rti) {
assert(rti == null || isJsArray(rti));
String rtiName = JS_GET_NAME(JsGetName.RTI_NAME);
JS('var', r'#[#] = #', target, rtiName, rti);
return target;
}
/// Returns the runtime type information of [target]. The returned value is a
/// list of type representations for the type arguments.
///
/// Called from generated code.
getRuntimeTypeInfo(Object target) {
if (target == null) return null;
String rtiName = JS_GET_NAME(JsGetName.RTI_NAME);
return JS('var', r'#[#]', target, rtiName);
}
/// Returns the type arguments of [object] as an instance of [substitutionName].
getRuntimeTypeArguments(interceptor, object, substitutionName) {
var substitution = getField(interceptor,
'${JS_GET_NAME(JsGetName.OPERATOR_AS_PREFIX)}$substitutionName');
return substitute(substitution, getRuntimeTypeInfo(object));
}
/// Returns the [index]th type argument of [target] as an instance of
/// [substitutionName].
///
/// Called from generated code.
@pragma('dart2js:noThrows')
@pragma('dart2js:noSideEffects')
@pragma('dart2js:noInline')
getRuntimeTypeArgumentIntercepted(
interceptor, Object target, String substitutionName, int index) {
var arguments =
getRuntimeTypeArguments(interceptor, target, substitutionName);
return arguments == null ? null : getIndex(arguments, index);
}
/// Returns the [index]th type argument of [target] as an instance of
/// [substitutionName].
///
/// Called from generated code.
@pragma('dart2js:noThrows')
@pragma('dart2js:noSideEffects')
@pragma('dart2js:noInline')
getRuntimeTypeArgument(Object target, String substitutionName, int index) {
var arguments = getRuntimeTypeArguments(target, target, substitutionName);
return arguments == null ? null : getIndex(arguments, index);
}
/// Returns the [index]th type argument of [target].
///
/// Called from generated code.
@pragma('dart2js:noThrows')
@pragma('dart2js:noSideEffects')
@pragma('dart2js:noInline')
getTypeArgumentByIndex(Object target, int index) {
var rti = getRuntimeTypeInfo(target);
return rti == null ? null : getIndex(rti, index);
}
/// Retrieves the class name from type information stored on the constructor
/// of [object].
String getClassName(var object) {
return rawRtiToJsConstructorName(getRawRuntimeType(getInterceptor(object)));
}
String _getRuntimeTypeAsString(var rti, List<String> genericContext) {
assert(isJsArray(rti));
String className = unminifyOrTag(rawRtiToJsConstructorName(getIndex(rti, 0)));
return '$className${_joinArguments(rti, 1, genericContext)}';
}
/// Returns a human-readable representation of the type representation [rti].
///
/// Called from generated code.
@pragma('dart2js:noInline')
String runtimeTypeToString(var rti) {
return _runtimeTypeToString(rti, null);
}
String _runtimeTypeToString(var rti, List<String> genericContext) {
if (isDartDynamicTypeRti(rti)) {
return 'dynamic';
}
if (isDartVoidTypeRti(rti)) {
return 'void';
}
if (isJsArray(rti)) {
// A list representing a type with arguments.
return _getRuntimeTypeAsString(rti, genericContext);
}
if (isJsFunction(rti)) {
// A reference to the constructor.
return unminifyOrTag(rawRtiToJsConstructorName(rti));
}
if (isDartJsInteropTypeArgumentRti(rti)) {
return 'dynamic';
}
if (isGenericFunctionTypeParameter(rti)) {
int index = rti;
if (genericContext == null || index < 0 || index >= genericContext.length) {
return 'unexpected-generic-index:${index}';
}
return '${genericContext[genericContext.length - index - 1]}';
}
if (isDartFunctionType(rti)) {
// TODO(sra): If there is a typedef tag, use the typedef name.
return _functionRtiToString(rti, genericContext);
}
if (isDartFutureOrType(rti)) {
var typeArgument = getFutureOrArgument(rti);
return 'FutureOr<${_runtimeTypeToString(typeArgument, genericContext)}>';
}
// We should not get here.
return 'unknown-reified-type';
}
// Returns a formatted String version of a function type.
//
// [genericContext] is list of the names of generic type parameters for generic
// function types. The de Bruijn indexing scheme references the type variables
// from the inner scope out. The parameters for each scope are pushed in
// reverse, e.g. `<P,Q>(<R,S,T>(R))` creates the list `[Q,P,T,S,R]`. This
// allows the de Bruijn index to simply index backwards from the end of
// [genericContext], e.g. in the outer scope index `0` is P and `1` is Q, and in
// the inner scope index `0` is R, `3` is P, and `4` is Q.
//
// [genericContext] is initially `null`.
String _functionRtiToString(var rti, List<String> genericContext) {
String typeParameters = '';
int outerContextLength;
String boundsTag = JS_GET_NAME(JsGetName.FUNCTION_TYPE_GENERIC_BOUNDS_TAG);
if (hasField(rti, boundsTag)) {
List boundsRti = JS('JSFixedArray', '#[#]', rti, boundsTag);
if (genericContext == null) {
genericContext = <String>[];
} else {
outerContextLength = genericContext.length;
}
int offset = genericContext.length;
for (int i = boundsRti.length; i > 0; i--) {
genericContext.add('T${offset + i}');
}
// All variables are in scope in the bounds.
String typeSep = '';
typeParameters = '<';
for (int i = 0; i < boundsRti.length; i++) {
typeParameters += typeSep;
typeParameters += genericContext[genericContext.length - i - 1];
typeSep = ', ';
var boundRti = boundsRti[i];
if (isInterestingBound(boundRti)) {
typeParameters +=
' extends ' + _runtimeTypeToString(boundRti, genericContext);
}
}
typeParameters += '>';
}
String returnTypeText;
String voidTag = JS_GET_NAME(JsGetName.FUNCTION_TYPE_VOID_RETURN_TAG);
if (JS('bool', '!!#[#]', rti, voidTag)) {
returnTypeText = 'void';
} else {
String returnTypeTag = JS_GET_NAME(JsGetName.FUNCTION_TYPE_RETURN_TYPE_TAG);
var returnRti = JS('', '#[#]', rti, returnTypeTag);
returnTypeText = _runtimeTypeToString(returnRti, genericContext);
}
String argumentsText = '';
String sep = '';
String requiredParamsTag =
JS_GET_NAME(JsGetName.FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG);
if (hasField(rti, requiredParamsTag)) {
List arguments = JS('JSFixedArray', '#[#]', rti, requiredParamsTag);
for (var argument in arguments) {
argumentsText += sep;
argumentsText += _runtimeTypeToString(argument, genericContext);
sep = ', ';
}
}
String optionalParamsTag =
JS_GET_NAME(JsGetName.FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG);
bool hasOptionalArguments = JS('bool', '# in #', optionalParamsTag, rti);
if (hasOptionalArguments) {
List optionalArguments = JS('JSFixedArray', '#[#]', rti, optionalParamsTag);
argumentsText += '$sep[';
sep = '';
for (var argument in optionalArguments) {
argumentsText += sep;
argumentsText += _runtimeTypeToString(argument, genericContext);
sep = ', ';
}
argumentsText += ']';
}
String namedParamsTag =
JS_GET_NAME(JsGetName.FUNCTION_TYPE_NAMED_PARAMETERS_TAG);
bool hasNamedArguments = JS('bool', '# in #', namedParamsTag, rti);
if (hasNamedArguments) {
var namedArguments = JS('', '#[#]', rti, namedParamsTag);
argumentsText += '$sep{';
sep = '';
for (String name in extractKeys(namedArguments)) {
argumentsText += sep;
argumentsText += _runtimeTypeToString(
JS('', '#[#]', namedArguments, name), genericContext);
argumentsText += ' $name';
sep = ', ';
}
argumentsText += '}';
}
if (outerContextLength != null) {
// Pop all of the generic type parameters.
JS('', '#.length = #', genericContext, outerContextLength);
}
// TODO(sra): Below is the same format as the VM. Change to:
//
// return '${returnTypeText} Function${typeParameters}(${argumentsText})';
//
return '${typeParameters}(${argumentsText}) => ${returnTypeText}';
}
/// Creates a comma-separated string of human-readable representations of the
/// type representations in the JavaScript array [types] starting at index
/// [startIndex].
String joinArguments(var types, int startIndex) {
return _joinArguments(types, startIndex, null);
}
String _joinArguments(var types, int startIndex, List<String> genericContext) {
if (types == null) return '';
assert(isJsArray(types));
var separator = '';
bool allDynamic = true;
StringBuffer buffer = new StringBuffer('');
for (int index = startIndex; index < getLength(types); index++) {
buffer.write(separator);
separator = ', ';
var argument = getIndex(types, index);
if (argument != null) {
allDynamic = false;
}
buffer.write(_runtimeTypeToString(argument, genericContext));
}
return '<$buffer>';
}
/// Returns a human-readable representation of the type of [object].
///
/// In minified mode does *not* use unminified identifiers (even when present).
String getRuntimeTypeString(var object) {
if (object is Closure) {
// This excludes classes that implement Function via a `call` method, but
// includes classes generated to represent closures in closure conversion.
var functionRti = extractFunctionTypeObjectFrom(object);
if (functionRti != null) {
return runtimeTypeToString(functionRti);
}
}
String className = getClassName(object);
if (object == null) return className;
String rtiName = JS_GET_NAME(JsGetName.RTI_NAME);
var rti = JS('var', r'#[#]', object, rtiName);
return "$className${joinArguments(rti, 0)}";
}
/// Returns the full type of [o] in the runtime type representation.
getRti(o) {
if (o is Closure) {
// This excludes classes that implement Function via a `call` method, but
// includes classes generated to represent closures in closure conversion.
var functionRti = extractFunctionTypeObjectFrom(o);
if (functionRti != null) return functionRti;
}
var interceptor = getInterceptor(o);
var type = getRawRuntimeType(interceptor);
if (o == null) return type;
if (JS('bool', 'typeof # != "object"', o)) return type;
var rti = getRuntimeTypeInfo(o);
if (rti != null) {
// If the type has type variables (that is, `rti != null`), make a copy of
// the type arguments and insert [o] in the first position to create a
// compound type representation.
rti = JS('JSExtendableArray', '#.slice()', rti); // Make a copy.
JS('', '#.splice(0, 0, #)', rti, type); // Insert type at position 0.
type = rti;
}
return type;
}
Type getRuntimeType(var object) {
if (JS_GET_FLAG('USE_NEW_RTI')) return newRti.getRuntimeType(object);
return new TypeImpl(getRti(object));
}
/// Applies the [substitution] on the [arguments].
///
/// See the comment in the beginning of this file for a description of the
/// possible values for [substitution].
substitute(var substitution, var arguments) {
if (substitution == null) return arguments;
assert(isJsFunction(substitution));
assert(arguments == null || isJsArray(arguments));
substitution = invoke(substitution, arguments);
if (substitution == null) return null;
if (isJsArray(substitution)) {
// Substitutions are generated too late to mark Array as used, so use a
// tautological JS 'cast' to mark Array as used. This is needed only in
// some tiny tests where the substition is the only thing that creates an
// Array.
return JS('JSArray', '#', substitution);
}
if (isJsFunction(substitution)) {
// TODO(johnniwinther): Check if this is still needed.
return invoke(substitution, arguments);
}
return arguments;
}
/// Perform a type check with arguments on the Dart object [object].
///
/// Parameters:
/// - [isField]: the name of the flag/function to check if the object
/// is of the correct class.
/// - [checks]: the (JavaScript) list of type representations for the
/// arguments to check against.
/// - [asField]: the name of the function that transforms the type
/// arguments of [objects] to an instance of the class that we check
/// against.
bool checkSubtype(Object object, String isField, List checks, String asField) {
if (object == null) return false;
var arguments = getRuntimeTypeInfo(object);
// Interceptor is needed for JSArray and native classes.
// TODO(sra): It could be a more specialized interceptor since [object] is not
// `null` or a primitive.
var interceptor = getInterceptor(object);
var isSubclass = getField(interceptor, isField);
// When we read the field and it is not there, [isSubclass] will be `null`.
if (isSubclass == null) return false;
// Should the asField function be passed the receiver?
var substitution = getField(interceptor, asField);
return checkArguments(substitution, arguments, null, checks, null);
}
/// Returns the field's type name.
///
/// In minified mode, uses the unminified names if available.
String computeTypeName(String isField, List arguments) {
// Extract the class name from the is field and append the textual
// representation of the type arguments.
return Primitives.formatType(
unminifyOrTag(isCheckPropertyToJsConstructorName(isField)), arguments);
}
/// Called from generated code.
Object subtypeCast(Object object, String isField, List checks, String asField) {
if (object == null) return object;
if (checkSubtype(object, isField, checks, asField)) return object;
String typeName = computeTypeName(isField, checks);
throw new CastErrorImplementation(object, typeName);
}
/// Called from generated code.
Object assertSubtype(
Object object, String isField, List checks, String asField) {
if (object == null) return object;
if (checkSubtype(object, isField, checks, asField)) return object;
String typeName = computeTypeName(isField, checks);
throw new TypeErrorImplementation(object, typeName);
}
/// Checks that the type represented by [subtype] is a subtype of [supertype].
/// If not a type error is thrown using [prefix], [infix], [suffix] and the
/// runtime types [subtype] and [supertype] to generate the error message.
///
/// Called from generated code.
assertIsSubtype(
var subtype, var supertype, String prefix, String infix, String suffix) {
if (!isSubtype(subtype, supertype)) {
String message = "TypeError: "
"$prefix${runtimeTypeToString(subtype)}$infix"
"${runtimeTypeToString(supertype)}$suffix";
throwTypeError(message);
}
}
throwTypeError(message) {
throw new TypeErrorImplementation.fromMessage(message);
}
bool checkArguments(
var substitution, var arguments, var sEnv, var checks, var tEnv) {
return areSubtypes(substitute(substitution, arguments), sEnv, checks, tEnv);
}
/// Checks whether the types of [s] are all subtypes of the types of [t].
///
/// [s] and [t] are either `null` or JavaScript arrays of type representations,
/// A `null` argument is interpreted as the arguments of a raw type, that is a
/// list of `dynamic`. If [s] and [t] are JavaScript arrays they must be of the
/// same length.
///
/// See the comment in the beginning of this file for a description of type
/// representations.
bool areSubtypes(var s, var sEnv, var t, var tEnv) {
// `null` means a raw type.
if (t == null) return true;
if (s == null) {
int len = getLength(t);
for (int i = 0; i < len; i++) {
if (!_isSubtype(null, null, getIndex(t, i), tEnv)) {
return false;
}
}
return true;
}
assert(isJsArray(s));
assert(isJsArray(t));
assert(getLength(s) == getLength(t));
int len = getLength(s);
for (int i = 0; i < len; i++) {
if (!_isSubtype(getIndex(s, i), sEnv, getIndex(t, i), tEnv)) {
return false;
}
}
return true;
}
/// Computes the signature by applying the type arguments of [context] as an
/// instance of [contextName] to the signature function [signature].
computeSignature(var signature, var context, var contextName) {
var interceptor = getInterceptor(context);
var typeArguments =
getRuntimeTypeArguments(interceptor, context, contextName);
return invokeOn(signature, context, typeArguments);
}
/// Returns `true` if the runtime type representation [type] is a top type.
/// That is, either `dynamic`, `void` or `Object`.
@pragma('dart2js:tryInline')
bool isTopType(var type) {
return isDartDynamicTypeRti(type) ||
isDartVoidTypeRti(type) ||
isDartObjectTypeRti(type) ||
isDartJsInteropTypeArgumentRti(type);
}
/// Returns `true` if the runtime type representation [type] is a supertype of
/// [Null].
@pragma('dart2js:tryInline')
bool isSupertypeOfNull(var type) {
return isSupertypeOfNullBase(type) || isSupertypeOfNullRecursive(type);
}
/// Returns `true` if the runtime type representation [type] is a simple
/// supertype of [Null].
///
/// This method doesn't handle `FutureOr<Null>`. This is handle by
/// [isSupertypeOfNullRecursive] because it requires a recursive check.
@pragma('dart2js:tryInline')
bool isSupertypeOfNullBase(var type) {
return isDartDynamicTypeRti(type) ||
isDartObjectTypeRti(type) ||
isNullTypeRti(type) ||
isDartVoidTypeRti(type) ||
isDartJsInteropTypeArgumentRti(type);
}
/// Returns `true` if the runtime type representation [type] is a `FutureOr`
/// type that is a supertype of [Null].
///
/// This method is recursive to be able to handle both `FutureOr<Null>` and
/// `FutureOr<FutureOr<Null>>` etc.
bool isSupertypeOfNullRecursive(var type) {
if (isGenericFunctionTypeParameter(type)) {
// We need to check for function type variables because `isDartFutureOrType`
// doesn't work on numbers.
return false;
}
if (isDartFutureOrType(type)) {
var typeArgument = getFutureOrArgument(type);
return isSupertypeOfNullBase(type) ||
isSupertypeOfNullRecursive(typeArgument);
}
return false;
}
/// Returns the type argument of the `FutureOr` runtime type representation
/// [type].
///
/// For instance `num` of `FutureOr<num>`.
@pragma('dart2js:tryInline')
Object getFutureOrArgument(var type) {
assert(isDartFutureOrType(type));
var typeArgumentTag = JS_GET_NAME(JsGetName.FUTURE_OR_TYPE_ARGUMENT_TAG);
return hasField(type, typeArgumentTag)
? getField(type, typeArgumentTag)
: null;
}
/// Tests whether the Dart object [o] is a subtype of the runtime type
/// representation [t].
///
/// See the comment in the beginning of this file for a description of type
/// representations.
bool checkSubtypeOfRuntimeType(o, t) {
if (o == null) return isSupertypeOfNull(t);
if (isTopType(t)) return true;
if (JS('bool', 'typeof # == "object"', t)) {
if (isDartFutureOrType(t)) {
// `o is FutureOr<T>` is equivalent to
//
// o is T || o is Future<T>
//
// T might be a function type, requiring extracting the closure's
// signature, so do the `o is T` check here and let the `Future` interface
// type test fall through to the `isSubtype` check at the end of this
// function.
var tTypeArgument = getFutureOrArgument(t);
if (checkSubtypeOfRuntimeType(o, tTypeArgument)) return true;
}
if (isDartFunctionType(t)) {
return functionTypeTest(o, t);
}
}
var interceptor = getInterceptor(o);
var type = getRawRuntimeType(interceptor);
var rti = getRuntimeTypeInfo(o);
if (rti != null) {
// If the type has type variables (that is, `rti != null`), make a copy of
// the type arguments and insert [o] in the first position to create a
// compound type representation.
rti = JS('JSExtendableArray', '#.slice()', rti); // Make a copy.
JS('', '#.splice(0, 0, #)', rti, type); // Insert type at position 0.
type = rti;
}
return isSubtype(type, t);
}
/// Called from generated code.
Object subtypeOfRuntimeTypeCast(Object object, var type) {
if (object != null && !checkSubtypeOfRuntimeType(object, type)) {
throw new CastErrorImplementation(object, runtimeTypeToString(type));
}
return object;
}
/// Called from generated code.
Object assertSubtypeOfRuntimeType(Object object, var type) {
if (object != null && !checkSubtypeOfRuntimeType(object, type)) {
throw new TypeErrorImplementation(object, runtimeTypeToString(type));
}
return object;
}
/// Extracts the type arguments from a type representation. The result is a
/// JavaScript array or `null`.
getArguments(var type) {
return isJsArray(type) ? JS('var', r'#.slice(1)', type) : null;
}
/// Checks whether the type represented by the type representation [s] is a
/// subtype of the type represented by the type representation [t].
///
/// See the comment in the beginning of this file for a description of type
/// representations.
///
/// The arguments [s] and [t] must be types, usually represented by the
/// constructor of the class, or an array (for generic class types).
bool isSubtype(var s, var t) {
return _isSubtype(s, null, t, null);
}
bool _isSubtype(var s, var sEnv, var t, var tEnv) {
// Subtyping is reflexive.
if (isIdentical(s, t)) return true;
// [t] is a top type?
if (isTopType(t)) return true;
if (isDartJsInteropTypeArgumentRti(s)) return true;
// [s] is a top type?
if (isTopType(s)) {
if (isGenericFunctionTypeParameter(t)) {
// We need to check for function type variables because
// `isDartFutureOrType` doesn't work on numbers.
return false;
}
if (isDartFutureOrType(t)) {
// [t] is FutureOr<T>. Check [s] <: T.
var tTypeArgument = getFutureOrArgument(t);
return _isSubtype(s, sEnv, tTypeArgument, tEnv);
}
return false;
}
// Generic function type parameters must match exactly, which would have
// exited earlier. The de Bruijn indexing ensures the representation as a
// small number can be used for type comparison.
if (isGenericFunctionTypeParameter(s)) {
// TODO(sra): Use the bound of the type variable.
return false;
}
if (isGenericFunctionTypeParameter(t)) return false;
if (isNullType(s)) return true;
if (isDartFunctionType(t)) {
return _isFunctionSubtype(s, sEnv, t, tEnv);
}
if (isDartFunctionType(s)) {
// Check function types against the `Function` class (`Object` is also a
// supertype, but is tested above with other 'top' types.).
return isDartFunctionTypeRti(t);
}
// Get the object describing the class and check for the subtyping flag
// constructed from the type of [s].
var typeOfS = isJsArray(s) ? getIndex(s, 0) : s;
if (isDartFutureOrType(t)) {
// [t] is FutureOr<T>
var tTypeArgument = getFutureOrArgument(t);
if (isDartFutureOrType(s)) {
// [S] is FutureOr<S>. Check S <: T
var sTypeArgument = getFutureOrArgument(s);
return _isSubtype(sTypeArgument, sEnv, tTypeArgument, tEnv);
} else if (_isSubtype(s, sEnv, tTypeArgument, tEnv)) {
// `true` because [s] <: T.
return true;
} else {
// Check [s] <: Future<T>.
String futureClass = JS_GET_NAME(JsGetName.FUTURE_CLASS_TYPE_NAME);
if (!builtinIsSubtype(typeOfS, futureClass)) {
// [s] doesn't implement Future.
return false;
}
var typeOfSPrototype = JS('', '#.prototype', typeOfS);
var field = '${JS_GET_NAME(JsGetName.OPERATOR_AS_PREFIX)}${futureClass}';
var futureSubstitution = getField(typeOfSPrototype, field);
var futureArguments = substitute(futureSubstitution, getArguments(s));
var futureArgument =
isJsArray(futureArguments) ? getIndex(futureArguments, 0) : null;
// [s] implements Future<S>. Check S <: T.
return _isSubtype(futureArgument, sEnv, tTypeArgument, tEnv);
}
}
// Get the object describing the class and check for the subtyping flag
// constructed from the type of [t].
var typeOfT = isJsArray(t) ? getIndex(t, 0) : t;
// Check for a subtyping flag.
// Get the necessary substitution of the type arguments, if there is one.
var substitution;
if (isNotIdentical(typeOfT, typeOfS)) {
String typeOfTString = rawRtiToJsConstructorName(typeOfT);
if (!builtinIsSubtype(typeOfS, typeOfTString)) {
return false;
}
var typeOfSPrototype = JS('', '#.prototype', typeOfS);
var field = '${JS_GET_NAME(JsGetName.OPERATOR_AS_PREFIX)}${typeOfTString}';
substitution = getField(typeOfSPrototype, field);
}
// The class of [s] is a subclass of the class of [t]. If [t] has no
// type arguments, it used as a raw type and [s] is a subtype of [t].
if (!isJsArray(t)) {
return true;
}
// Recursively check the type arguments.
return checkArguments(
substitution, getArguments(s), sEnv, getArguments(t), tEnv);
}
/// Top-level function subtype check when [t] is known to be a function type
/// rti.
bool isFunctionSubtype(var s, var t) {
return _isFunctionSubtype(s, null, t, null);
}
bool _isFunctionSubtype(var s, var sEnv, var t, var tEnv) {
assert(isDartFunctionType(t));
if (!isDartFunctionType(s)) return false;
var genericBoundsTag =
JS_GET_NAME(JsGetName.FUNCTION_TYPE_GENERIC_BOUNDS_TAG);
var voidReturnTag = JS_GET_NAME(JsGetName.FUNCTION_TYPE_VOID_RETURN_TAG);
var returnTypeTag = JS_GET_NAME(JsGetName.FUNCTION_TYPE_RETURN_TYPE_TAG);
// Generic function types must agree on number of type parameters and bounds.
if (hasField(s, genericBoundsTag)) {
if (hasNoField(t, genericBoundsTag)) return false;
var sBounds = getField(s, genericBoundsTag);
var tBounds = getField(t, genericBoundsTag);
int sGenericParameters = getLength(sBounds);
int tGenericParameters = getLength(tBounds);
if (sGenericParameters != tGenericParameters) return false;
// TODO(sra): Compare bounds, which should be 'equal' trees due to the de
// Bruijn numbering of type parameters.
// TODO(sra): Extend [sEnv] and [tEnv] with bindings for the [s] and [t]
// type parameters to enable checking the bound against non-type-parameter
// terms.
} else if (hasField(t, genericBoundsTag)) {
return false;
}
var sReturnType = getField(s, returnTypeTag);
var tReturnType = getField(t, returnTypeTag);
if (!_isSubtype(sReturnType, sEnv, tReturnType, tEnv)) return false;
var requiredParametersTag =
JS_GET_NAME(JsGetName.FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG);
var sParameterTypes = getField(s, requiredParametersTag);
var tParameterTypes = getField(t, requiredParametersTag);
var optionalParametersTag =
JS_GET_NAME(JsGetName.FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG);
var sOptionalParameterTypes = getField(s, optionalParametersTag);
var tOptionalParameterTypes = getField(t, optionalParametersTag);
int sParametersLen = sParameterTypes != null ? getLength(sParameterTypes) : 0;
int tParametersLen = tParameterTypes != null ? getLength(tParameterTypes) : 0;
int sOptionalParametersLen =
sOptionalParameterTypes != null ? getLength(sOptionalParameterTypes) : 0;
int tOptionalParametersLen =
tOptionalParameterTypes != null ? getLength(tOptionalParameterTypes) : 0;
if (sParametersLen > tParametersLen) {
// Too many required parameters in [s].
return false;
}
if (sParametersLen + sOptionalParametersLen <
tParametersLen + tOptionalParametersLen) {
// Too few required and optional parameters in [s].
return false;
}
int pos = 0;
// Check all required parameters of [s].
for (; pos < sParametersLen; pos++) {
if (!_isSubtype(getIndex(tParameterTypes, pos), tEnv,
getIndex(sParameterTypes, pos), sEnv)) {
return false;
}
}
int sPos = 0;
int tPos = pos;
// Check the remaining parameters of [t] with the first optional parameters
// of [s].
for (; tPos < tParametersLen; sPos++, tPos++) {
if (!_isSubtype(getIndex(tParameterTypes, tPos), tEnv,
getIndex(sOptionalParameterTypes, sPos), sEnv)) {
return false;
}
}
tPos = 0;
// Check the optional parameters of [t] with the remaining optional
// parameters of [s]:
for (; tPos < tOptionalParametersLen; sPos++, tPos++) {
if (!_isSubtype(getIndex(tOptionalParameterTypes, tPos), tEnv,
getIndex(sOptionalParameterTypes, sPos), sEnv)) {
return false;
}
}
var namedParametersTag =
JS_GET_NAME(JsGetName.FUNCTION_TYPE_NAMED_PARAMETERS_TAG);
var sNamedParameters = getField(s, namedParametersTag);
var tNamedParameters = getField(t, namedParametersTag);
if (tNamedParameters == null) return true;
if (sNamedParameters == null) return false;
return namedParametersSubtypeCheck(
sNamedParameters, sEnv, tNamedParameters, tEnv);
}
bool namedParametersSubtypeCheck(var s, var sEnv, var t, var tEnv) {
assert(isJsObject(s));
assert(isJsObject(t));
// Each named parameter in [t] must exist in [s] and be a subtype of the type
// in [s].
List names = JS('JSFixedArray', 'Object.getOwnPropertyNames(#)', t);
for (int i = 0; i < names.length; i++) {
var name = names[i];
if (JS('bool', '!Object.hasOwnProperty.call(#, #)', s, name)) {
return false;
}
var tType = JS('', '#[#]', t, name);
var sType = JS('', '#[#]', s, name);
if (!_isSubtype(tType, tEnv, sType, sEnv)) return false;
}
return true;
}
/// Returns whether [type] is the representation of a generic function type
/// parameter. Generic function type parameters are represented de Bruijn
/// indexes.
///
/// This test is only valid if [type] is known _not_ to be the void rti, whose
/// runtime representation is -1.
bool isGenericFunctionTypeParameter(var type) {
assert(!isDartVoidTypeRti(type));
return type is num; // Actually int, but 'is num' is faster.
}
/// Returns [genericFunctionRti] with type parameters bound to [parameters].
///
/// [genericFunctionRti] must be an rti representation with a number of generic
/// type parameters matching the number of types in [parameters].
///
/// Called from generated code.
@pragma('dart2js:noInline')
instantiatedGenericFunctionType(genericFunctionRti, parameters) {
if (genericFunctionRti == null) return null;
assert(isDartFunctionType(genericFunctionRti));
var genericBoundsTag =
JS_GET_NAME(JsGetName.FUNCTION_TYPE_GENERIC_BOUNDS_TAG);
assert(hasField(genericFunctionRti, genericBoundsTag));
var bounds = getField(genericFunctionRti, genericBoundsTag);
// Generic function types must agree on number of type parameters and bounds.
int boundLength = getLength(bounds);
int parametersLength = getLength(parameters);
assert(boundLength == parametersLength);
var result = JS('', '{#:1}', JS_GET_NAME(JsGetName.FUNCTION_TYPE_TAG));
return finishBindInstantiatedFunctionType(
genericFunctionRti, result, parameters, 0);
}
bindInstantiatedFunctionType(rti, parameters, int depth) {
var result = JS('', '{#:1}', JS_GET_NAME(JsGetName.FUNCTION_TYPE_TAG));
var genericBoundsTag =
JS_GET_NAME(JsGetName.FUNCTION_TYPE_GENERIC_BOUNDS_TAG);
if (hasField(rti, genericBoundsTag)) {
var bounds = getField(rti, genericBoundsTag);
depth += getLength(bounds);
setField(result, genericBoundsTag,
bindInstantiatedTypes(bounds, parameters, depth));
}
return finishBindInstantiatedFunctionType(rti, result, parameters, depth);
}
/// Common code for function types that copies all non-bounds parts of the
/// function [rti] into [result].
finishBindInstantiatedFunctionType(rti, result, parameters, int depth) {
var voidReturnTag = JS_GET_NAME(JsGetName.FUNCTION_TYPE_VOID_RETURN_TAG);
var returnTypeTag = JS_GET_NAME(JsGetName.FUNCTION_TYPE_RETURN_TYPE_TAG);
if (hasField(rti, voidReturnTag)) {
setField(result, voidReturnTag, getField(rti, voidReturnTag));
} else if (hasField(rti, returnTypeTag)) {
setField(result, returnTypeTag,
bindInstantiatedType(getField(rti, returnTypeTag), parameters, depth));
}
var requiredParametersTag =
JS_GET_NAME(JsGetName.FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG);
if (hasField(rti, requiredParametersTag)) {
setField(
result,
requiredParametersTag,
bindInstantiatedTypes(
getField(rti, requiredParametersTag), parameters, depth));
}
String optionalParametersTag =
JS_GET_NAME(JsGetName.FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG);
if (hasField(rti, optionalParametersTag)) {
setField(
result,
optionalParametersTag,
bindInstantiatedTypes(
getField(rti, optionalParametersTag), parameters, depth));
}
String namedParametersTag =
JS_GET_NAME(JsGetName.FUNCTION_TYPE_NAMED_PARAMETERS_TAG);
if (hasField(rti, namedParametersTag)) {
var namedParameters = getField(rti, namedParametersTag);
var boundNamed = JS('', '{}');
var names = JS('JSFixedArray', 'Object.keys(#)', namedParameters);
for (var name in names) {
setField(
boundNamed,
name,
bindInstantiatedType(
getField(namedParameters, name), parameters, depth));
}
setField(result, namedParametersTag, boundNamed);
}
return result;
}
/// Copies [rti], substituting generic type parameters from [parameters].
///
/// Generic type parameters are de Bruijn indexes counting up through the
/// generic function type parameters scopes to index into [parameters].
///
/// [depth] is the number of subsequent generic function parameters that are in
/// scope. This is subtracted off the de Bruijn index for the type parameter to
/// arrive at an potential index into [parameters].
bindInstantiatedType(rti, parameters, int depth) {
if (isDartDynamicTypeRti(rti)) return rti; // dynamic.
if (isDartVoidTypeRti(rti)) return rti; // void.
// Functions are constructors denoting the class of the constructor.
if (isJsFunction(rti)) return rti;
// de Bruijn type indexes.
if (isGenericFunctionTypeParameter(rti)) {
if (rti < depth) return rti;
return JS('', '#[#]', parameters, rti - depth);
}
// Other things encoded as numbers.
if (rti is num) return rti;
if (isJsArray(rti)) {
// An array is a parameterized class type, e.g. the list of three
// constructor functions [Map, String, int] represents `Map<String, int>`.
// Since the 'head' of the term and the arguments are encoded in the same
// scheme, it is sufficient to walk all the types.
return bindInstantiatedTypes(rti, parameters, depth);
}
if (isDartFunctionType(rti)) {
return bindInstantiatedFunctionType(rti, parameters, depth);
}
// Can't include the bad [rti] since it is not a Dart value.
throw new ArgumentError('Unknown RTI format in bindInstantiatedType.');
}
/// Returns a copy of array [rti] with each type bound.
bindInstantiatedTypes(rti, parameters, int depth) {
List array = JS('JSFixedArray', '#.slice()', rti);
for (int i = 0; i < array.length; i++) {
array[i] = bindInstantiatedType(array[i], parameters, depth);
}
return array;
}
/// Calls the JavaScript [function] with the [arguments] with the global scope
/// as the `this` context.
invoke(var function, var arguments) => invokeOn(function, null, arguments);
/// Calls the JavaScript [function] with the [arguments] with [receiver] as the
/// `this` context.
Object invokeOn(function, receiver, arguments) {
assert(isJsFunction(function));
assert(arguments == null || isJsArray(arguments));
return JS('var', r'#.apply(#, #)', function, receiver, arguments);
}
/// Calls the property [name] on the JavaScript [object].
call(var object, String name) => JS('var', r'#[#]()', object, name);
/// Returns the property [name] of the JavaScript object [object].
getField(var object, String name) => JS('var', r'#[#]', object, name);
/// Returns the property [index] of the JavaScript array [array].
getIndex(var array, int index) {
assert(isJsArray(array));
return JS('var', r'#[#]', array, index);
}
setField(var object, String name, var value) {
JS('', '#[#] = #', object, name, value);
}
setIndex(var array, int index, var value) {
JS('', '#[#] = #', array, index, value);
}
/// Returns the length of the JavaScript array [array].
int getLength(var array) {
assert(isJsArray(array));
return JS('int', r'#.length', array);
}
/// Returns whether [value] is a JavaScript array.
bool isJsArray(var value) {
return value is JSArray;
}
hasField(var object, var name) => JS('bool', r'# in #', name, object);
hasNoField(var object, var name) => !hasField(object, name);
/// Returns `true` if [o] is a JavaScript function.
bool isJsFunction(var o) => JS('bool', r'typeof # == "function"', o);
/// Returns `true` if [o] is a JavaScript object.
bool isJsObject(var o) => JS('bool', r"typeof # == 'object'", o);
/// Returns `true` if the JavaScript values [s] and [t] are identical. We use
/// this helper instead of [identical] because `identical` needs to merge
/// `null` and `undefined` (which we can avoid).
bool isIdentical(var s, var t) => JS('bool', '# === #', s, t);
/// Returns `true` if the JavaScript values [s] and [t] are not identical. We
/// use this helper instead of [identical] because `identical` needs to merge
/// `null` and `undefined` (which we can avoid).
bool isNotIdentical(var s, var t) => JS('bool', '# !== #', s, t);
/// 'Top' bounds are uninteresting: null/undefined and Object.
bool isInterestingBound(rti) =>
rti != null &&
isNotIdentical(
rti,
JS_BUILTIN(
'depends:none;effects:none;', JsBuiltin.dartObjectConstructor));