| Index: sdk/lib/_internal/lib/js_rti.dart
|
| diff --git a/sdk/lib/_internal/lib/js_rti.dart b/sdk/lib/_internal/lib/js_rti.dart
|
| index 015283c06fc9c4cca9b2412e96965563f6a9a722..fc023aac72b41866f33e4b8f9d6d0cd6d9f7bf52 100644
|
| --- a/sdk/lib/_internal/lib/js_rti.dart
|
| +++ b/sdk/lib/_internal/lib/js_rti.dart
|
| @@ -81,17 +81,20 @@ getRuntimeTypeInfo(Object target) {
|
| }
|
|
|
| /**
|
| - * Returns the [index]th type argument of [target] converted using
|
| - * [substitution].
|
| - *
|
| - * See the comment in the beginning of this file for a description of the
|
| - * possible values for [substitution].
|
| + * Returns the type arguments of [target] as an instance of [substitutionName].
|
| */
|
| -getRuntimeTypeArgument(Object target, var substitution, int index) {
|
| - assert(isNull(substitution) ||
|
| - isJsArray(substitution) ||
|
| - isJsFunction(substitution));
|
| - var arguments = substitute(substitution, getRuntimeTypeInfo(target));
|
| +getRuntimeTypeArguments(target, substitutionName) {
|
| + var substitution =
|
| + getField(target, '${JS_OPERATOR_AS_PREFIX()}$substitutionName');
|
| + return substitute(substitution, getRuntimeTypeInfo(target));
|
| +}
|
| +
|
| +/**
|
| + * Returns the [index]th type argument of [target] as an instance of
|
| + * [substitutionName].
|
| + */
|
| +getRuntimeTypeArgument(Object target, String substitutionName, int index) {
|
| + var arguments = getRuntimeTypeArguments(target, substitutionName);
|
| return isNull(arguments) ? null : getIndex(arguments, index);
|
| }
|
|
|
| @@ -189,7 +192,13 @@ substitute(var substitution, var arguments) {
|
| if (isJsArray(substitution)) {
|
| arguments = substitution;
|
| } else if (isJsFunction(substitution)) {
|
| - arguments = invoke(substitution, arguments);
|
| + substitution = invoke(substitution, arguments);
|
| + if (isJsArray(substitution)) {
|
| + arguments = substitution;
|
| + } else if (isJsFunction(substitution)) {
|
| + // TODO(johnniwinther): Check if this is still needed.
|
| + arguments = invoke(substitution, arguments);
|
| + }
|
| }
|
| return arguments;
|
| }
|
| @@ -234,7 +243,9 @@ Object subtypeCast(Object object, String isField, List checks, String asField) {
|
| if (object != null && !checkSubtype(object, isField, checks, asField)) {
|
| String actualType = Primitives.objectTypeName(object);
|
| String typeName = computeTypeName(isField, checks);
|
| - throw new CastErrorImplementation(object, typeName);
|
| + // TODO(johnniwinther): Move type lookup to [CastErrorImplementation] to
|
| + // align with [TypeErrorImplementation].
|
| + throw new CastErrorImplementation(actualType, typeName);
|
| }
|
| return object;
|
| }
|
| @@ -288,6 +299,82 @@ bool areSubtypes(var s, var t) {
|
| return true;
|
| }
|
|
|
| +Object functionSubtypeCast(Object object, String signatureName,
|
| + String contextName, var context,
|
| + var typeArguments) {
|
| + if (!checkFunctionSubtype(object, signatureName,
|
| + contextName, context, typeArguments)) {
|
| + String actualType = Primitives.objectTypeName(object);
|
| + // TODO(johnniwinther): Provide better function type naming.
|
| + String typeName = signatureName;
|
| + throw new CastErrorImplementation(actualType, typeName);
|
| + }
|
| + return object;
|
| +}
|
| +
|
| +Object assertFunctionSubtype(Object object, String signatureName,
|
| + String contextName, var context,
|
| + var typeArguments) {
|
| + if (!checkFunctionSubtype(object, signatureName,
|
| + contextName, context, typeArguments)) {
|
| + // TODO(johnniwinther): Provide better function type naming.
|
| + String typeName = signatureName;
|
| + throw new TypeErrorImplementation(object, typeName);
|
| + }
|
| + return object;
|
| +}
|
| +
|
| +/**
|
| + * Checks that the type of [target] is a subtype of the function type denoted by
|
| + * [signatureName]. If the type contains type variables, [contextName] holds the
|
| + * name of the class where these were declared and either [context] holds the
|
| + * object in which the runtime values of these can be found or [typeArguments]
|
| + * contains these values as a list of runtime type information.
|
| + */
|
| +bool checkFunctionSubtype(var target, String signatureName,
|
| + String contextName, var context,
|
| + var typeArguments) {
|
| + if (isNull(target)) return true;
|
| + var interceptor = getInterceptor(target);
|
| + if (hasField(interceptor, '${JS_OPERATOR_IS_PREFIX()}_$signatureName')) {
|
| + return true;
|
| + }
|
| + var signatureLocation = JS_GLOBAL_OBJECT();
|
| + if (isNotNull(contextName)) {
|
| + signatureLocation = getField(signatureLocation, contextName);
|
| + }
|
| + var typeSignature =
|
| + getField(signatureLocation, '${JS_SIGNATURE_NAME()}_$signatureName');
|
| + if (isNull(typeSignature)) {
|
| + // All checks can be determined statically so the type signature has not
|
| + // been computed.
|
| + return false;
|
| + }
|
| + var targetSignatureFunction = getField(interceptor, '${JS_SIGNATURE_NAME()}');
|
| + if (isNull(targetSignatureFunction)) return false;
|
| + var targetSignature = invokeOn(targetSignatureFunction, interceptor, null);
|
| + if (isJsFunction(typeSignature)) {
|
| + if (isNotNull(typeArguments)) {
|
| + typeSignature = invoke(typeSignature, typeArguments);
|
| + } else if (isNotNull(context)) {
|
| + typeSignature =
|
| + invoke(typeSignature, getRuntimeTypeArguments(context, contextName));
|
| + } else {
|
| + typeSignature = invoke(typeSignature, null);
|
| + }
|
| + }
|
| + return isFunctionSubtype(targetSignature, typeSignature);
|
| +}
|
| +
|
| +/**
|
| + * 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 typeArguments = getRuntimeTypeArguments(context, contextName);
|
| + return invokeOn(signature, context, typeArguments);
|
| +}
|
| +
|
| /**
|
| * Returns [:true:] if the runtime type representation [type] is a supertype of
|
| * [:Null:].
|
| @@ -359,21 +446,34 @@ getArguments(var type) {
|
| * representations.
|
| */
|
| bool isSubtype(var s, var t) {
|
| - // If either type is dynamic, [s] is a subtype of [t].
|
| - if (isNull(s) || isNull(t)) return true;
|
| // Subtyping is reflexive.
|
| if (isIdentical(s, t)) return true;
|
| + // If either type is dynamic, [s] is a subtype of [t].
|
| + if (isNull(s) || isNull(t)) return true;
|
| + if (hasField(t, '${JS_FUNCTION_TYPE_TAG()}')) {
|
| + if (hasNoField(s, '${JS_FUNCTION_TYPE_TAG()}')) {
|
| + var signatureName =
|
| + '${JS_OPERATOR_IS_PREFIX()}_${getField(t, JS_FUNCTION_TYPE_TAG())}';
|
| + if (hasField(s, signatureName)) return true;
|
| + var targetSignatureFunction = getField(s, '${JS_SIGNATURE_NAME()}');
|
| + if (isNull(targetSignatureFunction)) return false;
|
| + s = invokeOn(targetSignatureFunction, s, null);
|
| + }
|
| + return isFunctionSubtype(s, t);
|
| + }
|
| + // Check function types against the Function class.
|
| + if (getConstructorName(t) == JS_FUNCTION_CLASS_NAME() &&
|
| + hasField(s, '${JS_FUNCTION_TYPE_TAG()}')) {
|
| + return true;
|
| + }
|
| // Get the object describing the class and check for the subtyping flag
|
| // constructed from the type of [t].
|
| var typeOfS = isJsArray(s) ? getIndex(s, 0) : s;
|
| var typeOfT = isJsArray(t) ? getIndex(t, 0) : t;
|
| - // TODO(johnniwinther): replace this with the real function subtype test.
|
| - if (JS('bool', '#.func', s) == true || JS('bool', '#.func', t) == true ) {
|
| - return true;
|
| - }
|
| // Check for a subtyping flag.
|
| - var test = '${JS_OPERATOR_IS_PREFIX()}${runtimeTypeToString(typeOfT)}';
|
| - if (isNull(getField(typeOfS, test))) return false;
|
| + var name = runtimeTypeToString(typeOfT);
|
| + var test = '${JS_OPERATOR_IS_PREFIX()}${name}';
|
| + if (hasNoField(typeOfS, test)) return false;
|
| // Get the necessary substitution of the type arguments, if there is one.
|
| var substitution;
|
| if (isNotIdentical(typeOfT, typeOfS)) {
|
| @@ -391,14 +491,165 @@ bool isSubtype(var s, var t) {
|
| return checkArguments(substitution, getArguments(s), getArguments(t));
|
| }
|
|
|
| +bool isAssignable(var s, var t) {
|
| + return isSubtype(s, t) || isSubtype(t, s);
|
| +}
|
| +
|
| +/**
|
| + * If [allowShorter] is [:true:], [t] is allowed to be shorter than [s].
|
| + */
|
| +bool areAssignable(List s, List t, bool allowShorter) {
|
| + // Both lists are empty and thus equal.
|
| + if (isNull(t) && isNull(s)) return true;
|
| + // [t] is empty (and [s] is not) => only OK if [allowShorter].
|
| + if (isNull(t)) return allowShorter;
|
| + // [s] is empty (and [t] is not) => [s] is not longer or equal to [t].
|
| + if (isNull(s)) return false;
|
| +
|
| + assert(isJsArray(s));
|
| + assert(isJsArray(t));
|
| +
|
| + int sLength = getLength(s);
|
| + int tLength = getLength(t);
|
| + if (allowShorter) {
|
| + if (sLength < tLength) return false;
|
| + } else {
|
| + if (sLength != tLength) return false;
|
| + }
|
| +
|
| + for (int i = 0; i < tLength; i++) {
|
| + if (!isAssignable(getIndex(s, i), getIndex(t, i))) {
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +bool areAssignableMaps(var s, var t) {
|
| + if (isNull(t)) return true;
|
| + if (isNull(s)) return false;
|
| +
|
| + assert(isJsObject(s));
|
| + assert(isJsObject(t));
|
| +
|
| + return JS('bool', r'''
|
| + function (t, s, isAssignable) {
|
| + for (var $name in t) {
|
| + if (!s.hasOwnProperty($name)) {
|
| + return false;
|
| + }
|
| + var tType = t[$name];
|
| + var sType = s[$name];
|
| + if (!isAssignable.call$2(sType, tType)) {
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| + }(#, #, #)
|
| + ''', t, s, RAW_DART_FUNCTION_REF(isAssignable));
|
| +}
|
| +
|
| +bool isFunctionSubtype(var s, var t) {
|
| + assert(hasField(t, '${JS_FUNCTION_TYPE_TAG()}'));
|
| + if (hasNoField(s, '${JS_FUNCTION_TYPE_TAG()}')) return false;
|
| + if (hasField(s, '${JS_FUNCTION_TYPE_VOID_RETURN_TAG()}')) {
|
| + if (hasNoField(t, '${JS_FUNCTION_TYPE_VOID_RETURN_TAG()}') &&
|
| + hasField(t, '${JS_FUNCTION_TYPE_RETURN_TYPE_TAG()}')) {
|
| + return false;
|
| + }
|
| + } else if (hasNoField(t, '${JS_FUNCTION_TYPE_VOID_RETURN_TAG()}')) {
|
| + var sReturnType = getField(s, '${JS_FUNCTION_TYPE_RETURN_TYPE_TAG()}');
|
| + var tReturnType = getField(t, '${JS_FUNCTION_TYPE_RETURN_TYPE_TAG()}');
|
| + if (!isAssignable(sReturnType, tReturnType)) return false;
|
| + }
|
| + var sParameterTypes =
|
| + getField(s, '${JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG()}');
|
| + var tParameterTypes =
|
| + getField(t, '${JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG()}');
|
| +
|
| + var sOptionalParameterTypes =
|
| + getField(s, '${JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG()}');
|
| + var tOptionalParameterTypes =
|
| + getField(t, '${JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG()}');
|
| +
|
| + int sParametersLen =
|
| + isNotNull(sParameterTypes) ? getLength(sParameterTypes) : 0;
|
| + int tParametersLen =
|
| + isNotNull(tParameterTypes) ? getLength(tParameterTypes) : 0;
|
| +
|
| + int sOptionalParametersLen = isNotNull(sOptionalParameterTypes)
|
| + ? getLength(sOptionalParameterTypes) : 0;
|
| + int tOptionalParametersLen = isNotNull(tOptionalParameterTypes)
|
| + ? 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;
|
| + }
|
| + if (sParametersLen == tParametersLen) {
|
| + // Simple case: Same number of required parameters.
|
| + if (!areAssignable(sParameterTypes, tParameterTypes, false)) return false;
|
| + if (!areAssignable(sOptionalParameterTypes,
|
| + tOptionalParameterTypes, true)) {
|
| + return false;
|
| + }
|
| + } else {
|
| + // Complex case: Optional parameters of [s] for required parameters of [t].
|
| + int pos = 0;
|
| + // Check all required parameters of [s].
|
| + for (; pos < sParametersLen; pos++) {
|
| + if (!isAssignable(getIndex(sParameterTypes, pos),
|
| + getIndex(tParameterTypes, pos))) {
|
| + 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 (!isAssignable(getIndex(sOptionalParameterTypes, sPos),
|
| + getIndex(tParameterTypes, tPos))) {
|
| + return false;
|
| + }
|
| + }
|
| + sPos = 0;
|
| + // Check the optional parameters of [t] with the remaing optional parameters
|
| + // of [s]:
|
| + for (; tPos < tOptionalParametersLen ; sPos++, tPos++) {
|
| + if (!isAssignable(getIndex(tOptionalParameterTypes, sPos),
|
| + getIndex(tOptionalParameterTypes, tPos))) {
|
| + return false;
|
| + }
|
| + }
|
| + }
|
| +
|
| + var sNamedParameters =
|
| + getField(s, '${JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG()}');
|
| + var tNamedParameters =
|
| + getField(t, '${JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG()}');
|
| + return areAssignableMaps(sNamedParameters, tNamedParameters);
|
| +}
|
| +
|
| /**
|
| * Calls the JavaScript [function] with the [arguments] with the global scope
|
| * as the [:this:] context.
|
| */
|
| -invoke(var function, var arguments) {
|
| +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(isNull(arguments) || isJsArray(arguments));
|
| - return JS('var', r'#.apply(null, #)', function, arguments);
|
| + return JS('var', r'#.apply(#, #)', function, receiver, arguments);
|
| }
|
|
|
| /// Calls the property [name] on the JavaScript [object].
|
| @@ -419,9 +670,16 @@ int getLength(var array) {
|
| return JS('int', r'#.length', array);
|
| }
|
|
|
| +hasField(var object, var name) => JS('bool', r'#[#] != null', object, name);
|
| +
|
| +hasNoField(var object, var name) => JS('bool', r'#[#] == null', 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 [o] is equal to [:null:], that is either [:null:] or
|
| * [:undefined:]. We use this helper to avoid generating code under the invalid
|
|
|