Chromium Code Reviews| Index: dart/sdk/lib/_internal/lib/js_helper.dart |
| diff --git a/dart/sdk/lib/_internal/lib/js_helper.dart b/dart/sdk/lib/_internal/lib/js_helper.dart |
| index 7e753ef160c00c8277862e632ea5cf588e36f39b..412572139b373c0a5bfd6d9906f704f6be8891b7 100644 |
| --- a/dart/sdk/lib/_internal/lib/js_helper.dart |
| +++ b/dart/sdk/lib/_internal/lib/js_helper.dart |
| @@ -33,6 +33,7 @@ import 'dart:_collection-dev' as _symbol_dev; |
| import 'dart:_collection-dev' show MappedIterable; |
| import 'dart:_js_names' show |
| + extractKeys, |
| mangledNames, |
| unmangleGlobalNameIfPreservedAnyways; |
| @@ -2027,3 +2028,376 @@ class RuntimeError extends Error { |
| RuntimeError(this.message); |
| String toString() => "RuntimeError: $message"; |
| } |
| + |
| +abstract class RuntimeType { |
| + const RuntimeType(); |
| + |
| + toRti(); |
| +} |
| + |
| +class RuntimeFunctionType extends RuntimeType { |
| + final RuntimeType returnType; |
| + final List<RuntimeType> parameterTypes; |
| + final List<RuntimeType> optionalParameterTypes; |
| + final namedParameters; |
| + |
| + static var /* bool */ inAssert = false; |
| + |
| + RuntimeFunctionType(this.returnType, |
| + this.parameterTypes, |
| + this.optionalParameterTypes, |
| + this.namedParameters); |
| + |
| + bool get isVoid => returnType is VoidRuntimeType; |
| + |
| + /// Called from generated code. [expression] is a Dart object and this method |
| + /// returns true if [this] is a supertype of [expression]. |
| + @NoInline() @NoSideEffects() |
| + bool _isTest(expression) { |
| + var functionTypeObject = _extractFunctionTypeObjectFrom(expression); |
| + return functionTypeObject == null |
| + ? false |
| + : isFunctionSubtype(functionTypeObject, toRti()); |
| + } |
| + |
| + @NoInline() @NoSideEffects() |
| + _asCheck(expression) { |
| + // Type inferrer doesn't think this is called with dynamic arguments. |
|
ngeoffray
2013/12/09 09:48:52
Maybe add an 'obfuscate' method that will return t
ahe
2013/12/09 15:30:06
Done.
|
| + return _check(JS('', '#', expression), true); |
| + } |
| + |
| + @NoInline() @NoSideEffects() |
| + _assertCheck(expression) { |
| + if (inAssert) return; |
| + inAssert = true; // Don't try to check this library itself. |
| + try { |
| + // Type inferrer don't think this is called with dynamic arguments. |
| + return _check(JS('', '#', expression), false); |
| + } finally { |
| + inAssert = false; |
| + } |
| + } |
| + |
| + _check(expression, bool isCast) { |
| + if (expression == null) return null; |
| + if (_isTest(expression)) return expression; |
| + |
| + var self = new FunctionTypeInfoDecoderRing(toRti()).toString(); |
| + if (isCast) { |
| + var functionTypeObject = _extractFunctionTypeObjectFrom(expression); |
| + var pretty; |
| + if (functionTypeObject != null) { |
| + pretty = new FunctionTypeInfoDecoderRing(functionTypeObject).toString(); |
| + } else { |
| + pretty = Primitives.objectTypeName(expression); |
| + } |
| + throw new CastErrorImplementation(pretty, self); |
| + } else { |
| + // TODO(ahe): Pass "pretty" function-type to TypeErrorImplementation? |
| + throw new TypeErrorImplementation(expression, self); |
| + } |
| + } |
| + |
| + _extractFunctionTypeObjectFrom(o) { |
| + var interceptor = getInterceptor(o); |
| + return JS('bool', '# in #', JS_SIGNATURE_NAME(), interceptor) |
| + ? JS('', '#[#]()', interceptor, JS_SIGNATURE_NAME()) |
| + : null; |
| + } |
| + |
| + toRti() { |
| + var result = JS('=Object', '{ #: "dynafunc" }', JS_FUNCTION_TYPE_TAG()); |
| + if (isVoid) { |
| + JS('', '#[#] = true', result, JS_FUNCTION_TYPE_VOID_RETURN_TAG()); |
| + } else { |
| + if (returnType is! DynamicRuntimeType) { |
| + JS('', '#[#] = #', result, JS_FUNCTION_TYPE_RETURN_TYPE_TAG(), |
| + returnType.toRti()); |
| + } |
| + } |
| + if (parameterTypes != null && !parameterTypes.isEmpty) { |
| + JS('', '#[#] = #', result, JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG(), |
| + listToRti(parameterTypes)); |
| + } |
| + |
| + if (optionalParameterTypes != null && !optionalParameterTypes.isEmpty) { |
| + JS('', '#[#] = #', result, JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG(), |
| + listToRti(optionalParameterTypes)); |
| + } |
| + |
| + if (namedParameters != null) { |
| + var namedRti = JS('=Object', '{}'); |
| + var keys = extractKeys(namedParameters); |
| + for (var i = 0; i < keys.length; i++) { |
| + var name = keys[i]; |
| + var rti = JS('', '#[#]', namedParameters, name).toRti(); |
| + JS('', '#[#] = #', namedRti, name, rti); |
| + } |
| + JS('', '#[#] = #', result, JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG(), |
| + namedRti); |
| + } |
| + |
| + return result; |
| + } |
| + |
| + static listToRti(list) { |
| + list = JS('JSFixedArray', '#', list); |
| + var result = JS('JSExtendableArray', '[]'); |
|
ngeoffray
2013/12/09 09:48:52
Why the JS here?
ahe
2013/12/09 15:30:06
Avoids adding type parameters, for one thing. The
ngeoffray
2013/12/09 15:32:49
This is surprising, we've made sure in the backend
|
| + for (var i = 0; i < list.length; i++) { |
| + JS('', '#.push(#)', result, list[i].toRti()); |
|
ngeoffray
2013/12/09 09:48:52
Why the JS here?
ahe
2013/12/09 15:30:06
The generated code improved.
ngeoffray
2013/12/09 15:32:49
Ditto.
|
| + } |
| + return result; |
| + } |
| + |
| + String toString() { |
| + String result = '('; |
| + bool needsComma = false; |
| + if (parameterTypes != null) { |
| + for (var i = 0; i < parameterTypes.length; i++) { |
| + RuntimeType type = parameterTypes[i]; |
| + if (needsComma) result += ', '; |
| + result += '$type'; |
| + needsComma = true; |
| + } |
| + } |
| + if (optionalParameterTypes != null && !optionalParameterTypes.isEmpty) { |
| + if (needsComma) result += ', '; |
| + needsComma = false; |
| + result += '['; |
| + for (var i = 0; i < optionalParameterTypes.length; i++) { |
| + RuntimeType type = optionalParameterTypes[i]; |
| + if (needsComma) result += ', '; |
| + result += '$type'; |
| + needsComma = true; |
| + } |
| + result += ']'; |
| + } else if (namedParameters != null) { |
| + if (needsComma) result += ', '; |
| + needsComma = false; |
| + result += '{'; |
| + var keys = extractKeys(namedParameters); |
| + for (var i = 0; i < keys.length; i++) { |
| + var name = keys[i]; |
| + if (needsComma) result += ', '; |
| + var rti = JS('', '#[#]', namedParameters, name).toRti(); |
| + result += '$rti ${JS("String", "#", name)}'; |
| + needsComma = true; |
| + } |
| + result += '}'; |
| + } |
| + |
| + result += ') -> $returnType'; |
| + return result; |
| + } |
| +} |
| + |
| +RuntimeFunctionType buildFunctionType(returnType, |
| + parameterTypes, |
| + optionalParameterTypes) { |
| + return new RuntimeFunctionType( |
| + returnType, |
| + parameterTypes, |
| + optionalParameterTypes, |
| + null); |
| +} |
| + |
| +RuntimeFunctionType buildNamedFunctionType(returnType, |
| + parameterTypes, |
| + namedParameters) { |
| + return new RuntimeFunctionType( |
| + returnType, |
| + parameterTypes, |
| + null, |
| + namedParameters); |
| +} |
| + |
| +RuntimeType buildInterfaceType(rti, typeArguments) { |
| + String name = JS('String|Null', r'#.name', rti); |
| + if (typeArguments == null || typeArguments.isEmpty) { |
| + return new RuntimeTypePlain(name); |
| + } |
| + return new RuntimeTypeGeneric(name, typeArguments, null); |
| +} |
| + |
| +class DynamicRuntimeType extends RuntimeType { |
| + const DynamicRuntimeType(); |
| + |
| + String toString() => 'dynamic'; |
| + |
| + toRti() => null; |
| +} |
| + |
| +RuntimeType getDynamicRuntimeType() => const DynamicRuntimeType(); |
| + |
| +class VoidRuntimeType extends RuntimeType { |
| + const VoidRuntimeType(); |
| + |
| + String toString() => 'void'; |
| + |
| + toRti() => throw 'internal error'; |
| +} |
| + |
| +RuntimeType getVoidRuntimeType() => const VoidRuntimeType(); |
| + |
| +/** |
| + * Meta helper for function type tests. |
| + * |
| + * A "meta helper" is a helper function that is never called but simulates how |
| + * generated code behaves as far as resolution and type inference is concerned. |
| + */ |
| +functionTypeTestMetaHelper() { |
| + var dyn = JS('', 'x'); |
| + var dyn2 = JS('', 'x'); |
| + List fixedListOrNull = JS('JSFixedArray|Null', 'x'); |
| + List fixedListOrNull2 = JS('JSFixedArray|Null', 'x'); |
| + List fixedList = JS('JSFixedArray', 'x'); |
| + // TODO(ahe): Can we use [UnknownJavaScriptObject] below? |
| + var /* UnknownJavaScriptObject */ jsObject = JS('=Object', 'x'); |
| + |
| + buildFunctionType(dyn, fixedListOrNull, fixedListOrNull2); |
| + buildNamedFunctionType(dyn, fixedList, jsObject); |
| + buildInterfaceType(dyn, fixedListOrNull); |
| + getDynamicRuntimeType(); |
| + getVoidRuntimeType(); |
| + convertRtiToRuntimeType(dyn); |
| + dyn._isTest(dyn2); |
| + dyn._asCheck(dyn2); |
| + dyn._assertCheck(dyn2); |
| +} |
| + |
| +RuntimeType convertRtiToRuntimeType(rti) { |
| + if (rti == null) { |
| + return getDynamicRuntimeType(); |
| + } else if (JS('bool', 'typeof # == "function"', rti)) { |
| + return new RuntimeTypePlain(JS('String', r'rti.name')); |
| + } else if (JS('bool', '#.constructor == Array', rti)) { |
| + List list = JS('JSFixedArray', '#', rti); |
| + String name = JS('String', r'#.name', list[0]); |
| + List arguments = []; |
| + for (int i = 1; i < list.length; i++) { |
| + arguments.add(convertRtiToRuntimeType(list[i])); |
| + } |
| + return new RuntimeTypeGeneric(name, arguments, rti); |
| + } else if (JS('bool', '"func" in #', rti)) { |
| + return new FunctionTypeInfoDecoderRing(rti).toRuntimeType(); |
| + } else { |
| + throw new RuntimeError( |
| + "Cannot convert " |
| + "'${JS('String', 'JSON.stringify(#)', rti)}' to RuntimeType."); |
| + } |
| +} |
| + |
| +class RuntimeTypePlain extends RuntimeType { |
| + final String name; |
| + |
| + RuntimeTypePlain(this.name); |
| + |
| + toRti() { |
| + var rti = JS('', 'init.allClasses[#]', name); |
| + if (rti == null) throw "no type for '$name'"; |
| + return rti; |
| + } |
| + |
| + String toString() => name; |
| +} |
| + |
| +class RuntimeTypeGeneric extends RuntimeType { |
| + final String name; |
| + final List<RuntimeType> arguments; |
| + var rti; |
| + |
| + RuntimeTypeGeneric(this.name, this.arguments, this.rti); |
| + |
| + toRti() { |
| + if (rti != null) return rti; |
| + var result = JS('JSExtendableArray', '[init.allClasses[#]]', name); |
| + if (result[0] == null) { |
| + throw "no type for '$name<...>'"; |
| + } |
| + for (RuntimeType argument in arguments) { |
| + JS('', '#.push(#)', result, argument.toRti()); |
| + } |
| + return rti = result; |
| + } |
| + |
| + String toString() => '$name<${arguments.join(", ")}>'; |
| +} |
| + |
| +class FunctionTypeInfoDecoderRing { |
| + final _typeData; |
| + String _cachedToString; |
| + |
| + FunctionTypeInfoDecoderRing(this._typeData); |
| + |
| + bool get _hasReturnType => JS('bool', '"ret" in #', _typeData); |
| + get _returnType => JS('', '#.ret', _typeData); |
| + |
| + bool get _isVoid => JS('bool', '!!#.void', _typeData); |
| + |
| + bool get _hasArguments => JS('bool', '"args" in #', _typeData); |
| + List get _arguments => JS('JSExtendableArray', '#.args', _typeData); |
| + |
| + bool get _hasOptionalArguments => JS('bool', '"opt" in #', _typeData); |
| + List get _optionalArguments => JS('JSExtendableArray', '#.opt', _typeData); |
| + |
| + bool get _hasNamedArguments => JS('bool', '"named" in #', _typeData); |
| + get _namedArguments => JS('=Object', '#.named', _typeData); |
| + |
| + RuntimeType toRuntimeType() { |
| + // TODO(ahe): Implement this (and update return type). |
| + return const DynamicRuntimeType(); |
| + } |
| + |
| + String _convert(type) { |
| + String result = runtimeTypeToString(type); |
| + if (result != null) return result; |
| + if (JS('bool', '"func" in #', type)) { |
| + return new FunctionTypeInfoDecoderRing(type).toString(); |
| + } else { |
| + throw 'bad type'; |
| + } |
| + } |
| + |
| + String toString() { |
| + if (_cachedToString != null) return _cachedToString; |
| + var s = "("; |
| + var sep = ''; |
| + if (_hasArguments) { |
| + for (var argument in _arguments) { |
| + s += sep; |
| + s += _convert(argument); |
| + sep = ', '; |
| + } |
| + } |
| + if (_hasOptionalArguments) { |
| + s += '$sep['; |
| + sep = ''; |
| + for (var argument in _optionalArguments) { |
| + s += sep; |
| + s += _convert(argument); |
| + sep = ', '; |
| + } |
| + s += ']'; |
| + } |
| + if (_hasNamedArguments) { |
| + s += '$sep{'; |
| + sep = ''; |
| + for (var name in extractKeys(_namedArguments)) { |
| + s += sep; |
| + s += '$name: '; |
| + s += _convert(JS('', '#[#]', _namedArguments, name)); |
| + sep = ', '; |
| + } |
| + s += '}'; |
| + } |
| + s += ') -> '; |
| + if (_isVoid) { |
| + s += 'void'; |
| + } else if (_hasReturnType) { |
| + s += _convert(_returnType); |
| + } else { |
| + s += 'dynamic'; |
| + } |
| + return _cachedToString = "$s"; |
| + } |
| +} |