| 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);
|
| }
|
|
|