From b027a76bea3dab42fc929e621587b43eda667731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Borges?= Date: Tue, 6 Oct 2020 16:41:45 +0100 Subject: [PATCH] Feature `Function.prototype.call` (#805) --- boa/src/builtins/function/mod.rs | 21 +++++++++++++ boa/src/builtins/function/tests.rs | 49 ++++++++++++++++++++++++++++++ boa/src/builtins/object/tests.rs | 17 +++++------ boa/src/builtins/regexp/mod.rs | 7 +++-- 4 files changed, 83 insertions(+), 11 deletions(-) diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index f7e26bdca8b..365b0ffb8a7 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -312,6 +312,26 @@ impl BuiltInFunctionObject { fn prototype(_: &Value, _: &[Value], _: &mut Context) -> Result { Ok(Value::undefined()) } + + /// `Function.prototype.call` + /// + /// The call() method invokes self with the first argument as the `this` value. + /// + /// More information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.call + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call + fn call(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: Value = args.get(0).cloned().unwrap_or_default(); + // TODO?: 3. Perform PrepareForTailCall + let start = if !args.is_empty() { 1 } else { 0 }; + context.call(this, &this_arg, &args[start..]) + } } impl BuiltIn for BuiltInFunctionObject { @@ -339,6 +359,7 @@ impl BuiltIn for BuiltInFunctionObject { ) .name(Self::NAME) .length(Self::LENGTH) + .method(Self::call, "call", 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 1bcc7f417c3..df8b09efdf7 100644 --- a/boa/src/builtins/function/tests.rs +++ b/boa/src/builtins/function/tests.rs @@ -114,3 +114,52 @@ fn function_prototype_length() { assert!(value.is_number()); assert_eq!(value.as_number().unwrap(), 0.0); } + +#[test] +fn function_prototype_call() { + let mut engine = Context::new(); + let func = r#" + let e = new Error() + Object.prototype.toString.call(e) + "#; + let value = forward_val(&mut engine, func).unwrap(); + assert!(value.is_string()); + assert_eq!(value.as_string().unwrap(), "[object Error]"); +} + +#[test] +fn function_prototype_call_throw() { + let mut engine = Context::new(); + let throw = r#" + let call = Function.prototype.call; + call(call) + "#; + let value = forward_val(&mut engine, throw).unwrap_err(); + assert!(value.is_object()); + let string = value.to_string(&mut engine).unwrap(); + assert!(string.starts_with("TypeError")) +} + +#[test] +fn function_prototype_call_multiple_args() { + 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.call(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); +} diff --git a/boa/src/builtins/object/tests.rs b/boa/src/builtins/object/tests.rs index 6ff8ea4fbbd..0c8eae2999e 100644 --- a/boa/src/builtins/object/tests.rs +++ b/boa/src/builtins/object/tests.rs @@ -161,15 +161,14 @@ fn object_to_string() { let o = Object(); "#; eprintln!("{}", forward(&mut ctx, init)); - // TODO: need Function.prototype.call to be implemented - // assert_eq!( - // forward(&mut ctx, "Object.prototype.toString.call(u)"), - // "\"[object Undefined]\"" - // ); - // assert_eq!( - // forward(&mut ctx, "Object.prototype.toString.call(n)"), - // "\"[object Null]\"" - // ); + assert_eq!( + forward(&mut ctx, "Object.prototype.toString.call(u)"), + "\"[object Undefined]\"" + ); + assert_eq!( + forward(&mut ctx, "Object.prototype.toString.call(n)"), + "\"[object Null]\"" + ); assert_eq!(forward(&mut ctx, "a.toString()"), "\"[object Array]\""); assert_eq!(forward(&mut ctx, "f.toString()"), "\"[object Function]\""); assert_eq!(forward(&mut ctx, "e.toString()"), "\"[object Error]\""); diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 327c0997aea..b9c6c30becf 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -438,12 +438,15 @@ impl RegExp { /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/toString #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_string(this: &Value, _: &[Value], _: &mut Context) -> Result { + pub(crate) fn to_string(this: &Value, _: &[Value], context: &mut Context) -> Result { let (body, flags) = if let Some(object) = this.as_object() { let regex = object.as_regexp().unwrap(); (regex.original_source.clone(), regex.flags.clone()) } else { - panic!("object is not an object") + return context.throw_type_error(format!( + "Method RegExp.prototype.toString called on incompatible receiver {}", + this.display() + )); }; Ok(Value::from(format!("/{}/{}", body, flags))) }