Index: sdk/lib/_internal/js_runtime/lib/js_helper.dart |
diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart |
index 39c897f066532fa068985a811fd99e44ba34a554..f522a7703d916b017dedb9394b759673a92b219c 100644 |
--- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart |
+++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart |
@@ -1156,64 +1156,178 @@ class Primitives { |
namedArgumentList)); |
} |
- static applyFunctionNewEmitter(Function function, |
- List positionalArguments, |
- Map<String, dynamic> namedArguments) { |
- if (namedArguments == null) { |
- int requiredParameterCount = JS('int', r'#[#]', function, |
- JS_GET_NAME(JsGetName.REQUIRED_PARAMETER_PROPERTY)); |
- int argumentCount = positionalArguments.length; |
- if (argumentCount < requiredParameterCount) { |
- return functionNoSuchMethod(function, positionalArguments, null); |
+ /** |
+ * Implements [Function.apply] for the lazy and startup emitters. |
+ * |
+ * There are two types of closures that can reach this function: |
+ * |
+ * 1. tear-offs (including tear-offs of static functions). |
+ * 2. anonymous closures. |
+ * |
+ * They are treated differently (although there are lots of similarities). |
+ * Both have in common that they have |
+ * a [JsGetName.CALL_CATCH_ALL] and |
+ * a [JsGetName.REQUIRED_PARAMETER_PROPERTY] property. |
+ * |
+ * If the closure supports optional parameters, then they also feature |
+ * a [JsGetName.DEFAULT_VALUES_PROPERTY] property. |
+ * |
+ * The catch-all property is a method that takes all arguments (including |
+ * all optional positional or named arguments). If the function accepts |
+ * optional arguments, then the default-values property stores (potentially |
+ * wrapped in a function) the default values for the optional arguments. If |
+ * the function accepts optional positional arguments, then the value is a |
+ * JavaScript array with the default values. Otherwise, when the function |
+ * accepts optional named arguments, it is a JavaScript object. |
+ * |
+ * The default-values property may either contain the value directly, or |
+ * it can be a function that returns the default-values when invoked. |
+ * |
+ * If the function is an anonymous closure, then the catch-all property |
+ * only contains a string pointing to the property that should be used |
+ * instead. For example, if the catch-all property contains the string |
+ * "call$4", then the object's "call$4" property should be used as if it was |
+ * the value of the catch-all property. |
+ */ |
+ static applyFunction2(Function function, |
+ List positionalArguments, |
+ Map<String, dynamic> namedArguments) { |
+ // Fast shortcut for the common case. |
+ if (JS('bool', '# instanceof Array', positionalArguments) && |
+ (namedArguments == null || namedArguments.isEmpty)) { |
+ // Let the compiler know that we did a type-test. |
+ List arguments = (JS('JSArray', '#', positionalArguments)); |
+ int argumentCount = arguments.length; |
+ if (argumentCount == 0) { |
+ String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX0); |
+ if (JS('bool', '!!#[#]', function, selectorName)) { |
+ return JS('', '#[#]()', function, selectorName); |
+ } |
+ } else if (argumentCount == 1) { |
+ String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX1); |
+ if (JS('bool', '!!#[#]', function, selectorName)) { |
+ return JS('', '#[#](#[0])', function, selectorName, arguments); |
+ } |
+ } else if (argumentCount == 2) { |
+ String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX2); |
+ if (JS('bool', '!!#[#]', function, selectorName)) { |
+ return JS('', '#[#](#[0],#[1])', function, selectorName, |
+ arguments, arguments); |
+ } |
+ } else if (argumentCount == 3) { |
+ String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX3); |
+ if (JS('bool', '!!#[#]', function, selectorName)) { |
+ return JS('', '#[#](#[0],#[1],#[2])', function, selectorName, |
+ arguments, arguments, arguments); |
+ } |
} |
String selectorName = |
'${JS_GET_NAME(JsGetName.CALL_PREFIX)}\$$argumentCount'; |
var jsStub = JS('var', r'#[#]', function, selectorName); |
- if (jsStub == null) { |
- // Do a dynamic call. |
- var interceptor = getInterceptor(function); |
- var jsFunction = JS('', '#[#]', interceptor, |
- JS_GET_NAME(JsGetName.CALL_CATCH_ALL)); |
- var defaultValues = JS('var', r'#[#]', function, |
- JS_GET_NAME(JsGetName.DEFAULT_VALUES_PROPERTY)); |
- if (!JS('bool', '# instanceof Array', defaultValues)) { |
- // The function expects named arguments! |
- return functionNoSuchMethod(function, positionalArguments, null); |
- } |
- int defaultsLength = JS('int', "#.length", defaultValues); |
- int maxArguments = requiredParameterCount + defaultsLength; |
- if (argumentCount > maxArguments) { |
- // The function expects less arguments! |
- return functionNoSuchMethod(function, positionalArguments, null); |
- } |
- List arguments = new List.from(positionalArguments); |
- List missingDefaults = JS('JSArray', '#.slice(#)', defaultValues, |
- argumentCount - requiredParameterCount); |
- arguments.addAll(missingDefaults); |
- return JS('var', '#.apply(#, #)', jsFunction, function, arguments); |
+ if (jsStub != null) { |
+ return JS('var', '#.apply(#, #)', jsStub, function, arguments); |
+ } |
+ } |
+ |
+ return _genericApplyFunction2( |
+ function, positionalArguments, namedArguments); |
+ } |
+ |
+ static _genericApplyFunction2(Function function, |
+ List positionalArguments, |
+ Map<String, dynamic> namedArguments) { |
+ List arguments; |
+ if (positionalArguments != null) { |
+ if (JS('bool', '# instanceof Array', positionalArguments)) { |
+ arguments = JS('JSArray', '#', positionalArguments); |
+ } else { |
+ arguments = new List.from(positionalArguments); |
} |
- return JS('var', '#.apply(#, #)', jsStub, function, positionalArguments); |
} else { |
- var interceptor = getInterceptor(function); |
- var jsFunction = JS('', '#[#]', interceptor, |
- JS_GET_NAME(JsGetName.CALL_CATCH_ALL)); |
- var defaultValues = JS('JSArray', r'#[#]', function, |
+ arguments = []; |
+ } |
+ |
+ int argumentCount = arguments.length; |
+ |
+ int requiredParameterCount = JS('int', r'#[#]', function, |
+ JS_GET_NAME(JsGetName.REQUIRED_PARAMETER_PROPERTY)); |
+ |
+ if (argumentCount < requiredParameterCount) { |
+ return functionNoSuchMethod(function, arguments, namedArguments); |
+ } |
+ |
+ var defaultValues = JS('var', r'#[#]', function, |
JS_GET_NAME(JsGetName.DEFAULT_VALUES_PROPERTY)); |
+ if (JS('bool', 'typeof # == "function"', defaultValues)) { |
+ // Anonymous closures return a function that returns the default values |
+ // instead of providing the default values directly. |
+ defaultValues = JS('', '#()', defaultValues); |
+ } |
+ bool acceptsOptionalArguments = defaultValues != null; |
+ |
+ var interceptor = getInterceptor(function); |
+ var jsFunction = JS('', '#[#]', interceptor, |
+ JS_GET_NAME(JsGetName.CALL_CATCH_ALL)); |
+ if (jsFunction is String) { |
+ // Anonymous closures redirect to the catch-all property instead of |
+ // storing the catch-all method directly in the catch-all property. |
+ jsFunction = JS('', '#[#]', interceptor, jsFunction); |
+ } |
+ |
+ if (!acceptsOptionalArguments) { |
+ if (argumentCount == requiredParameterCount) { |
+ return JS('var', r'#.apply(#, #)', jsFunction, function, arguments); |
+ } |
+ return functionNoSuchMethod(function, arguments, namedArguments); |
+ } |
+ |
+ bool acceptsPositionalArguments = |
+ JS('bool', '# instanceof Array', defaultValues); |
+ |
+ if (acceptsPositionalArguments) { |
+ if (namedArguments != null && namedArguments.isNotEmpty) { |
+ // Tried to invoke a function that takes optional positional arguments |
+ // with named arguments. |
+ return functionNoSuchMethod(function, arguments, namedArguments); |
+ } |
+ |
+ int defaultsLength = JS('int', "#.length", defaultValues); |
+ int maxArguments = requiredParameterCount + defaultsLength; |
+ if (argumentCount > maxArguments) { |
+ // The function expects fewer arguments. |
+ return functionNoSuchMethod(function, arguments, null); |
+ } |
+ List missingDefaults = JS('JSArray', '#.slice(#)', defaultValues, |
+ argumentCount - requiredParameterCount); |
+ arguments.addAll(missingDefaults); |
+ return JS('var', '#.apply(#, #)', jsFunction, function, arguments); |
+ } else { |
+ // Handle named arguments. |
+ |
+ if (argumentCount > requiredParameterCount) { |
+ // Tried to invoke a function that takes named parameters with |
+ // too many positional arguments. |
+ return functionNoSuchMethod(function, arguments, namedArguments); |
+ } |
+ |
List keys = JS('JSArray', r'Object.keys(#)', defaultValues); |
- List arguments = new List.from(positionalArguments); |
- int used = 0; |
- for (String key in keys) { |
- var value = namedArguments[key]; |
- if (value != null) { |
- used++; |
- arguments.add(value); |
- } else { |
- arguments.add(JS('var', r'#[#]', defaultValues, key)); |
+ if (namedArguments == null) { |
+ for (String key in keys) { |
+ arguments.add(JS('var', '#[#]', defaultValues, key)); |
+ } |
+ } else { |
+ int used = 0; |
+ for (String key in keys) { |
+ if (namedArguments.containsKey(key)) { |
+ used++; |
+ arguments.add(namedArguments[key]); |
+ } else { |
+ arguments.add(JS('var', r'#[#]', defaultValues, key)); |
+ } |
+ } |
+ if (used != namedArguments.length) { |
+ return functionNoSuchMethod(function, arguments, namedArguments); |
} |
- } |
- if (used != namedArguments.length) { |
- return functionNoSuchMethod(function, positionalArguments, |
- namedArguments); |
} |
return JS('var', r'#.apply(#, #)', jsFunction, function, arguments); |
} |