Chromium Code Reviews| 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 0cd37e1cd1031abe633b22be37ac8a73cf60b5a9..81f9e6fa74d8a9fdfb4278f297f2de01f3ae5261 100644 |
| --- a/sdk/lib/_internal/lib/js_rti.dart |
| +++ b/sdk/lib/_internal/lib/js_rti.dart |
| @@ -15,8 +15,17 @@ getRuntimeTypeInfo(target) { |
| return JS('var', r'#.$builtinTypeInfo', target); |
| } |
| +/** |
| + * Returns the type arguments of [target] as an instance of [substitutionName]. |
| + */ |
| +getRuntimeTypeArguments(target, substitutionName) { |
| + var substitution = |
| + getField(target, '${JS_OPERATOR_AS_PREFIX()}$substitutionName'); |
| + return substitute(substitution, getRuntimeTypeInfo(target)); |
| +} |
| + |
| getRuntimeTypeArgument(target, substitution, index) { |
| - var arguments = substitute(substitution, getRuntimeTypeInfo(target)); |
| + var arguments = getRuntimeTypeArguments(target, substitution); |
| return (arguments == null) ? null : getField(arguments, index); |
| } |
| @@ -44,12 +53,12 @@ String getRuntimeTypeAsString(List runtimeType) { |
| return '$className${joinArguments(runtimeType, 1)}'; |
| } |
| -String getConstructorName(type) => JS('String', r'#.builtin$cls', type); |
| +String getConstructorName(var type) => JS('String', r'#.builtin$cls', type); |
| -String runtimeTypeToString(type) { |
| - if (type == null) { |
| +String runtimeTypeToString(var type) { |
| + if (isNull(type)) { |
| return 'dynamic'; |
| - } else if (type is JSArray) { |
| + } else if (isJsArray(type)) { |
| // A list representing a type with arguments. |
| return getRuntimeTypeAsString(type); |
| } else { |
| @@ -63,7 +72,7 @@ String joinArguments(var types, int startIndex) { |
| bool firstArgument = true; |
| bool allDynamic = true; |
| StringBuffer buffer = new StringBuffer(); |
| - for (int index = startIndex; index < types.length; index++) { |
| + for (int index = startIndex; index < getLength(types); index++) { |
| if (firstArgument) { |
| firstArgument = false; |
| } else { |
| @@ -91,17 +100,28 @@ Type getRuntimeType(var object) { |
| bool isJsFunction(var o) => JS('bool', r'typeof # == "function"', o); |
| +bool isJsObject(var o) => JS('bool', r"typeof # == 'object'", o); |
| + |
| Object invoke(function, arguments) { |
| return JS('var', r'#.apply(null, #)', function, arguments); |
|
karlklose
2013/06/19 14:37:05
How about
Object invoke(function, arguments) =>
Johnni Winther
2013/06/21 12:19:15
Done.
|
| } |
| +Object invokeOn(function, receiver, arguments) { |
| + return JS('var', r'#.apply(#, #)', function, receiver, arguments); |
| +} |
| + |
| Object call(target, name) => JS('var', r'#[#]()', target, name); |
| substitute(var substitution, var arguments) { |
| if (substitution is JSArray) { |
| arguments = substitution; |
| } else if (isJsFunction(substitution)) { |
| - arguments = invoke(substitution, arguments); |
| + substitution = invoke(substitution, arguments); |
| + if (isJsArray(substitution)) { |
| + arguments = substitution; |
| + } else if (isJsFunction(substitution)) { |
| + arguments = invoke(substitution, arguments); |
|
karlklose
2013/06/19 14:37:05
This happens for thunks (if I understand correctly
Johnni Winther
2013/06/21 12:19:15
I think it that case it is the first [invoke] call
|
| + } |
| } |
| return arguments; |
| } |
| @@ -146,7 +166,7 @@ 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); |
| + throw new CastErrorImplementation(actualType, typeName); |
|
karlklose
2013/06/19 14:37:05
Perhaps we should move the type lookup to CastErro
Johnni Winther
2013/06/21 12:19:15
Added a TODO.
|
| } |
| return object; |
| } |
| @@ -172,17 +192,90 @@ bool checkArguments(var substitution, var arguments, var checks) { |
| return areSubtypes(substitute(substitution, arguments), checks); |
| } |
| +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]. The |
|
karlklose
2013/06/19 14:37:05
This comment is not
Johnni Winther
2013/06/21 12:19:15
Now it
|
| + */ |
| +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_SETUP_OBJECT(); |
|
karlklose
2013/06/19 14:37:05
var -> String.
Johnni Winther
2013/06/21 12:19:15
No. JS_SETUP_OBJECT returns a JS 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); |
|
karlklose
2013/06/19 14:37:05
Remove one space.
Johnni Winther
2013/06/21 12:19:15
Done.
|
| + 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); |
| +} |
| + |
| +/** |
| + * Applies the type arguments of [context] as an instance of [contextName] to |
| + * the signature function [signature]. |
| + */ |
| +applySignature(var signature, var context, var contextName) { |
|
karlklose
2013/06/19 14:37:05
Perhaps computeSignature would be a better name.
Johnni Winther
2013/06/21 12:19:15
Done.
|
| + var typeArguments = getRuntimeTypeArguments(context, contextName); |
| + return invokeOn(signature, context, typeArguments); |
| +} |
| + |
| bool areSubtypes(List s, List t) { |
| // [:null:] means a raw type. |
| - if (s == null || t == null) return true; |
| + if (isNull(s) || isNull(t)) return true; |
| - assert(s is JSArray); |
| - assert(t is JSArray); |
| - assert(s.length == t.length); |
| + assert(isJsArray(s)); |
| + assert(isJsArray(t)); |
| + assert(getLength(s) == getLength(t)); |
| - int len = s.length; |
| + int len = getLength(s); |
| for (int i = 0; i < len; i++) { |
| - if (!isSubtype(s[i], t[i])) { |
| + if (!isSubtype(getIndex(s, i), getIndex(t, i))) { |
| return false; |
| } |
| } |
| @@ -200,6 +293,14 @@ bool isSubtypeOfNull(type) { |
| return type == null || getConstructorName(type) == JS_OBJECT_CLASS_NAME(); |
| } |
| +hasField(var object, var name) => JS('bool', r'#[#] != null', object, name); |
| + |
| +hasNoField(var object, var name) => JS('bool', r'#[#] == null', object, name); |
| + |
| +isNull(var object) => JS('bool', '# == null', object); |
| + |
| +isNotNull(var object) => JS('bool', '# != null', object); |
| + |
| /** |
| * Tests whether the Dart object [o] is a subtype of the runtime type |
| * representation [t], which is a type representation as described in the |
| @@ -261,24 +362,37 @@ Object assertSubtypeOfRuntimeType(Object object, var type) { |
| * 5) [:null:]: the dynamic type. |
| */ |
| bool isSubtype(var s, var t) { |
| - // If either type is dynamic, [s] is a subtype of [t]. |
| - if (JS('bool', '# == null', s) || JS('bool', '# == null', t)) return true; |
| // Subtyping is reflexive. |
| - if (JS('bool', '# === #', s, t)) return true; |
| - // Get the object describing the class and check for the subtyping flag |
| - // constructed from the type of [t]. |
| - var typeOfS = s is JSArray ? s[0] : s; |
| - var typeOfT = t is JSArray ? t[0] : t; |
| - // TODO(johnniwinther): replace this with the real function subtype test. |
| - if (JS('bool', '#.func', s) == true || JS('bool', '#.func', t) == true ) { |
| + 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; |
| // Check for a subtyping flag. |
| - var test = '${JS_OPERATOR_IS_PREFIX()}${runtimeTypeToString(typeOfT)}'; |
| - if (getField(typeOfS, test) == null) 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 (JS('bool', '# !== #', typeOfT, typeOfS)) { |
| + if (isNotIdentical(typeOfT, typeOfS)) { |
| var field = '${JS_OPERATOR_AS_PREFIX()}${runtimeTypeToString(typeOfT)}'; |
| substitution = getField(typeOfS, field); |
| } |
| @@ -286,12 +400,172 @@ bool isSubtype(var s, var t) { |
| // arguments and no substitution, it is used as raw type. If [t] has no |
| // type arguments, it used as a raw type. In both cases, [s] is a subtype |
| // of [t]. |
| - if ((s is! JSArray && JS('bool', '# == null', substitution)) || |
| - t is! JSArray) { |
| + if ((!isJsArray(s) && isNull(substitution)) || |
| + !isJsArray(t)) { |
| return true; |
| } |
| // Recursively check the type arguments. |
| return checkArguments(substitution, getArguments(s), getArguments(t)); |
| } |
| +bool isAssignable(var s, var t) { |
| + return isSubtype(s, t) || isSubtype(t, s); |
| +} |
| + |
| +getIndex(var array, int index) { |
| + assert(isJsArray(array)); |
| + return JS('var', '#[#]', array, index); |
| +} |
| + |
| +int getLength(var array) { |
| + assert(isJsArray(array)); |
| + return JS('int', '#.length', array); |
| +} |
| + |
| +/** |
| + * If [allowShorter] is [:true:], [t] is allowed to be shorter that [s]. |
|
karlklose
2013/06/19 14:37:05
'that' -> 'than'.
Perhaps explain when this is th
Johnni Winther
2013/06/21 12:19:15
Done.
|
| + */ |
| +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)) { |
|
karlklose
2013/06/19 14:37:05
Long line.
Johnni Winther
2013/06/21 12:19:15
Done.
|
| + 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++) { |
|
karlklose
2013/06/19 14:37:05
Remove space before ';'.
Johnni Winther
2013/06/21 12:19:15
Done.
|
| + 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()}'); |
| + if (!areAssignableMaps(sNamedParameters, tNamedParameters)) return false; |
|
karlklose
2013/06/19 14:37:05
'return areAssignableMaps(sNamedParameters, tNamed
Johnni Winther
2013/06/21 12:19:15
Done.
|
| + |
| + return true; |
| +} |
| + |
| createRuntimeType(String name) => new TypeImpl(name); |
| + |
| +bool isIdentical(var s, var t) => JS('bool', '# === #', s, t); |
| + |
| +bool isNotIdentical(var s, var t) => JS('bool', '# !== #', s, t); |