diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index d343a0439d..2cee695190 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -332,6 +332,34 @@ impl BuiltInFunctionObject { let start = if !args.is_empty() { 1 } else { 0 }; context.call(this, &this_arg, &args[start..]) } + + /// `Function.prototype.apply` + /// + /// The apply() method invokes self with the first argument as the `this` value + /// and the rest of the arguments provided as an array (or an array-like object). + /// + /// More information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.apply + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply + fn apply(this: &Value, args: &[Value], context: &mut Context) -> Result { + if !this.is_function() { + return context.throw_type_error(format!("{} is not a function", this.display())); + } + let this_arg = args.get(0).cloned().unwrap_or_default(); + let arg_array = args.get(1).cloned().unwrap_or_default(); + if arg_array.is_null_or_undefined() { + // TODO?: 3.a. PrepareForTailCall + return context.call(this, &this_arg, &[]); + } + let arg_list = context + .extract_array_properties(&arg_array) + .map_err(|()| arg_array)?; + // TODO?: 5. PrepareForTailCall + context.call(this, &this_arg, &arg_list) + } } impl BuiltIn for BuiltInFunctionObject { @@ -360,6 +388,7 @@ impl BuiltIn for BuiltInFunctionObject { .name(Self::NAME) .length(Self::LENGTH) .method(Self::call, "call", 1) + .method(Self::apply, "apply", 1) .build(); (Self::NAME, function_object.into(), Self::attribute()) diff --git a/boa/src/builtins/function/tests.rs b/boa/src/builtins/function/tests.rs index df8b09efdf..ebc0e9c78b 100644 --- a/boa/src/builtins/function/tests.rs +++ b/boa/src/builtins/function/tests.rs @@ -163,3 +163,52 @@ fn function_prototype_call_multiple_args() { .unwrap(); assert!(boolean); } + +#[test] +fn function_prototype_apply() { + let mut engine = Context::new(); + let init = r#" + const numbers = [6, 7, 3, 4, 2]; + const max = Math.max.apply(null, numbers); + const min = Math.min.apply(null, numbers); + "#; + forward_val(&mut engine, init).unwrap(); + + let boolean = forward_val(&mut engine, "max == 7") + .unwrap() + .as_boolean() + .unwrap(); + assert!(boolean); + + let boolean = forward_val(&mut engine, "min == 2") + .unwrap() + .as_boolean() + .unwrap(); + assert!(boolean); +} + +#[test] +fn function_prototype_apply_on_object() { + let mut engine = Context::new(); + let init = r#" + function f(a, b) { + this.a = a; + this.b = b; + } + let o = {a: 0, b: 0}; + f.apply(o, [1, 2]); + "#; + forward_val(&mut engine, init).unwrap(); + + let boolean = forward_val(&mut engine, "o.a == 1") + .unwrap() + .as_boolean() + .unwrap(); + assert!(boolean); + + let boolean = forward_val(&mut engine, "o.b == 2") + .unwrap() + .as_boolean() + .unwrap(); + assert!(boolean); +}