Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(564)

Unified Diff: sdk/lib/_internal/lib/js_rti.dart

Issue 12334070: Support runtime check of function types. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fix status files Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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);

Powered by Google App Engine
This is Rietveld 408576698