Skip to content

Commit

Permalink
Feature Function.prototype.call (#805)
Browse files Browse the repository at this point in the history
  • Loading branch information
RageKnify committed Oct 6, 2020
1 parent 03f9632 commit b027a76
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 11 deletions.
21 changes: 21 additions & 0 deletions boa/src/builtins/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,26 @@ impl BuiltInFunctionObject {
fn prototype(_: &Value, _: &[Value], _: &mut Context) -> Result<Value> {
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<Value> {
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 {
Expand Down Expand Up @@ -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())
Expand Down
49 changes: 49 additions & 0 deletions boa/src/builtins/function/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
17 changes: 8 additions & 9 deletions boa/src/builtins/object/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]\"");
Expand Down
7 changes: 5 additions & 2 deletions boa/src/builtins/regexp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Value> {
pub(crate) fn to_string(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
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)))
}
Expand Down

0 comments on commit b027a76

Please sign in to comment.