From 9f845465bf97ad6e3558e497c7a3151b0a57e609 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 10 Oct 2021 19:40:18 -0500 Subject: [PATCH] Refactor `Function` internal methods and implement `BoundFunction` objects (#1583) * Refactor `[[Call]]` and `[[Construct]]` to be InternalFunctions * Implement `BoundFunction` exotic objects * Implement remaining `Function` builtin methods * Lift `OrdinaryHasInstance` to be inside JsValue * Implement `JsValue::InstanceOf` operator inside JsValue * Implement `JsValue::[as/is]_[callable/constructor] * Enable mutability for the `Closure` type inside closures --- boa/examples/closures.rs | 15 +- boa/src/builtins/array/array_iterator.rs | 88 ++-- boa/src/builtins/array/mod.rs | 155 +++--- boa/src/builtins/array_buffer/mod.rs | 45 +- boa/src/builtins/bigint/mod.rs | 25 +- boa/src/builtins/boolean/mod.rs | 16 +- boa/src/builtins/boolean/tests.rs | 2 +- boa/src/builtins/date/mod.rs | 10 +- boa/src/builtins/function/mod.rs | 467 +++++++++++++----- boa/src/builtins/intrinsics.rs | 2 +- boa/src/builtins/iterable/mod.rs | 16 +- boa/src/builtins/json/mod.rs | 75 ++- boa/src/builtins/map/map_iterator.rs | 15 +- boa/src/builtins/map/mod.rs | 32 +- boa/src/builtins/number/mod.rs | 16 +- boa/src/builtins/object/for_in_iterator.rs | 92 ++-- boa/src/builtins/object/mod.rs | 23 +- boa/src/builtins/reflect/mod.rs | 24 +- boa/src/builtins/regexp/mod.rs | 137 +++-- .../builtins/regexp/regexp_string_iterator.rs | 132 +++-- boa/src/builtins/set/mod.rs | 62 +-- boa/src/builtins/set/set_iterator.rs | 109 ++-- boa/src/builtins/string/mod.rs | 68 ++- boa/src/builtins/string/string_iterator.rs | 67 ++- boa/src/builtins/symbol/mod.rs | 22 +- boa/src/builtins/typed_array/mod.rs | 84 ++-- boa/src/bytecompiler.rs | 2 +- boa/src/class.rs | 29 +- boa/src/context.rs | 18 +- boa/src/exec/tests.rs | 8 +- .../object/internal_methods/bound_function.rs | 100 ++++ boa/src/object/internal_methods/function.rs | 314 ++++++++++++ boa/src/object/internal_methods/mod.rs | 67 ++- boa/src/object/jsobject.rs | 338 +------------ boa/src/object/mod.rs | 148 +++--- boa/src/object/operations.rs | 111 ++++- boa/src/syntax/ast/node/declaration/mod.rs | 2 +- boa/src/syntax/ast/node/new/mod.rs | 16 +- boa/src/syntax/ast/node/object/mod.rs | 6 +- .../syntax/ast/node/operator/bin_op/mod.rs | 31 +- boa/src/value/mod.rs | 89 ++-- boa/src/value/operations.rs | 36 ++ boa/src/vm/code_block.rs | 13 +- boa/src/vm/mod.rs | 34 +- 44 files changed, 1733 insertions(+), 1428 deletions(-) create mode 100644 boa/src/object/internal_methods/bound_function.rs create mode 100644 boa/src/object/internal_methods/function.rs diff --git a/boa/examples/closures.rs b/boa/examples/closures.rs index 36eddf4099..c4d49eb204 100644 --- a/boa/examples/closures.rs +++ b/boa/examples/closures.rs @@ -21,6 +21,7 @@ fn main() -> Result<(), JsValue> { println!("Called `closure`"); // `variable` is captured from the main function. println!("variable = {}", variable); + println!(); // We return the moved variable as a `JsValue`. Ok(JsValue::new(variable)) @@ -52,7 +53,7 @@ fn main() -> Result<(), JsValue> { // Now, we execute some operations that return a `Clone` type let clone_variable = BigStruct { - greeting: JsString::from("Hello from Javascript!"), + greeting: JsString::from("Hello!"), object, }; @@ -73,7 +74,11 @@ fn main() -> Result<(), JsValue> { captures.greeting.as_str(), ]); + // We can also mutate the moved data inside the closure. + captures.greeting = format!("{} Hello!", captures.greeting).into(); + println!("{}", message); + println!(); // We convert `message` into `Jsvalue` to be able to return it. Ok(message.into()) @@ -100,7 +105,13 @@ fn main() -> Result<(), JsValue> { assert_eq!( context.eval("createMessage()")?, - "message from `Boa dev`: Hello from Javascript!".into() + "message from `Boa dev`: Hello!".into() + ); + + // The data mutates between calls + assert_eq!( + context.eval("createMessage(); createMessage();")?, + "message from `Boa dev`: Hello! Hello! Hello!".into() ); // We have moved `Clone` variables into a closure and executed that closure diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index 6583a2c84f..02e6377350 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -62,57 +62,53 @@ impl ArrayIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - if let JsValue::Object(ref object) = this { - let mut object = object.borrow_mut(); - if let Some(array_iterator) = object.as_array_iterator_mut() { - let index = array_iterator.next_index; - if array_iterator.done { - return Ok(create_iter_result_object( - JsValue::undefined(), - true, - context, - )); - } + let mut array_iterator = this.as_object().map(|obj| obj.borrow_mut()); + let array_iterator = array_iterator + .as_mut() + .and_then(|obj| obj.as_array_iterator_mut()) + .ok_or_else(|| context.construct_type_error("`this` is not an ArrayIterator"))?; + let index = array_iterator.next_index; + if array_iterator.done { + return Ok(create_iter_result_object( + JsValue::undefined(), + true, + context, + )); + } - let len = if let Some(f) = array_iterator.array.borrow().as_typed_array() { - if f.is_detached() { - return context.throw_type_error( - "Cannot get value from typed array that has a detached array buffer", - ); - } + let len = if let Some(f) = array_iterator.array.borrow().as_typed_array() { + if f.is_detached() { + return context.throw_type_error( + "Cannot get value from typed array that has a detached array buffer", + ); + } - f.array_length() - } else { - array_iterator.array.length_of_array_like(context)? - }; + f.array_length() + } else { + array_iterator.array.length_of_array_like(context)? + }; - if index >= len { - array_iterator.done = true; - return Ok(create_iter_result_object( - JsValue::undefined(), - true, - context, - )); - } - array_iterator.next_index = index + 1; - return match array_iterator.kind { - PropertyNameKind::Key => { - Ok(create_iter_result_object(index.into(), false, context)) - } - PropertyNameKind::Value => { - let element_value = array_iterator.array.get(index, context)?; - Ok(create_iter_result_object(element_value, false, context)) - } - PropertyNameKind::KeyAndValue => { - let element_value = array_iterator.array.get(index, context)?; - let result = - Array::create_array_from_list([index.into(), element_value], context); - Ok(create_iter_result_object(result.into(), false, context)) - } - }; + if index >= len { + array_iterator.done = true; + return Ok(create_iter_result_object( + JsValue::undefined(), + true, + context, + )); + } + array_iterator.next_index = index + 1; + match array_iterator.kind { + PropertyNameKind::Key => Ok(create_iter_result_object(index.into(), false, context)), + PropertyNameKind::Value => { + let element_value = array_iterator.array.get(index, context)?; + Ok(create_iter_result_object(element_value, false, context)) + } + PropertyNameKind::KeyAndValue => { + let element_value = array_iterator.array.get(index, context)?; + let result = Array::create_array_from_list([index.into(), element_value], context); + Ok(create_iter_result_object(result.into(), false, context)) } } - context.throw_type_error("`this` is not an ArrayIterator") } /// Create the %ArrayIteratorPrototype% object diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index cf32720fa1..6663983838 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -49,7 +49,7 @@ impl BuiltIn for Array { let get_species = FunctionBuilder::native(context, Self::get_species) .name("get [Symbol.species]") - .constructable(false) + .constructor(false) .build(); let values_function = Self::values_intrinsic(context); @@ -354,14 +354,12 @@ impl Array { } // 7. If IsConstructor(C) is false, throw a TypeError exception. - if let Some(c) = c.as_object() { - if !c.is_constructable() { - return Err(context.construct_type_error("Symbol.species must be a constructor")); - } + if let Some(c) = c.as_constructor() { // 8. Return ? Construct(C, « 𝔽(length) »). Ok( c.construct(&[JsValue::new(length)], &c.clone().into(), context)? .as_object() + .cloned() .unwrap(), ) } else { @@ -412,10 +410,12 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.isarray /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray pub(crate) fn is_array(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { - match args.get(0).and_then(|x| x.as_object()) { - Some(object) => Ok(JsValue::new(object.borrow().is_array())), - None => Ok(JsValue::new(false)), - } + Ok(args + .get_or_undefined(0) + .as_object() + .map(|obj| obj.borrow().is_array()) + .unwrap_or_default() + .into()) } /// `Array.of(...items)` @@ -439,10 +439,11 @@ impl Array { // a. Let A be ? Construct(C, « lenNumber »). // 5. Else, // a. Let A be ? ArrayCreate(len). - let a = match this.as_object() { - Some(object) if object.is_constructable() => object + let a = match this.as_constructor() { + Some(constructor) => constructor .construct(&[len.into()], this, context)? .as_object() + .cloned() .ok_or_else(|| { context.construct_type_error("object constructor didn't return an object") })?, @@ -683,15 +684,9 @@ impl Array { // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = if let Some(arg) = args - .get(0) - .and_then(JsValue::as_object) - .filter(JsObject::is_callable) - { - arg - } else { - return context.throw_type_error("Array.prototype.forEach: invalid callback function"); - }; + let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + context.construct_type_error("Array.prototype.forEach: invalid callback function") + })?; // 4. Let k be 0. // 5. Repeat, while k < len, for k in 0..len { @@ -792,7 +787,7 @@ impl Array { let func = array.get("join", context)?; // 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%. // 4. Return ? Call(func, array). - if let Some(func) = func.as_object().filter(JsObject::is_callable) { + if let Some(func) = func.as_callable() { func.call(&array.into(), &[], context) } else { crate::builtins::object::Object::to_string(&array.into(), &[], context) @@ -1030,15 +1025,9 @@ impl Array { // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = if let Some(arg) = args - .get(0) - .and_then(JsValue::as_object) - .filter(JsObject::is_callable) - { - arg - } else { - return context.throw_type_error("Array.prototype.every: callback is not callable"); - }; + let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + context.construct_type_error("Array.prototype.every: callback is not callable") + })?; let this_arg = args.get_or_undefined(1); @@ -1088,10 +1077,9 @@ impl Array { // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = args.get_or_undefined(0); - if !callback.is_function() { - return context.throw_type_error("Array.prototype.map: Callbackfn is not callable"); - } + let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + context.construct_type_error("Array.prototype.map: Callbackfn is not callable") + })?; // 4. Let A be ? ArraySpeciesCreate(O, len). let a = Self::array_species_create(&o, len, context)?; @@ -1110,7 +1098,7 @@ impl Array { let k_value = o.get(k, context)?; // ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). let mapped_value = - context.call(callback, this_arg, &[k_value, k.into(), this.into()])?; + callback.call(this_arg, &[k_value, k.into(), this.into()], context)?; // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). a.create_data_property_or_throw(k, mapped_value, context)?; } @@ -1297,12 +1285,9 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = match args.get(0).and_then(JsValue::as_object) { - Some(predicate) if predicate.is_callable() => predicate, - _ => { - return context.throw_type_error("Array.prototype.find: predicate is not callable") - } - }; + let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { + context.construct_type_error("Array.prototype.find: predicate is not callable") + })?; let this_arg = args.get_or_undefined(1); @@ -1357,13 +1342,9 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = match args.get(0).and_then(JsValue::as_object) { - Some(predicate) if predicate.is_callable() => predicate, - _ => { - return context - .throw_type_error("Array.prototype.reduce: predicate is not callable") - } - }; + let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { + context.construct_type_error("Array.prototype.reduce: predicate is not callable") + })?; let this_arg = args.get_or_undefined(1); @@ -1469,10 +1450,9 @@ impl Array { let source_len = o.length_of_array_like(context)?; // 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception. - let mapper_function = args.get_or_undefined(0); - if !mapper_function.is_function() { - return context.throw_type_error("flatMap mapper function is not callable"); - } + let mapper_function = args.get_or_undefined(0).as_callable().ok_or_else(|| { + context.construct_type_error("flatMap mapper function is not callable") + })?; // 4. Let A be ? ArraySpeciesCreate(O, 0). let a = Self::array_species_create(&o, 0, context)?; @@ -1484,7 +1464,7 @@ impl Array { source_len as u64, 0, 1, - Some(mapper_function.as_object().unwrap()), + Some(mapper_function.clone()), args.get_or_undefined(1), context, )?; @@ -1574,7 +1554,7 @@ impl Array { // 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth) target_index = Self::flatten_into_array( target, - &element, + element, element_len as u64, target_index, new_depth, @@ -1999,21 +1979,11 @@ impl Array { let length = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = args - .get(0) - .map(|a| a.to_object(context)) - .transpose()? - .ok_or_else(|| { - context.construct_type_error( - "missing argument 0 when calling function Array.prototype.filter", - ) - })?; + let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + context.construct_type_error("Array.prototype.filter: `callback` must be callable") + })?; let this_arg = args.get_or_undefined(1); - if !callback.is_callable() { - return context.throw_type_error("the callback must be callable"); - } - // 4. Let A be ? ArraySpeciesCreate(O, 0). let a = Self::array_species_create(&o, 0, context)?; @@ -2073,15 +2043,9 @@ impl Array { // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = if let Some(arg) = args - .get(0) - .and_then(JsValue::as_object) - .filter(JsObject::is_callable) - { - arg - } else { - return context.throw_type_error("Array.prototype.some: callback is not callable"); - }; + let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + context.construct_type_error("Array.prototype.some: callback is not callable") + })?; // 4. Let k be 0. // 5. Repeat, while k < len, @@ -2127,10 +2091,9 @@ impl Array { context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - let comparefn = match args.get(0).cloned() { - // todo: change to `is_callable` inside `JsValue` - Some(fun) if fun.is_function() => fun, - None => JsValue::undefined(), + let comparefn = match args.get_or_undefined(0) { + JsValue::Object(ref obj) if obj.is_callable() => Some(obj), + JsValue::Undefined => None, _ => { return context.throw_type_error( "The comparison function must be either a function or undefined", @@ -2157,11 +2120,11 @@ impl Array { } // 4. If comparefn is not undefined, then - if !comparefn.is_undefined() { + if let Some(cmp) = comparefn { let args = [x.clone(), y.clone()]; // a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)). - let v = context - .call(&comparefn, &JsValue::Undefined, &args)? + let v = cmp + .call(&JsValue::Undefined, &args, context)? .to_number(context)?; // b. If v is NaN, return +0𝔽. // c. Return v. @@ -2265,13 +2228,10 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = match args.get(0).and_then(JsValue::as_object) { - Some(callback) if callback.is_callable() => callback, - _ => { - return context - .throw_type_error("Array.prototype.reduce: callback function is not callable") - } - }; + let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + context + .construct_type_error("Array.prototype.reduce: callback function is not callable") + })?; // 4. If len = 0 and initialValue is not present, throw a TypeError exception. if len == 0 && args.get(1).is_none() { @@ -2363,14 +2323,11 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = match args.get(0).and_then(JsValue::as_object) { - Some(callback) if callback.is_callable() => callback, - _ => { - return context.throw_type_error( - "Array.prototype.reduceRight: callback function is not callable", - ) - } - }; + let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + context.construct_type_error( + "Array.prototype.reduceRight: callback function is not callable", + ) + })?; // 4. If len is 0 and initialValue is not present, throw a TypeError exception. if len == 0 && args.get(1).is_none() { @@ -2661,7 +2618,7 @@ impl Array { FunctionBuilder::native(context, Self::values) .name("values") .length(0) - .constructable(false) + .constructor(false) .build() } } diff --git a/boa/src/builtins/array_buffer/mod.rs b/boa/src/builtins/array_buffer/mod.rs index 8ac9d29fc7..acb57174c6 100644 --- a/boa/src/builtins/array_buffer/mod.rs +++ b/boa/src/builtins/array_buffer/mod.rs @@ -40,7 +40,7 @@ impl BuiltIn for ArrayBuffer { let get_species = FunctionBuilder::native(context, Self::get_species) .name("get [Symbol.species]") - .constructable(false) + .constructor(false) .build(); ConstructorBuilder::with_standard_object( @@ -229,35 +229,38 @@ impl ArrayBuffer { let ctor = obj.species_constructor(StandardObjects::array_buffer_object, context)?; // 16. Let new be ? Construct(ctor, « 𝔽(newLen) »). - let new = Self::constructor(&ctor.into(), &[new_len.into()], context)?; + let new = ctor.construct(&[new_len.into()], &ctor.clone().into(), context)?; // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). - let new_obj = if let Some(obj) = new.as_object() { - obj - } else { - return context.throw_type_error("ArrayBuffer constructor returned non-object value"); - }; - let mut new_obj_borrow = new_obj.borrow_mut(); - let new_array_buffer = if let Some(b) = new_obj_borrow.as_array_buffer_mut() { - b - } else { - return context.throw_type_error("ArrayBuffer constructor returned invalid object"); - }; + let new_obj = new.as_object().cloned().ok_or_else(|| { + context.construct_type_error("ArrayBuffer constructor returned non-object value") + })?; - // TODO: Shared Array Buffer - // 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. - - // 19. If IsDetachedBuffer(new) is true, throw a TypeError exception. - if Self::is_detached_buffer(new_array_buffer) { - return context - .throw_type_error("ArrayBuffer constructor returned detached ArrayBuffer"); + { + let new_obj = new_obj.borrow(); + let new_array_buffer = new_obj.as_array_buffer().ok_or_else(|| { + context.construct_type_error("ArrayBuffer constructor returned invalid object") + })?; + + // TODO: Shared Array Buffer + // 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. + + // 19. If IsDetachedBuffer(new) is true, throw a TypeError exception. + if new_array_buffer.is_detached_buffer() { + return context + .throw_type_error("ArrayBuffer constructor returned detached ArrayBuffer"); + } } - // 20. If SameValue(new, O) is true, throw a TypeError exception. if JsValue::same_value(&new, this) { return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer"); } + let mut new_obj_borrow = new_obj.borrow_mut(); + let new_array_buffer = new_obj_borrow + .as_array_buffer_mut() + .expect("Already checked that `new_obj` was an `ArrayBuffer`"); + // 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception. if new_array_buffer.array_buffer_byte_length < new_len { return context.throw_type_error("New ArrayBuffer length too small"); diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index 4d76268361..4266725517 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -53,7 +53,7 @@ impl BuiltIn for BigInt { .static_method(Self::as_int_n, "asIntN", 2) .static_method(Self::as_uint_n, "asUintN", 2) .callable(true) - .constructable(false) + .constructor(false) .property( to_string_tag, Self::NAME, @@ -123,23 +123,20 @@ impl BigInt { /// [spec]: https://tc39.es/ecma262/#sec-thisbigintvalue #[inline] fn this_bigint_value(value: &JsValue, context: &mut Context) -> JsResult { - match value { + value // 1. If Type(value) is BigInt, return value. - JsValue::BigInt(ref bigint) => return Ok(bigint.clone()), - + .as_bigint() + .cloned() // 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then // a. Assert: Type(value.[[BigIntData]]) is BigInt. // b. Return value.[[BigIntData]]. - JsValue::Object(ref object) => { - if let Some(bigint) = object.borrow().as_bigint() { - return Ok(bigint.clone()); - } - } - _ => {} - } - - // 3. Throw a TypeError exception. - Err(context.construct_type_error("'this' is not a BigInt")) + .or_else(|| { + value + .as_object() + .and_then(|obj| obj.borrow().as_bigint().cloned()) + }) + // 3. Throw a TypeError exception. + .ok_or_else(|| context.construct_type_error("'this' is not a BigInt")) } /// `BigInt.prototype.toString( [radix] )` diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 02c31e2db8..84aa2fd486 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -83,18 +83,10 @@ impl Boolean { /// /// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue fn this_boolean_value(value: &JsValue, context: &mut Context) -> JsResult { - match value { - JsValue::Boolean(boolean) => return Ok(*boolean), - JsValue::Object(ref object) => { - let object = object.borrow(); - if let Some(boolean) = object.as_boolean() { - return Ok(boolean); - } - } - _ => {} - } - - Err(context.construct_type_error("'this' is not a boolean")) + value + .as_boolean() + .or_else(|| value.as_object().and_then(|obj| obj.borrow().as_boolean())) + .ok_or_else(|| context.construct_type_error("'this' is not a boolean")) } /// The `toString()` method returns a string representing the specified `Boolean` object. diff --git a/boa/src/builtins/boolean/tests.rs b/boa/src/builtins/boolean/tests.rs index 57360aae7a..77db02ccf3 100644 --- a/boa/src/builtins/boolean/tests.rs +++ b/boa/src/builtins/boolean/tests.rs @@ -60,6 +60,6 @@ fn instances_have_correct_proto_set() { assert_eq!( &*bool_instance.as_object().unwrap().prototype(), - &bool_prototype.as_object() + &bool_prototype.as_object().cloned() ); } diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index a2757019ea..1ca7bba1e8 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -1924,10 +1924,8 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-thistimevalue #[inline] pub fn this_time_value(value: &JsValue, context: &mut Context) -> JsResult { - if let JsValue::Object(ref object) = value { - if let Some(date) = object.borrow().as_date() { - return Ok(*date); - } - } - Err(context.construct_type_error("'this' is not a Date")) + value + .as_object() + .and_then(|obj| obj.borrow().as_date().copied()) + .ok_or_else(|| context.construct_type_error("'this' is not a Date")) } diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 658b061390..4d74eaad03 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -12,11 +12,14 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function use std::{ + any::Any, + borrow::Cow, fmt, ops::{Deref, DerefMut}, }; use dyn_clone::DynClone; +use gc::{Gc, GcCell}; use crate::{ builtins::BuiltIn, @@ -24,15 +27,22 @@ use crate::{ environment::lexical_environment::Environment, gc::{Finalize, Trace}, object::JsObject, - object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - NativeObject, ObjectData, - }, + object::{internal_methods::get_prototype_from_constructor, NativeObject, ObjectData}, property::Attribute, property::PropertyDescriptor, syntax::ast::node::{FormalParameter, RcStatementList}, BoaProfiler, Context, JsResult, JsValue, }; +use crate::{object::Object, symbol::WellKnownSymbols}; +use crate::{ + object::{ConstructorBuilder, FunctionBuilder}, + property::PropertyKey, + JsString, +}; +use crate::{ + object::{Ref, RefMut}, + value::IntegerOrInfinity, +}; use super::JsArgs; @@ -66,7 +76,6 @@ pub trait ClosureFunctionSignature: { } -// The `Copy` bound automatically infers `DynCopy` and `DynClone` impl ClosureFunctionSignature for T where T: Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult + Copy + 'static { @@ -116,80 +125,45 @@ impl ConstructorKind { matches!(self, Self::Derived) } } -// We don't use a standalone `NativeObject` for `Captures` because it doesn't -// guarantee that the internal type implements `Clone`. -// This private trait guarantees that the internal type passed to `Captures` -// implements `Clone`, and `DynClone` allows us to implement `Clone` for -// `Box`. -trait CapturesObject: NativeObject + DynClone {} -impl CapturesObject for T {} -dyn_clone::clone_trait_object!(CapturesObject); - -/// Wrapper for `Box` that allows passing additional + +/// Wrapper for `Gc>` that allows passing additional /// captures through a `Copy` closure. /// -/// Any type implementing `Trace + Any + Debug + Clone` +/// Any type implementing `Trace + Any + Debug` /// can be used as a capture context, so you can pass e.g. a String, /// a tuple or even a full struct. /// -/// You can downcast to any type and handle the fail case as you like -/// with `downcast_ref` and `downcast_mut`, or you can use `try_downcast_ref` -/// and `try_downcast_mut` to automatically throw a `TypeError` if the downcast -/// fails. -#[derive(Debug, Clone, Trace, Finalize)] -pub struct Captures(Box); +/// You can cast to `Any` with `as_any`, `as_mut_any` and downcast +/// with `Any::downcast_ref` and `Any::downcast_mut` to recover the original +/// type. +#[derive(Clone, Debug, Trace, Finalize)] +pub struct Captures(Gc>>); impl Captures { /// Creates a new capture context. pub(crate) fn new(captures: T) -> Self where - T: NativeObject + Clone, + T: NativeObject, { - Self(Box::new(captures)) + Self(Gc::new(GcCell::new(Box::new(captures)))) } - /// Downcasts `Captures` to the specified type, returning a reference to the - /// downcasted type if successful or `None` otherwise. - pub fn downcast_ref(&self) -> Option<&T> - where - T: NativeObject + Clone, - { - self.0.deref().as_any().downcast_ref::() - } - - /// Mutably downcasts `Captures` to the specified type, returning a - /// mutable reference to the downcasted type if successful or `None` otherwise. - pub fn downcast_mut(&mut self) -> Option<&mut T> - where - T: NativeObject + Clone, - { - self.0.deref_mut().as_mut_any().downcast_mut::() - } - - /// Downcasts `Captures` to the specified type, returning a reference to the - /// downcasted type if successful or a `TypeError` otherwise. - pub fn try_downcast_ref(&self, context: &mut Context) -> JsResult<&T> - where - T: NativeObject + Clone, - { - self.0 - .deref() - .as_any() - .downcast_ref::() - .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) + /// Casts `Captures` to `Any` + /// + /// # Panics + /// + /// Panics if it's already borrowed as `&mut Any` + pub fn as_any(&self) -> gc::GcCellRef<'_, dyn Any> { + Ref::map(self.0.borrow(), |data| data.deref().as_any()) } - /// Downcasts `Captures` to the specified type, returning a reference to the - /// downcasted type if successful or a `TypeError` otherwise. - pub fn try_downcast_mut(&mut self, context: &mut Context) -> JsResult<&mut T> - where - T: NativeObject + Clone, - { - self.0 - .deref_mut() - .as_mut_any() - .downcast_mut::() - .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) + /// Mutably casts `Captures` to `Any` + /// + /// # Panics + /// + /// Panics if it's already borrowed as `&mut Any` + pub fn as_mut_any(&self) -> gc::GcCellRefMut<'_, Box, dyn Any> { + RefMut::map(self.0.borrow_mut(), |data| data.deref_mut().as_mut_any()) } } @@ -198,21 +172,21 @@ impl Captures { /// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node) /// /// -#[derive(Clone, Trace, Finalize)] +#[derive(Trace, Finalize)] pub enum Function { Native { #[unsafe_ignore_trace] function: NativeFunctionSignature, - constructable: bool, + constructor: bool, }, Closure { #[unsafe_ignore_trace] function: Box, - constructable: bool, + constructor: bool, captures: Captures, }, Ordinary { - constructable: bool, + constructor: bool, this_mode: ThisMode, body: RcStatementList, params: Box<[FormalParameter]>, @@ -220,7 +194,7 @@ pub enum Function { }, #[cfg(feature = "vm")] VmOrdinary { - code: gc::Gc, + code: Gc, environment: Environment, }, } @@ -233,7 +207,6 @@ impl fmt::Debug for Function { impl Function { // Adds the final rest parameters to the Environment as an array - #[cfg(not(feature = "vm"))] pub(crate) fn add_rest_param( param: &FormalParameter, index: usize, @@ -277,14 +250,14 @@ impl Function { .expect("Failed to intialize binding"); } - /// Returns true if the function object is constructable. - pub fn is_constructable(&self) -> bool { + /// Returns true if the function object is a constructor. + pub fn is_constructor(&self) -> bool { match self { - Self::Native { constructable, .. } => *constructable, - Self::Closure { constructable, .. } => *constructable, - Self::Ordinary { constructable, .. } => *constructable, + Self::Native { constructor, .. } => *constructor, + Self::Closure { constructor, .. } => *constructor, + Self::Ordinary { constructor, .. } => *constructor, #[cfg(feature = "vm")] - Self::VmOrdinary { code, .. } => code.constructable, + Self::VmOrdinary { code, .. } => code.constructor, } } } @@ -324,7 +297,7 @@ pub(crate) fn make_builtin_fn( interpreter.standard_objects().function_object().prototype(), ObjectData::function(Function::Native { function, - constructable: false, + constructor: false, }), ); let attribute = PropertyDescriptor::builder() @@ -362,38 +335,14 @@ impl BuiltInFunctionObject { prototype, ObjectData::function(Function::Native { function: |_, _, _| Ok(JsValue::undefined()), - constructable: true, + constructor: true, }), ); Ok(this.into()) } - fn prototype(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Ok(JsValue::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: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - if !this.is_function() { - return context.throw_type_error(format!("{} is not a function", this.display())); - } - let this_arg = args.get_or_undefined(0); - // TODO?: 3. Perform PrepareForTailCall - let start = if !args.is_empty() { 1 } else { 0 }; - context.call(this, this_arg, &args[start..]) - } - - /// `Function.prototype.apply` + /// `Function.prototype.apply ( thisArg, argArray )` /// /// 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). @@ -405,26 +354,156 @@ impl BuiltInFunctionObject { /// [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: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - if !this.is_function() { - return context.throw_type_error(format!("{} is not a function", this.display())); - } + // 1. Let func be the this value. + // 2. If IsCallable(func) is false, throw a TypeError exception. + let func = this.as_callable().ok_or_else(|| { + context.construct_type_error(format!("{} is not a function", this.display())) + })?; + let this_arg = args.get_or_undefined(0); let arg_array = args.get_or_undefined(1); + // 3. If argArray is undefined or null, then if arg_array.is_null_or_undefined() { + // a. Perform PrepareForTailCall(). // TODO?: 3.a. PrepareForTailCall - return context.call(this, this_arg, &[]); + + // b. Return ? Call(func, thisArg). + return func.call(this_arg, &[], context); } + + // 4. Let argList be ? CreateListFromArrayLike(argArray). let arg_list = arg_array.create_list_from_array_like(&[], context)?; + + // 5. Perform PrepareForTailCall(). // TODO?: 5. PrepareForTailCall - context.call(this, this_arg, &arg_list) + + // 6. Return ? Call(func, thisArg, argList). + func.call(this_arg, &arg_list, context) + } + + /// `Function.prototype.bind ( thisArg, ...args )` + /// + /// The bind() method creates a new function that, when called, has its + /// this keyword set to the provided value, with a given sequence of arguments + /// preceding any provided when the new function is called. + /// + /// More information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.bind + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind + fn bind(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let Target be the this value. + // 2. If IsCallable(Target) is false, throw a TypeError exception. + let target = this.as_callable().ok_or_else(|| { + context.construct_type_error("cannot bind `this` without a `[[Call]]` internal method") + })?; + + let this_arg = args.get_or_undefined(0).clone(); + let bound_args = args.get(1..).unwrap_or_else(|| &[]).to_vec(); + let arg_count = bound_args.len() as i64; + + // 3. Let F be ? BoundFunctionCreate(Target, thisArg, args). + let f = BoundFunction::create(target.clone(), this_arg, bound_args, context)?; + + // 4. Let L be 0. + let mut l = JsValue::new(0); + + // 5. Let targetHasLength be ? HasOwnProperty(Target, "length"). + // 6. If targetHasLength is true, then + if target.has_own_property("length", context)? { + // a. Let targetLen be ? Get(Target, "length"). + let target_len = target.get("length", context)?; + // b. If Type(targetLen) is Number, then + if target_len.is_number() { + // 1. Let targetLenAsInt be ! ToIntegerOrInfinity(targetLen). + match target_len + .to_integer_or_infinity(context) + .expect("to_integer_or_infinity cannot fail for a number") + { + // i. If targetLen is +∞𝔽, set L to +∞. + IntegerOrInfinity::PositiveInfinity => l = f64::INFINITY.into(), + // ii. Else if targetLen is -∞𝔽, set L to 0. + IntegerOrInfinity::NegativeInfinity => {} + // iii. Else, + IntegerOrInfinity::Integer(target_len) => { + // 2. Assert: targetLenAsInt is finite. + // 3. Let argCount be the number of elements in args. + // 4. Set L to max(targetLenAsInt - argCount, 0). + l = (target_len - arg_count).max(0).into(); + } + } + } + } + + // 7. Perform ! SetFunctionLength(F, L). + f.define_property_or_throw( + "length", + PropertyDescriptor::builder() + .value(l) + .writable(false) + .enumerable(false) + .configurable(true), + context, + ) + .expect("defining the `length` property for a new object should not fail"); + + // 8. Let targetName be ? Get(Target, "name"). + let target_name = target.get("name", context)?; + + // 9. If Type(targetName) is not String, set targetName to the empty String. + let target_name = target_name + .as_string() + .map_or(JsString::new(""), Clone::clone); + + // 10. Perform SetFunctionName(F, targetName, "bound"). + set_function_name(&f, &target_name.into(), Some("bound"), context); + + // 11. Return F. + Ok(f.into()) + } + + /// `Function.prototype.call ( thisArg, ...args )` + /// + /// The call() method calls a function with a given this value and arguments provided individually. + /// + /// 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: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let func be the this value. + // 2. If IsCallable(func) is false, throw a TypeError exception. + let func = this.as_callable().ok_or_else(|| { + context.construct_type_error(format!("{} is not a function", this.display())) + })?; + let this_arg = args.get_or_undefined(0); + + // 3. Perform PrepareForTailCall(). + // TODO?: 3. Perform PrepareForTailCall + + // 4. Return ? Call(func, thisArg, args). + func.call(this_arg, args.get(1..).unwrap_or(&[]), context) } #[allow(clippy::wrong_self_convention)] fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let object = this.as_object().map(JsObject::borrow); + let function = object + .as_deref() + .and_then(Object::as_function) + .ok_or_else(|| context.construct_type_error("Not a function"))?; + let name = { // Is there a case here where if there is no name field on a value // name should default to None? Do all functions have names set? - let value = this.get_field("name", &mut *context)?; + let value = this + .as_object() + .expect("checked that `this` was an object above") + .get("name", &mut *context)?; if value.is_null_or_undefined() { None } else { @@ -432,23 +511,11 @@ impl BuiltInFunctionObject { } }; - let function = { - let object = this - .as_object() - .map(|object| object.borrow().as_function().cloned()); - - if let Some(Some(function)) = object { - function - } else { - return context.throw_type_error("Not a function"); - } - }; - - match (&function, name) { + match (function, name) { ( Function::Native { function: _, - constructable: _, + constructor: _, }, Some(name), ) => Ok(format!("function {}() {{\n [native Code]\n}}", &name).into()), @@ -495,6 +562,22 @@ impl BuiltInFunctionObject { _ => Ok("TODO".into()), } } + + /// `Function.prototype [ @@hasInstance ] ( V )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function.prototype-@@hasinstance + fn has_instance(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let F be the this value. + // 2. Return ? OrdinaryHasInstance(F, V). + Ok(JsValue::ordinary_has_instance(this, args.get_or_undefined(0), context)?.into()) + } + + fn prototype(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + Ok(JsValue::undefined()) + } } impl BuiltIn for BuiltInFunctionObject { @@ -511,9 +594,17 @@ impl BuiltIn for BuiltInFunctionObject { FunctionBuilder::native(context, Self::prototype) .name("") .length(0) - .constructable(false) + .constructor(false) .build_function_prototype(&function_prototype); + let symbol_has_instance = WellKnownSymbols::has_instance(); + + let has_instance = FunctionBuilder::native(context, Self::has_instance) + .name("[Symbol.iterator]") + .length(1) + .constructor(false) + .build(); + let function_object = ConstructorBuilder::with_standard_object( context, Self::constructor, @@ -521,11 +612,137 @@ impl BuiltIn for BuiltInFunctionObject { ) .name(Self::NAME) .length(Self::LENGTH) - .method(Self::call, "call", 1) .method(Self::apply, "apply", 1) + .method(Self::bind, "bind", 1) + .method(Self::call, "call", 1) .method(Self::to_string, "toString", 0) + .property(symbol_has_instance, has_instance, Attribute::default()) .build(); function_object.into() } } + +/// Abstract operation `SetFunctionName` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-setfunctionname +fn set_function_name( + function: &JsObject, + name: &PropertyKey, + prefix: Option<&str>, + context: &mut Context, +) { + // 1. Assert: F is an extensible object that does not have a "name" own property. + // 2. If Type(name) is Symbol, then + let mut name = match name { + PropertyKey::Symbol(sym) => { + // a. Let description be name's [[Description]] value. + if let Some(desc) = sym.description() { + // c. Else, set name to the string-concatenation of "[", description, and "]". + Cow::Owned(JsString::concat_array(&["[", &desc, "]"])) + } else { + // b. If description is undefined, set name to the empty String. + Cow::Owned(JsString::new("")) + } + } + PropertyKey::String(string) => Cow::Borrowed(string), + PropertyKey::Index(index) => Cow::Owned(JsString::new(format!("{}", index))), + }; + + // 3. Else if name is a Private Name, then + // a. Set name to name.[[Description]]. + // todo: implement Private Names + + // 4. If F has an [[InitialName]] internal slot, then + // a. Set F.[[InitialName]] to name. + // todo: implement [[InitialName]] for builtins + + // 5. If prefix is present, then + if let Some(prefix) = prefix { + name = Cow::Owned(JsString::concat_array(&[prefix, " ", &name])); + // b. If F has an [[InitialName]] internal slot, then + // i. Optionally, set F.[[InitialName]] to name. + // todo: implement [[InitialName]] for builtins + } + + // 6. Return ! DefinePropertyOrThrow(F, "name", PropertyDescriptor { [[Value]]: name, + // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }). + function + .define_property_or_throw( + "name", + PropertyDescriptor::builder() + .value(name.into_owned()) + .writable(false) + .enumerable(false) + .configurable(true), + context, + ) + .expect("defining the `name` property must not fail per the spec"); +} + +/// Binds a `Function Object` when `bind` is called. +#[derive(Debug, Trace, Finalize)] +pub struct BoundFunction { + target_function: JsObject, + this: JsValue, + args: Vec, +} + +impl BoundFunction { + /// Abstract operation `BoundFunctionCreate` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-boundfunctioncreate + pub fn create( + target_function: JsObject, + this: JsValue, + args: Vec, + context: &mut Context, + ) -> JsResult { + // 1. Let proto be ? targetFunction.[[GetPrototypeOf]](). + let proto = target_function.__get_prototype_of__(context)?; + let is_constructor = target_function.is_constructor(); + + // 2. Let internalSlotsList be the internal slots listed in Table 35, plus [[Prototype]] and [[Extensible]]. + // 3. Let obj be ! MakeBasicObject(internalSlotsList). + // 4. Set obj.[[Prototype]] to proto. + // 5. Set obj.[[Call]] as described in 10.4.1.1. + // 6. If IsConstructor(targetFunction) is true, then + // a. Set obj.[[Construct]] as described in 10.4.1.2. + // 7. Set obj.[[BoundTargetFunction]] to targetFunction. + // 8. Set obj.[[BoundThis]] to boundThis. + // 9. Set obj.[[BoundArguments]] to boundArgs. + // 10. Return obj. + Ok(JsObject::from_proto_and_data( + proto, + ObjectData::bound_function( + BoundFunction { + target_function, + this, + args, + }, + is_constructor, + ), + )) + } + + /// Get a reference to the bound function's this. + pub fn this(&self) -> &JsValue { + &self.this + } + + /// Get a reference to the bound function's target function. + pub fn target_function(&self) -> &JsObject { + &self.target_function + } + + /// Get a reference to the bound function's args. + pub fn args(&self) -> &[JsValue] { + self.args.as_slice() + } +} diff --git a/boa/src/builtins/intrinsics.rs b/boa/src/builtins/intrinsics.rs index 97c7eb762b..d940b5f2ff 100644 --- a/boa/src/builtins/intrinsics.rs +++ b/boa/src/builtins/intrinsics.rs @@ -31,7 +31,7 @@ fn create_throw_type_error(context: &mut Context) -> JsObject { context.standard_objects().function_object().prototype(), ObjectData::function(Function::Native { function: throw_type_error, - constructable: false, + constructor: false, }), ); diff --git a/boa/src/builtins/iterable/mod.rs b/boa/src/builtins/iterable/mod.rs index 1c21fba1b9..0c3c5a63c8 100644 --- a/boa/src/builtins/iterable/mod.rs +++ b/boa/src/builtins/iterable/mod.rs @@ -121,22 +121,26 @@ impl JsValue { // a. If hint is async, then if hint == IteratorHint::Async { // i. Set method to ? GetMethod(obj, @@asyncIterator). - let method = self.get_method(WellKnownSymbols::async_iterator(), context)?; - // ii. If method is undefined, then - if method.is_undefined() { + if let Some(method) = + self.get_method(WellKnownSymbols::async_iterator(), context)? + { + method.into() + } else { + // ii. If method is undefined, then // 1. Let syncMethod be ? GetMethod(obj, @@iterator). - let sync_method = self.get_method(WellKnownSymbols::iterator(), context)?; + let sync_method = self + .get_method(WellKnownSymbols::iterator(), context)? + .map_or(JsValue::Undefined, JsValue::from); // 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod). let _sync_iterator_record = self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method)); // 3. Return ! CreateAsyncFromSyncIterator(syncIteratorRecord). todo!("CreateAsyncFromSyncIterator"); } - - method } else { // b. Otherwise, set method to ? GetMethod(obj, @@iterator). self.get_method(WellKnownSymbols::iterator(), context)? + .map_or(JsValue::Undefined, JsValue::from) } }; diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 496f1162d2..7e6124880b 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -97,23 +97,22 @@ impl Json { // 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral. let unfiltered = context.eval(script_string.as_bytes())?; - match args.get(1).cloned().unwrap_or_default().as_object() { - // 11. If IsCallable(reviver) is true, then - Some(obj) if obj.is_callable() => { - // a. Let root be ! OrdinaryObjectCreate(%Object.prototype%). - let root = context.construct_object(); - - // b. Let rootName be the empty String. - // c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered). - root.create_data_property_or_throw("", unfiltered, context) - .expect("CreateDataPropertyOrThrow should never throw here"); - - // d. Return ? InternalizeJSONProperty(root, rootName, reviver). - Self::internalize_json_property(root, "".into(), obj, context) - } + // 11. If IsCallable(reviver) is true, then + if let Some(obj) = args.get_or_undefined(1).as_callable() { + // a. Let root be ! OrdinaryObjectCreate(%Object.prototype%). + let root = context.construct_object(); + + // b. Let rootName be the empty String. + // c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered). + root.create_data_property_or_throw("", unfiltered, context) + .expect("CreateDataPropertyOrThrow should never throw here"); + + // d. Return ? InternalizeJSONProperty(root, rootName, reviver). + Self::internalize_json_property(&root, "".into(), obj, context) + } else { // 12. Else, // a. Return unfiltered. - _ => Ok(unfiltered), + Ok(unfiltered) } } @@ -124,9 +123,9 @@ impl Json { /// /// [spec]: https://tc39.es/ecma262/#sec-internalizejsonproperty fn internalize_json_property( - holder: JsObject, + holder: &JsObject, name: JsString, - reviver: JsObject, + reviver: &JsObject, context: &mut Context, ) -> JsResult { // 1. Let val be ? Get(holder, name). @@ -145,9 +144,9 @@ impl Json { // 1. Let prop be ! ToString(𝔽(I)). // 2. Let newElement be ? InternalizeJSONProperty(val, prop, reviver). let new_element = Self::internalize_json_property( - obj.clone(), + obj, i.to_string().into(), - reviver.clone(), + reviver, context, )?; @@ -174,12 +173,8 @@ impl Json { let p = p.as_string().unwrap(); // 1. Let newElement be ? InternalizeJSONProperty(val, P, reviver). - let new_element = Self::internalize_json_property( - obj.clone(), - p.clone(), - reviver.clone(), - context, - )?; + let new_element = + Self::internalize_json_property(obj, p.clone(), reviver, context)?; // 2. If newElement is undefined, then if new_element.is_undefined() { @@ -196,7 +191,7 @@ impl Json { } // 3. Return ? Call(reviver, holder, « name, val »). - reviver.call(&holder.into(), &[name.into(), val], context) + reviver.call(&holder.clone().into(), &[name.into(), val], context) } /// `JSON.stringify( value[, replacer[, space]] )` @@ -237,7 +232,7 @@ impl Json { // a. If IsCallable(replacer) is true, then if replacer_obj.is_callable() { // i. Set ReplacerFunction to replacer. - replacer_function = Some(replacer_obj) + replacer_function = Some(replacer_obj.clone()) // b. Else, } else { // i. Let isArray be ? IsArray(replacer). @@ -352,7 +347,7 @@ impl Json { // 12. Return ? SerializeJSONProperty(state, the empty String, wrapper). Ok( - Self::serialize_json_property(&mut state, JsString::new(""), wrapper, context)? + Self::serialize_json_property(&mut state, JsString::new(""), &wrapper, context)? .map(|s| s.into()) .unwrap_or_default(), ) @@ -367,7 +362,7 @@ impl Json { fn serialize_json_property( state: &mut StateRecord, key: JsString, - holder: JsObject, + holder: &JsObject, context: &mut Context, ) -> JsResult> { // 1. Let value be ? Get(holder, key). @@ -390,11 +385,11 @@ impl Json { // 3. If state.[[ReplacerFunction]] is not undefined, then if let Some(obj) = &state.replacer_function { // a. Set value to ? Call(state.[[ReplacerFunction]], holder, « key, value »). - value = obj.call(&holder.into(), &[key.into(), value], context)? + value = obj.call(&holder.clone().into(), &[key.into(), value], context)? } // 4. If Type(value) is Object, then - if let Some(obj) = value.as_object() { + if let Some(obj) = value.as_object().cloned() { // a. If value has a [[NumberData]] internal slot, then if obj.is_number() { // i. Set value to ? ToNumber(value). @@ -530,11 +525,11 @@ impl Json { /// [spec]: https://tc39.es/ecma262/#sec-serializejsonobject fn serialize_json_object( state: &mut StateRecord, - value: JsObject, + value: &JsObject, context: &mut Context, ) -> JsResult { // 1. If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical. - let limiter = RecursionLimiter::new(&value); + let limiter = RecursionLimiter::new(value); if limiter.live { return Err(context.construct_type_error("cyclic object value")); } @@ -566,7 +561,7 @@ impl Json { // 8. For each element P of K, do for p in &k { // a. Let strP be ? SerializeJSONProperty(state, P, value). - let str_p = Self::serialize_json_property(state, p.clone(), value.clone(), context)?; + let str_p = Self::serialize_json_property(state, p.clone(), value, context)?; // b. If strP is not undefined, then if let Some(str_p) = str_p { @@ -643,11 +638,11 @@ impl Json { /// [spec]: https://tc39.es/ecma262/#sec-serializejsonarray fn serialize_json_array( state: &mut StateRecord, - value: JsObject, + value: &JsObject, context: &mut Context, ) -> JsResult { // 1. If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical. - let limiter = RecursionLimiter::new(&value); + let limiter = RecursionLimiter::new(value); if limiter.live { return Err(context.construct_type_error("cyclic object value")); } @@ -673,12 +668,8 @@ impl Json { // 8. Repeat, while index < len, while index < len { // a. Let strP be ? SerializeJSONProperty(state, ! ToString(𝔽(index)), value). - let str_p = Self::serialize_json_property( - state, - index.to_string().into(), - value.clone(), - context, - )?; + let str_p = + Self::serialize_json_property(state, index.to_string().into(), value, context)?; // b. If strP is undefined, then if let Some(str_p) = str_p { diff --git a/boa/src/builtins/map/map_iterator.rs b/boa/src/builtins/map/map_iterator.rs index 6d72ff17da..340ff75a29 100644 --- a/boa/src/builtins/map/map_iterator.rs +++ b/boa/src/builtins/map/map_iterator.rs @@ -66,16 +66,11 @@ impl MapIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let iterator_object = match this { - JsValue::Object(obj) if obj.borrow().is_map_iterator() => obj, - _ => return context.throw_type_error("`this` is not a MapIterator"), - }; - - let mut iterator_object = iterator_object.borrow_mut(); - - let map_iterator = iterator_object - .as_map_iterator_mut() - .expect("checked that obj was a map iterator"); + let mut map_iterator = this.as_object().map(|obj| obj.borrow_mut()); + let map_iterator = map_iterator + .as_mut() + .and_then(|obj| obj.as_map_iterator_mut()) + .ok_or_else(|| context.construct_type_error("`this` is not a MapIterator"))?; let item_kind = map_iterator.map_iteration_kind; diff --git a/boa/src/builtins/map/mod.rs b/boa/src/builtins/map/mod.rs index ac764caae2..4ae94eb8a1 100644 --- a/boa/src/builtins/map/mod.rs +++ b/boa/src/builtins/map/mod.rs @@ -50,19 +50,19 @@ impl BuiltIn for Map { let get_species = FunctionBuilder::native(context, Self::get_species) .name("get [Symbol.species]") - .constructable(false) + .constructor(false) .build(); let get_size = FunctionBuilder::native(context, Self::get_size) .name("get size") .length(0) - .constructable(false) + .constructor(false) .build(); let entries_function = FunctionBuilder::native(context, Self::entries) .name("entries") .length(0) - .constructable(false) + .constructor(false) .build(); let map_object = ConstructorBuilder::with_standard_object( @@ -450,19 +450,16 @@ impl Map { ) -> JsResult { // 1. Let M be the this value. // 2. Perform ? RequireInternalSlot(M, [[MapData]]). - let map = match this { - JsValue::Object(obj) if obj.is_map() => obj, - _ => return context.throw_type_error("`this` is not a Map"), - }; + let map = this + .as_object() + .filter(|obj| obj.is_map()) + .ok_or_else(|| context.construct_type_error("`this` is not a Map"))?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = match args.get_or_undefined(0) { - JsValue::Object(obj) if obj.is_callable() => obj, - val => { - let name = val.to_string(context)?; - return context.throw_type_error(format!("{} is not a function", name)); - } - }; + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { + context.construct_type_error(format!("{} is not a function", callback.display())) + })?; let this_arg = args.get_or_undefined(1); @@ -543,10 +540,9 @@ pub(crate) fn add_entries_from_iterable( context: &mut Context, ) -> JsResult { // 1. If IsCallable(adder) is false, throw a TypeError exception. - let adder = match adder { - JsValue::Object(obj) if obj.is_callable() => obj, - _ => return context.throw_type_error("property `set` of `NewTarget` is not callable"), - }; + let adder = adder.as_callable().ok_or_else(|| { + context.construct_type_error("property `set` of `NewTarget` is not callable") + })?; // 2. Let iteratorRecord be ? GetIterator(iterable). let iterator_record = iterable.get_iterator(context, None, None)?; diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index a7a49b8872..5a63c6567b 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -184,18 +184,10 @@ impl Number { /// /// [spec]: https://tc39.es/ecma262/#sec-thisnumbervalue fn this_number_value(value: &JsValue, context: &mut Context) -> JsResult { - match *value { - JsValue::Integer(integer) => return Ok(f64::from(integer)), - JsValue::Rational(rational) => return Ok(rational), - JsValue::Object(ref object) => { - if let Some(number) = object.borrow().as_number() { - return Ok(number); - } - } - _ => {} - } - - Err(context.construct_type_error("'this' is not a number")) + value + .as_number() + .or_else(|| value.as_object().and_then(|obj| obj.borrow().as_number())) + .ok_or_else(|| context.construct_type_error("'this' is not a number")) } /// `Number.prototype.toExponential( [fractionDigits] )` diff --git a/boa/src/builtins/object/for_in_iterator.rs b/boa/src/builtins/object/for_in_iterator.rs index 6289208051..ab1bebf5ce 100644 --- a/boa/src/builtins/object/for_in_iterator.rs +++ b/boa/src/builtins/object/for_in_iterator.rs @@ -62,63 +62,59 @@ impl ForInIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - if let JsValue::Object(ref o) = this { - let mut for_in_iterator = o.borrow_mut(); - if let Some(iterator) = for_in_iterator.as_for_in_iterator_mut() { - let mut object = iterator.object.to_object(context)?; - loop { - if !iterator.object_was_visited { - let keys = object.__own_property_keys__(context)?; - for k in keys { - match k { - PropertyKey::String(ref k) => { - iterator.remaining_keys.push_back(k.clone()); - } - PropertyKey::Index(i) => { - iterator.remaining_keys.push_back(i.to_string().into()); - } - _ => {} - } + let mut iterator = this.as_object().map(|obj| obj.borrow_mut()); + let iterator = iterator + .as_mut() + .and_then(|obj| obj.as_for_in_iterator_mut()) + .ok_or_else(|| context.construct_type_error("`this` is not a ForInIterator"))?; + let mut object = iterator.object.to_object(context)?; + loop { + if !iterator.object_was_visited { + let keys = object.__own_property_keys__(context)?; + for k in keys { + match k { + PropertyKey::String(ref k) => { + iterator.remaining_keys.push_back(k.clone()); } - iterator.object_was_visited = true; - } - while let Some(r) = iterator.remaining_keys.pop_front() { - if !iterator.visited_keys.contains(&r) { - if let Some(desc) = object - .__get_own_property__(&PropertyKey::from(r.clone()), context)? - { - iterator.visited_keys.insert(r.clone()); - if desc.expect_enumerable() { - return Ok(create_iter_result_object( - JsValue::new(r.to_string()), - false, - context, - )); - } - } + PropertyKey::Index(i) => { + iterator.remaining_keys.push_back(i.to_string().into()); } + _ => {} } - let proto = object.prototype().clone(); - match proto { - Some(o) => { - object = o; - } - _ => { + } + iterator.object_was_visited = true; + } + while let Some(r) = iterator.remaining_keys.pop_front() { + if !iterator.visited_keys.contains(&r) { + if let Some(desc) = + object.__get_own_property__(&PropertyKey::from(r.clone()), context)? + { + iterator.visited_keys.insert(r.clone()); + if desc.expect_enumerable() { return Ok(create_iter_result_object( - JsValue::undefined(), - true, + JsValue::new(r.to_string()), + false, context, - )) + )); } } - iterator.object = JsValue::new(object.clone()); - iterator.object_was_visited = false; } - } else { - context.throw_type_error("`this` is not a ForInIterator") } - } else { - context.throw_type_error("`this` is not an ForInIterator") + let proto = object.prototype().clone(); + match proto { + Some(o) => { + object = o; + } + _ => { + return Ok(create_iter_result_object( + JsValue::undefined(), + true, + context, + )) + } + } + iterator.object = JsValue::new(object.clone()); + iterator.object_was_visited = false; } } diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 773ef74b59..f3b4c835d0 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -134,9 +134,10 @@ impl Object { let properties = args.get_or_undefined(1); let obj = match prototype { - JsValue::Object(_) | JsValue::Null => { - JsObject::from_proto_and_data(prototype.as_object(), ObjectData::ordinary()) - } + JsValue::Object(_) | JsValue::Null => JsObject::from_proto_and_data( + prototype.as_object().cloned(), + ObjectData::ordinary(), + ), _ => { return context.throw_type_error(format!( "Object prototype may only be an Object or null: {}", @@ -195,10 +196,7 @@ impl Object { args: &[JsValue], context: &mut Context, ) -> JsResult { - let object = args - .get(0) - .unwrap_or(&JsValue::undefined()) - .to_object(context)?; + let object = args.get_or_undefined(0).to_object(context)?; let descriptors = context.construct_object(); for key in object.borrow().properties().keys() { @@ -321,7 +319,7 @@ impl Object { } }; - let mut obj = if let Some(obj) = o.as_object() { + let obj = if let Some(obj) = o.as_object() { obj } else { // 3. If Type(O) is not Object, return O. @@ -379,7 +377,7 @@ impl Object { context: &mut Context, ) -> JsResult { let object = args.get_or_undefined(0); - if let Some(object) = object.as_object() { + if let JsValue::Object(object) = object { let key = args .get(1) .unwrap_or(&JsValue::Undefined) @@ -391,7 +389,7 @@ impl Object { object.define_property_or_throw(key, desc, context)?; - Ok(object.into()) + Ok(object.clone().into()) } else { context.throw_type_error("Object.defineProperty called on non-object") } @@ -413,10 +411,9 @@ impl Object { context: &mut Context, ) -> JsResult { let arg = args.get_or_undefined(0); - let arg_obj = arg.as_object(); - if let Some(obj) = arg_obj { + if let JsValue::Object(obj) = arg { let props = args.get_or_undefined(1); - object_define_properties(&obj, props.clone(), context)?; + object_define_properties(obj, props.clone(), context)?; Ok(arg.clone()) } else { context.throw_type_error("Expected an object") diff --git a/boa/src/builtins/reflect/mod.rs b/boa/src/builtins/reflect/mod.rs index 9982831cea..f8c789e17a 100644 --- a/boa/src/builtins/reflect/mod.rs +++ b/boa/src/builtins/reflect/mod.rs @@ -110,12 +110,12 @@ impl Reflect { .ok_or_else(|| context.construct_type_error("target must be a function"))?; let args_list = args.get_or_undefined(1); - if !target.is_constructable() { + if !target.is_constructor() { return context.throw_type_error("target must be a constructor"); } let new_target = if let Some(new_target) = args.get(2) { - if new_target.as_object().map(|o| o.is_constructable()) != Some(true) { + if new_target.as_object().map(|o| o.is_constructor()) != Some(true) { return context.throw_type_error("newTarget must be constructor"); } new_target.clone() @@ -147,7 +147,7 @@ impl Reflect { let key = args.get_or_undefined(1).to_property_key(context)?; let prop_desc: JsValue = args .get(2) - .and_then(|v| v.as_object()) + .and_then(|v| v.as_object().cloned()) .ok_or_else(|| context.construct_type_error("property descriptor must be an object"))? .into(); @@ -218,13 +218,17 @@ impl Reflect { args: &[JsValue], context: &mut Context, ) -> JsResult { - match args.get(0) { - Some(v) if v.is_object() => (), - _ => return context.throw_type_error("target must be an object"), + if args.get_or_undefined(0).is_object() { + // This function is the same as Object.prototype.getOwnPropertyDescriptor, that why + // it is invoked here. + builtins::object::Object::get_own_property_descriptor( + &JsValue::undefined(), + args, + context, + ) + } else { + context.throw_type_error("target must be an object") } - // This function is the same as Object.prototype.getOwnPropertyDescriptor, that why - // it is invoked here. - builtins::object::Object::get_own_property_descriptor(&JsValue::undefined(), args, context) } /// Gets the prototype of an object. @@ -375,7 +379,7 @@ impl Reflect { args: &[JsValue], context: &mut Context, ) -> JsResult { - let mut target = args + let target = args .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index f6386a8512..f3d19a572f 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -80,42 +80,42 @@ impl BuiltIn for RegExp { let get_species = FunctionBuilder::native(context, Self::get_species) .name("get [Symbol.species]") - .constructable(false) + .constructor(false) .build(); let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; let get_global = FunctionBuilder::native(context, Self::get_global) .name("get global") - .constructable(false) + .constructor(false) .build(); let get_ignore_case = FunctionBuilder::native(context, Self::get_ignore_case) .name("get ignoreCase") - .constructable(false) + .constructor(false) .build(); let get_multiline = FunctionBuilder::native(context, Self::get_multiline) .name("get multiline") - .constructable(false) + .constructor(false) .build(); let get_dot_all = FunctionBuilder::native(context, Self::get_dot_all) .name("get dotAll") - .constructable(false) + .constructor(false) .build(); let get_unicode = FunctionBuilder::native(context, Self::get_unicode) .name("get unicode") - .constructable(false) + .constructor(false) .build(); let get_sticky = FunctionBuilder::native(context, Self::get_sticky) .name("get sticky") - .constructable(false) + .constructor(false) .build(); let get_flags = FunctionBuilder::native(context, Self::get_flags) .name("get flags") - .constructable(false) + .constructor(false) .build(); let get_source = FunctionBuilder::native(context, Self::get_source) .name("get source") - .constructable(false) + .constructor(false) .build(); let regexp_object = ConstructorBuilder::with_standard_object( context, @@ -197,15 +197,7 @@ impl RegExp { let flags = args.get_or_undefined(1); // 1. Let patternIsRegExp be ? IsRegExp(pattern). - let pattern_is_regexp = if let JsValue::Object(obj) = &pattern { - if obj.is_regexp() { - Some(obj) - } else { - None - } - } else { - None - }; + let pattern_is_regexp = pattern.as_object().filter(|obj| obj.is_regexp()); // 2. If NewTarget is undefined, then // 3. Else, let newTarget be NewTarget. @@ -414,7 +406,7 @@ impl RegExp { } if JsObject::equals( - &object, + object, &context.standard_objects().regexp_object().prototype, ) { return Ok(JsValue::undefined()); @@ -703,10 +695,10 @@ impl RegExp { ) -> JsResult { // 1. Let R be the this value. // 2. If Type(R) is not Object, throw a TypeError exception. - if !this.is_object() { - return context - .throw_type_error("RegExp.prototype.test method called on incompatible value"); - } + let this = this.as_object().ok_or_else(|| { + context + .construct_type_error("RegExp.prototype.test method called on incompatible value") + })?; // 3. Let string be ? ToString(S). let arg_str = args @@ -745,19 +737,15 @@ impl RegExp { ) -> JsResult { // 1. Let R be the this value. // 2. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]). - let obj = this.as_object().unwrap_or_default(); - if !obj.is_regexp() { - return Err( + let obj = this + .as_object() + .filter(|obj| obj.is_regexp()) + .ok_or_else(|| { context.construct_type_error("RegExp.prototype.exec called with invalid value") - ); - } + })?; // 3. Let S be ? ToString(string). - let arg_str = args - .get(0) - .cloned() - .unwrap_or_default() - .to_string(context)?; + let arg_str = args.get_or_undefined(0).to_string(context)?; // 4. Return ? RegExpBuiltinExec(R, S). if let Some(v) = Self::abstract_builtin_exec(obj, arg_str, context)? { @@ -774,23 +762,20 @@ impl RegExp { /// /// [spec]: https://tc39.es/ecma262/#sec-regexpexec pub(crate) fn abstract_exec( - this: &JsValue, + this: &JsObject, input: JsString, context: &mut Context, ) -> JsResult> { // 1. Assert: Type(R) is Object. - let object = this - .as_object() - .ok_or_else(|| context.construct_type_error("RegExpExec called with invalid value"))?; // 2. Assert: Type(S) is String. // 3. Let exec be ? Get(R, "exec"). - let exec = this.get_field("exec", context)?; + let exec = this.get("exec", context)?; // 4. If IsCallable(exec) is true, then - if exec.is_function() { + if let Some(exec) = exec.as_callable() { // a. Let result be ? Call(exec, R, « S »). - let result = context.call(&exec, this, &[input.into()])?; + let result = exec.call(&this.clone().into(), &[input.into()], context)?; // b. If Type(result) is neither Object nor Null, throw a TypeError exception. if !result.is_object() && !result.is_null() { @@ -800,16 +785,16 @@ impl RegExp { } // c. Return result. - return Ok(result.as_object()); + return Ok(result.as_object().cloned()); } // 5. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]). - if !object.is_regexp() { + if !this.is_regexp() { return Err(context.construct_type_error("RegExpExec called with invalid value")); } // 6. Return ? RegExpBuiltinExec(R, S). - Self::abstract_builtin_exec(object, input, context) + Self::abstract_builtin_exec(this, input, context) } /// `22.2.5.2.2 RegExpBuiltinExec ( R, S )` @@ -819,7 +804,7 @@ impl RegExp { /// /// [spec]: https://tc39.es/ecma262/#sec-regexpbuiltinexec pub(crate) fn abstract_builtin_exec( - this: JsObject, + this: &JsObject, input: JsString, context: &mut Context, ) -> JsResult> { @@ -1080,7 +1065,7 @@ impl RegExp { // 6. Else, if !global { // a. Return ? RegExpExec(rx, S). - if let Some(v) = Self::abstract_exec(&JsValue::new(rx), arg_str, context)? { + if let Some(v) = Self::abstract_exec(rx, arg_str, context)? { Ok(v.into()) } else { Ok(JsValue::null()) @@ -1103,8 +1088,7 @@ impl RegExp { // f. Repeat, loop { // i. Let result be ? RegExpExec(rx, S). - let result = - Self::abstract_exec(&JsValue::new(rx.clone()), arg_str.clone(), context)?; + let result = Self::abstract_exec(rx, arg_str.clone(), context)?; // ii. If result is null, then // iii. Else, @@ -1194,27 +1178,20 @@ impl RegExp { ) -> JsResult { // 1. Let R be the this value. // 2. If Type(R) is not Object, throw a TypeError exception. - if !this.is_object() { - return context.throw_type_error( + let regexp = this.as_object().ok_or_else(|| { + context.construct_type_error( "RegExp.prototype.match_all method called on incompatible value", - ); - } + ) + })?; // 3. Let S be ? ToString(string). - let arg_str = args - .get(0) - .cloned() - .unwrap_or_default() - .to_string(context)?; + let arg_str = args.get_or_undefined(0).to_string(context)?; // 4. Let C be ? SpeciesConstructor(R, %RegExp%). - let c = this - .as_object() - .unwrap_or_default() - .species_constructor(StandardObjects::regexp_object, context)?; + let c = regexp.species_constructor(StandardObjects::regexp_object, context)?; // 5. Let flags be ? ToString(? Get(R, "flags")). - let flags = this.get_field("flags", context)?.to_string(context)?; + let flags = regexp.get("flags", context)?.to_string(context)?; // 6. Let matcher be ? Construct(C, « R, flags »). let matcher = c.construct( @@ -1222,12 +1199,15 @@ impl RegExp { &c.clone().into(), context, )?; + let matcher = matcher + .as_object() + .expect("construct must always return an Object"); // 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). - let last_index = this.get_field("lastIndex", context)?.to_length(context)?; + let last_index = regexp.get("lastIndex", context)?.to_length(context)?; // 8. Perform ? Set(matcher, "lastIndex", lastIndex, true). - matcher.set_field("lastIndex", last_index, true, context)?; + matcher.set("lastIndex", last_index, true, context)?; // 9. If flags contains "g", let global be true. // 10. Else, let global be false. @@ -1239,7 +1219,11 @@ impl RegExp { // 13. Return ! CreateRegExpStringIterator(matcher, S, global, fullUnicode). RegExpStringIterator::create_regexp_string_iterator( - &matcher, arg_str, global, unicode, context, + matcher.clone(), + arg_str, + global, + unicode, + context, ) } @@ -1282,7 +1266,10 @@ impl RegExp { // 5. Let functionalReplace be IsCallable(replaceValue). let mut replace_value = args.get_or_undefined(1).clone(); - let functional_replace = replace_value.is_function(); + let functional_replace = replace_value + .as_object() + .map(|obj| obj.is_callable()) + .unwrap_or_default(); // 6. If functionalReplace is false, then if !functional_replace { @@ -1310,7 +1297,7 @@ impl RegExp { // 11. Repeat, while done is false, loop { // a. Let result be ? RegExpExec(rx, S). - let result = Self::abstract_exec(&JsValue::new(rx.clone()), arg_str.clone(), context)?; + let result = Self::abstract_exec(rx, arg_str.clone(), context)?; // b. If result is null, set done to true. // c. Else, @@ -1530,7 +1517,7 @@ impl RegExp { } // 6. Let result be ? RegExpExec(rx, S). - let result = Self::abstract_exec(&JsValue::new(rx.clone()), arg_str, context)?; + let result = Self::abstract_exec(rx, arg_str, context)?; // 7. Let currentLastIndex be ? Get(rx, "lastIndex"). let current_last_index = rx.get("lastIndex", context)?; @@ -1602,10 +1589,14 @@ impl RegExp { // 10. Let splitter be ? Construct(C, « rx, newFlags »). let splitter = constructor.construct( - &[rx.into(), new_flags.into()], + &[this.clone(), new_flags.into()], &constructor.clone().into(), context, )?; + let splitter = splitter + .as_object() + // todo: remove when we handle realms on `get_prototype_from_constructor` and make `construct` always return a `JsObject` + .ok_or_else(|| context.construct_type_error("constructor did not return an object"))?; // 11. Let A be ! ArrayCreate(0). let a = Array::array_create(0, None, context).unwrap(); @@ -1632,7 +1623,7 @@ impl RegExp { // 16. If size is 0, then if size == 0 { // a. Let z be ? RegExpExec(splitter, S). - let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?; + let result = Self::abstract_exec(splitter, arg_str.clone(), context)?; // b. If z is not null, return A. if result.is_some() { @@ -1655,18 +1646,16 @@ impl RegExp { // 19. Repeat, while q < size, while q < size { // a. Perform ? Set(splitter, "lastIndex", 𝔽(q), true). - splitter.set_field("lastIndex", JsValue::new(q), true, context)?; + splitter.set("lastIndex", JsValue::new(q), true, context)?; // b. Let z be ? RegExpExec(splitter, S). - let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?; + let result = Self::abstract_exec(splitter, arg_str.clone(), context)?; // c. If z is null, set q to AdvanceStringIndex(S, q, unicodeMatching). // d. Else, if let Some(result) = result { // i. Let e be ℝ(? ToLength(? Get(splitter, "lastIndex"))). - let mut e = splitter - .get_field("lastIndex", context)? - .to_length(context)?; + let mut e = splitter.get("lastIndex", context)?.to_length(context)?; // ii. Set e to min(e, size). e = std::cmp::min(e, size); diff --git a/boa/src/builtins/regexp/regexp_string_iterator.rs b/boa/src/builtins/regexp/regexp_string_iterator.rs index 6754a58f5e..fd8092234f 100644 --- a/boa/src/builtins/regexp/regexp_string_iterator.rs +++ b/boa/src/builtins/regexp/regexp_string_iterator.rs @@ -23,7 +23,7 @@ use crate::{ // TODO: See todos in create_regexp_string_iterator and next. #[derive(Debug, Clone, Finalize, Trace)] pub struct RegExpStringIterator { - matcher: JsValue, + matcher: JsObject, string: JsString, global: bool, unicode: bool, @@ -32,7 +32,7 @@ pub struct RegExpStringIterator { // TODO: See todos in create_regexp_string_iterator and next. impl RegExpStringIterator { - fn new(matcher: JsValue, string: JsString, global: bool, unicode: bool) -> Self { + fn new(matcher: JsObject, string: JsString, global: bool, unicode: bool) -> Self { Self { matcher, string, @@ -49,7 +49,7 @@ impl RegExpStringIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-createregexpstringiterator pub(crate) fn create_regexp_string_iterator( - matcher: &JsValue, + matcher: JsObject, string: JsString, global: bool, unicode: bool, @@ -69,83 +69,71 @@ impl RegExpStringIterator { let regexp_string_iterator = JsObject::from_proto_and_data( context.iterator_prototypes().regexp_string_iterator(), - ObjectData::reg_exp_string_iterator(Self::new( - matcher.clone(), - string, - global, - unicode, - )), + ObjectData::reg_exp_string_iterator(Self::new(matcher, string, global, unicode)), ); Ok(regexp_string_iterator.into()) } pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - if let JsValue::Object(ref object) = this { - let mut object = object.borrow_mut(); - if let Some(iterator) = object.as_regexp_string_iterator_mut() { - if iterator.completed { - return Ok(create_iter_result_object( - JsValue::undefined(), - true, - context, - )); - } - - // TODO: This is the code that should be created as a closure in create_regexp_string_iterator. - - // i. Let match be ? RegExpExec(R, S). - let m = RegExp::abstract_exec(&iterator.matcher, iterator.string.clone(), context)?; - - if let Some(m) = m { - // iii. If global is false, then - if !iterator.global { - // 1. Perform ? Yield(match). - // 2. Return undefined. - iterator.completed = true; - return Ok(create_iter_result_object(m.into(), false, context)); - } - - // iv. Let matchStr be ? ToString(? Get(match, "0")). - let m_str = m.get("0", context)?.to_string(context)?; - - // v. If matchStr is the empty String, then - if m_str.is_empty() { - // 1. Let thisIndex be ℝ(? ToLength(? Get(R, "lastIndex"))). - let this_index = iterator - .matcher - .get_field("lastIndex", context)? - .to_length(context)?; - - // 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode). - let next_index = advance_string_index( - iterator.string.clone(), - this_index, - iterator.unicode, - ); - - // 3. Perform ? Set(R, "lastIndex", 𝔽(nextIndex), true). - iterator - .matcher - .set_field("lastIndex", next_index, true, context)?; - } - - // vi. Perform ? Yield(match). - Ok(create_iter_result_object(m.into(), false, context)) - } else { - // ii. If match is null, return undefined. - iterator.completed = true; - Ok(create_iter_result_object( - JsValue::undefined(), - true, - context, - )) - } - } else { - context.throw_type_error("`this` is not a RegExpStringIterator") + let mut iterator = this.as_object().map(|obj| obj.borrow_mut()); + let iterator = iterator + .as_mut() + .and_then(|obj| obj.as_regexp_string_iterator_mut()) + .ok_or_else(|| context.construct_type_error("`this` is not a RegExpStringIterator"))?; + if iterator.completed { + return Ok(create_iter_result_object( + JsValue::undefined(), + true, + context, + )); + } + + // TODO: This is the code that should be created as a closure in create_regexp_string_iterator. + + // i. Let match be ? RegExpExec(R, S). + let m = RegExp::abstract_exec(&iterator.matcher, iterator.string.clone(), context)?; + + if let Some(m) = m { + // iii. If global is false, then + if !iterator.global { + // 1. Perform ? Yield(match). + // 2. Return undefined. + iterator.completed = true; + return Ok(create_iter_result_object(m.into(), false, context)); } + + // iv. Let matchStr be ? ToString(? Get(match, "0")). + let m_str = m.get("0", context)?.to_string(context)?; + + // v. If matchStr is the empty String, then + if m_str.is_empty() { + // 1. Let thisIndex be ℝ(? ToLength(? Get(R, "lastIndex"))). + let this_index = iterator + .matcher + .get("lastIndex", context)? + .to_length(context)?; + + // 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode). + let next_index = + advance_string_index(iterator.string.clone(), this_index, iterator.unicode); + + // 3. Perform ? Set(R, "lastIndex", 𝔽(nextIndex), true). + iterator + .matcher + .set("lastIndex", next_index, true, context)?; + } + + // vi. Perform ? Yield(match). + Ok(create_iter_result_object(m.into(), false, context)) } else { - context.throw_type_error("`this` is not a RegExpStringIterator") + // ii. If match is null, return undefined. + iterator.completed = true; + Ok(create_iter_result_object( + JsValue::undefined(), + true, + context, + )) } } diff --git a/boa/src/builtins/set/mod.rs b/boa/src/builtins/set/mod.rs index 1a21f43920..c0882d773e 100644 --- a/boa/src/builtins/set/mod.rs +++ b/boa/src/builtins/set/mod.rs @@ -47,11 +47,11 @@ impl BuiltIn for Set { let get_species = FunctionBuilder::native(context, Self::get_species) .name("get [Symbol.species]") - .constructable(false) + .constructor(false) .build(); let size_getter = FunctionBuilder::native(context, Self::size_getter) - .constructable(false) + .constructor(false) .name("get size") .build(); @@ -62,7 +62,7 @@ impl BuiltIn for Set { let values_function = FunctionBuilder::native(context, Self::values) .name("values") .length(0) - .constructable(false) + .constructor(false) .build(); let set_object = ConstructorBuilder::with_standard_object( @@ -142,9 +142,9 @@ impl Set { let adder = obj.get("add", context)?; // 6 - if !adder.is_function() { - return context.throw_type_error("'add' of 'newTarget' is not a function"); - } + let adder = adder.as_callable().ok_or_else(|| { + context.construct_type_error("'add' of 'newTarget' is not a function") + })?; // 7 let iterator_record = iterable.clone().get_iterator(context, None, None)?; @@ -158,7 +158,7 @@ impl Set { let next_value = next.value; // d, e - if let Err(status) = context.call(&adder, &obj.clone().into(), &[next_value]) { + if let Err(status) = adder.call(&obj.clone().into(), &[next_value], context) { return iterator_record.close(Err(status), context); } @@ -337,17 +337,15 @@ impl Set { let mut index = 0; while index < Set::get_size(this, context)? { - let arguments = if let JsValue::Object(ref object) = this { - let object = object.borrow(); - if let Some(set) = object.as_set_ref() { - set.get_index(index) - .map(|value| [value.clone(), value.clone(), this.clone()]) - } else { - return context.throw_type_error("'this' is not a Set"); - } - } else { - return context.throw_type_error("'this' is not a Set"); - }; + let arguments = this + .as_object() + .and_then(|obj| { + obj.borrow().as_set_ref().map(|set| { + set.get_index(index) + .map(|value| [value.clone(), value.clone(), this.clone()]) + }) + }) + .ok_or_else(|| context.construct_type_error("'this' is not a Set"))?; if let Some(arguments) = arguments { context.call(callback_arg, &this_arg, &arguments)?; @@ -376,14 +374,13 @@ impl Set { ) -> JsResult { let value = args.get_or_undefined(0); - if let JsValue::Object(ref object) = this { - let object = object.borrow(); - if let Some(set) = object.as_set_ref() { - return Ok(set.contains(value).into()); - } - } - - Err(context.construct_type_error("'this' is not a Set")) + this.as_object() + .and_then(|obj| { + obj.borrow() + .as_set_ref() + .map(|set| set.contains(value).into()) + }) + .ok_or_else(|| context.construct_type_error("'this' is not a Set")) } /// `Set.prototype.values( )` @@ -426,15 +423,8 @@ impl Set { /// Helper function to get the size of the set. fn get_size(set: &JsValue, context: &mut Context) -> JsResult { - if let JsValue::Object(ref object) = set { - let object = object.borrow(); - if let Some(set) = object.as_set_ref() { - Ok(set.size()) - } else { - Err(context.construct_type_error("'this' is not a Set")) - } - } else { - Err(context.construct_type_error("'this' is not a Set")) - } + set.as_object() + .and_then(|obj| obj.borrow().as_set_ref().map(|set| set.size())) + .ok_or_else(|| context.construct_type_error("'this' is not a Set")) } } diff --git a/boa/src/builtins/set/set_iterator.rs b/boa/src/builtins/set/set_iterator.rs index d7a0437e04..3720423143 100644 --- a/boa/src/builtins/set/set_iterator.rs +++ b/boa/src/builtins/set/set_iterator.rs @@ -63,73 +63,62 @@ impl SetIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - if let JsValue::Object(ref object) = this { - let mut object = object.borrow_mut(); - if let Some(set_iterator) = object.as_set_iterator_mut() { - let m = &set_iterator.iterated_set; - let mut index = set_iterator.next_index; - let item_kind = &set_iterator.iteration_kind; + let mut set_iterator = this.as_object().map(|obj| obj.borrow_mut()); - if set_iterator.iterated_set.is_undefined() { - return Ok(create_iter_result_object( - JsValue::undefined(), - true, - context, - )); - } + let set_iterator = set_iterator + .as_mut() + .and_then(|obj| obj.as_set_iterator_mut()) + .ok_or_else(|| context.construct_type_error("`this` is not an SetIterator"))?; + { + let m = &set_iterator.iterated_set; + let mut index = set_iterator.next_index; + let item_kind = &set_iterator.iteration_kind; - if let JsValue::Object(ref object) = m { - if let Some(entries) = object.borrow().as_set_ref() { - let num_entries = entries.size(); - while index < num_entries { - let e = entries.get_index(index); - index += 1; - set_iterator.next_index = index; - if let Some(value) = e { - match item_kind { - PropertyNameKind::Value => { - return Ok(create_iter_result_object( - value.clone(), - false, - context, - )); - } - PropertyNameKind::KeyAndValue => { - let result = Array::create_array_from_list( - [value.clone(), value.clone()], - context, - ); - return Ok(create_iter_result_object( - result.into(), - false, - context, - )); - } - PropertyNameKind::Key => { - panic!("tried to collect only keys of Set") - } - } - } - } - } else { - return Err(context.construct_type_error("'this' is not a Set")); - } - } else { - return Err(context.construct_type_error("'this' is not a Set")); - } - - set_iterator.iterated_set = JsValue::undefined(); - Ok(create_iter_result_object( + if set_iterator.iterated_set.is_undefined() { + return Ok(create_iter_result_object( JsValue::undefined(), true, context, - )) - } else { - context.throw_type_error("`this` is not an SetIterator") + )); + } + + let entries = m.as_object().map(|obj| obj.borrow()); + let entries = entries + .as_ref() + .and_then(|obj| obj.as_set_ref()) + .ok_or_else(|| context.construct_type_error("'this' is not a Set"))?; + + let num_entries = entries.size(); + while index < num_entries { + let e = entries.get_index(index); + index += 1; + set_iterator.next_index = index; + if let Some(value) = e { + match item_kind { + PropertyNameKind::Value => { + return Ok(create_iter_result_object(value.clone(), false, context)); + } + PropertyNameKind::KeyAndValue => { + let result = Array::create_array_from_list( + [value.clone(), value.clone()], + context, + ); + return Ok(create_iter_result_object(result.into(), false, context)); + } + PropertyNameKind::Key => { + panic!("tried to collect only keys of Set") + } + } + } } - } else { - context.throw_type_error("`this` is not an SetIterator") } + + set_iterator.iterated_set = JsValue::undefined(); + Ok(create_iter_result_object( + JsValue::undefined(), + true, + context, + )) } /// Create the %SetIteratorPrototype% object diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 0ce79e3d6a..aa9b8d70f8 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -225,18 +225,10 @@ impl String { } fn this_string_value(this: &JsValue, context: &mut Context) -> JsResult { - match this { - JsValue::String(ref string) => return Ok(string.clone()), - JsValue::Object(ref object) => { - let object = object.borrow(); - if let Some(string) = object.as_string() { - return Ok(string); - } - } - _ => {} - } - - Err(context.construct_type_error("'this' is not a string")) + this.as_string() + .cloned() + .or_else(|| this.as_object().and_then(|obj| obj.borrow().as_string())) + .ok_or_else(|| context.construct_type_error("'this' is not a string")) } /// `String.fromCharCode(...codePoints)` @@ -720,10 +712,10 @@ impl String { } fn is_regexp_object(value: &JsValue) -> bool { - match value { - JsValue::Object(ref obj) => obj.borrow().is_regexp(), - _ => false, - } + value + .as_object() + .map(|obj| obj.borrow().is_regexp()) + .unwrap_or_default() } /// `String.prototype.replace( regexp|substr, newSubstr|function )` @@ -759,12 +751,12 @@ impl String { let replacer = search_value.get_method(WellKnownSymbols::replace(), context)?; // b. If replacer is not undefined, then - if !replacer.is_undefined() { + if let Some(replacer) = replacer { // i. Return ? Call(replacer, searchValue, « O, replaceValue »). - return context.call( - &replacer, + return replacer.call( search_value, &[this.clone(), replace_value.clone()], + context, ); } } @@ -776,7 +768,10 @@ impl String { let search_str = search_value.to_string(context)?; // 5. Let functionalReplace be IsCallable(replaceValue). - let functional_replace = replace_value.is_function(); + let functional_replace = replace_value + .as_object() + .map(|obj| obj.is_callable()) + .unwrap_or_default(); // 6. If functionalReplace is false, then // a. Set replaceValue to ? ToString(replaceValue). @@ -868,7 +863,7 @@ impl String { // 2. If searchValue is neither undefined nor null, then if !search_value.is_null_or_undefined() { // a. Let isRegExp be ? IsRegExp(searchValue). - if let Some(obj) = search_value.as_object() { + if let Some(obj) = search_value.as_object().filter(|obj| obj.is_regexp()) { // b. If isRegExp is true, then if obj.is_regexp() { // i. Let flags be ? Get(searchValue, "flags"). @@ -890,9 +885,9 @@ impl String { let replacer = search_value.get_method(WellKnownSymbols::replace(), context)?; // d. If replacer is not undefined, then - if !replacer.is_undefined() { + if let Some(replacer) = replacer { // i. Return ? Call(replacer, searchValue, « O, replaceValue »). - return context.call(&replacer, search_value, &[o.into(), replace_value.clone()]); + return replacer.call(search_value, &[o.into(), replace_value.clone()], context); } } @@ -903,7 +898,10 @@ impl String { let search_string = search_value.to_string(context)?; // 5. Let functionalReplace be IsCallable(replaceValue). - let functional_replace = replace_value.is_function(); + let functional_replace = replace_value + .as_object() + .map(|obj| obj.is_callable()) + .unwrap_or_default(); // 6. If functionalReplace is false, then let replace_value_string = if !functional_replace { @@ -1127,9 +1125,9 @@ impl String { // a. Let matcher be ? GetMethod(regexp, @@match). let matcher = regexp.get_method(WellKnownSymbols::r#match(), context)?; // b. If matcher is not undefined, then - if !matcher.is_undefined() { + if let Some(matcher) = matcher { // i. Return ? Call(matcher, regexp, « O »). - return context.call(&matcher, regexp, &[o.clone()]); + return matcher.call(regexp, &[o.clone()], context); } } @@ -1492,9 +1490,9 @@ impl String { // a. Let splitter be ? GetMethod(separator, @@split). let splitter = separator.get_method(WellKnownSymbols::split(), context)?; // b. If splitter is not undefined, then - if !splitter.is_undefined() { + if let Some(splitter) = splitter { // i. Return ? Call(splitter, separator, « O, limit »). - return context.call(&splitter, separator, &[this.clone(), limit.clone()]); + return splitter.call(separator, &[this.clone(), limit.clone()], context); } } @@ -1661,9 +1659,9 @@ impl String { if !regexp.is_null_or_undefined() { // a. Let isRegExp be ? IsRegExp(regexp). // b. If isRegExp is true, then - if regexp.as_object().unwrap_or_default().is_regexp() { + if let Some(regexp_obj) = regexp.as_object().filter(|obj| obj.is_regexp()) { // i. Let flags be ? Get(regexp, "flags"). - let flags = regexp.get_field("flags", context)?; + let flags = regexp_obj.get("flags", context)?; // ii. Perform ? RequireObjectCoercible(flags). flags.require_object_coercible(context)?; @@ -1675,13 +1673,11 @@ impl String { ); } } - // c. Let matcher be ? GetMethod(regexp, @@matchAll). let matcher = regexp.get_method(WellKnownSymbols::match_all(), context)?; // d. If matcher is not undefined, then - if !matcher.is_undefined() { - // i. Return ? Call(matcher, regexp, « O »). - return context.call(&matcher, regexp, &[o.clone()]); + if let Some(matcher) = matcher { + return matcher.call(regexp, &[o.clone()], context); } } @@ -1757,9 +1753,9 @@ impl String { // a. Let searcher be ? GetMethod(regexp, @@search). let searcher = regexp.get_method(WellKnownSymbols::search(), context)?; // b. If searcher is not undefined, then - if !searcher.is_undefined() { + if let Some(searcher) = searcher { // i. Return ? Call(searcher, regexp, « O »). - return context.call(&searcher, regexp, &[o.clone()]); + return searcher.call(regexp, &[o.clone()], context); } } diff --git a/boa/src/builtins/string/string_iterator.rs b/boa/src/builtins/string/string_iterator.rs index 2adf8e8069..f6147bf11d 100644 --- a/boa/src/builtins/string/string_iterator.rs +++ b/boa/src/builtins/string/string_iterator.rs @@ -32,42 +32,39 @@ impl StringIterator { } pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - if let JsValue::Object(ref object) = this { - let mut object = object.borrow_mut(); - if let Some(string_iterator) = object.as_string_iterator_mut() { - if string_iterator.string.is_undefined() { - return Ok(create_iter_result_object( - JsValue::undefined(), - true, - context, - )); - } - let native_string = string_iterator.string.to_string(context)?; - let len = native_string.encode_utf16().count() as i32; - let position = string_iterator.next_index; - if position >= len { - string_iterator.string = JsValue::undefined(); - return Ok(create_iter_result_object( - JsValue::undefined(), - true, - context, - )); - } - let (_, code_unit_count, _) = - code_point_at(native_string, position).expect("Invalid code point position"); - string_iterator.next_index += code_unit_count as i32; - let result_string = crate::builtins::string::String::substring( - &string_iterator.string, - &[position.into(), string_iterator.next_index.into()], - context, - )?; - Ok(create_iter_result_object(result_string, false, context)) - } else { - context.throw_type_error("`this` is not an ArrayIterator") - } - } else { - context.throw_type_error("`this` is not an ArrayIterator") + let mut string_iterator = this.as_object().map(|obj| obj.borrow_mut()); + let string_iterator = string_iterator + .as_mut() + .and_then(|obj| obj.as_string_iterator_mut()) + .ok_or_else(|| context.construct_type_error("`this` is not an ArrayIterator"))?; + + if string_iterator.string.is_undefined() { + return Ok(create_iter_result_object( + JsValue::undefined(), + true, + context, + )); + } + let native_string = string_iterator.string.to_string(context)?; + let len = native_string.encode_utf16().count() as i32; + let position = string_iterator.next_index; + if position >= len { + string_iterator.string = JsValue::undefined(); + return Ok(create_iter_result_object( + JsValue::undefined(), + true, + context, + )); } + let (_, code_unit_count, _) = + code_point_at(native_string, position).expect("Invalid code point position"); + string_iterator.next_index += code_unit_count as i32; + let result_string = crate::builtins::string::String::substring( + &string_iterator.string, + &[position.into(), string_iterator.next_index.into()], + context, + )?; + Ok(create_iter_result_object(result_string, false, context)) } /// Create the %ArrayIteratorPrototype% object diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index 1bc43b1e0b..cc45939811 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -102,12 +102,12 @@ impl BuiltIn for Symbol { let to_primitive = FunctionBuilder::native(context, Self::to_primitive) .name("[Symbol.toPrimitive]") .length(1) - .constructable(false) + .constructor(false) .build(); let get_description = FunctionBuilder::native(context, Self::get_description) .name("get description") - .constructable(false) + .constructor(false) .build(); let symbol_object = ConstructorBuilder::with_standard_object( @@ -141,7 +141,7 @@ impl BuiltIn for Symbol { Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ) .callable(true) - .constructable(false) + .constructor(false) .property( symbol_to_string_tag, Self::NAME, @@ -190,18 +190,10 @@ impl Symbol { } fn this_symbol_value(value: &JsValue, context: &mut Context) -> JsResult { - match value { - JsValue::Symbol(ref symbol) => return Ok(symbol.clone()), - JsValue::Object(ref object) => { - let object = object.borrow(); - if let Some(symbol) = object.as_symbol() { - return Ok(symbol); - } - } - _ => {} - } - - Err(context.construct_type_error("'this' is not a Symbol")) + value + .as_symbol() + .or_else(|| value.as_object().and_then(|obj| obj.borrow().as_symbol())) + .ok_or_else(|| context.construct_type_error("'this' is not a Symbol")) } /// `Symbol.prototype.toString()` diff --git a/boa/src/builtins/typed_array/mod.rs b/boa/src/builtins/typed_array/mod.rs index bdaba5b9db..658e821d73 100644 --- a/boa/src/builtins/typed_array/mod.rs +++ b/boa/src/builtins/typed_array/mod.rs @@ -56,7 +56,7 @@ macro_rules! typed_array { let get_species = FunctionBuilder::native(context, TypedArray::get_species) .name("get [Symbol.species]") - .constructable(false) + .constructor(false) .build(); ConstructorBuilder::with_standard_object( @@ -146,7 +146,11 @@ macro_rules! typed_array { // ii. If firstArgument has a [[TypedArrayName]] internal slot, then if first_argument.is_typed_array() { // 1. Perform ? InitializeTypedArrayFromTypedArray(O, firstArgument). - TypedArray::initialize_from_typed_array(&o, first_argument, context)?; + TypedArray::initialize_from_typed_array( + &o, + first_argument.clone(), + context, + )?; } else if first_argument.is_array_buffer() { // iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then @@ -159,7 +163,7 @@ macro_rules! typed_array { // 3. Perform ? InitializeTypedArrayFromArrayBuffer(O, firstArgument, byteOffset, length). TypedArray::initialize_from_array_buffer( &o, - first_argument, + first_argument.clone(), byte_offset, length, context, @@ -177,10 +181,13 @@ macro_rules! typed_array { first_argument_v.get_method(WellKnownSymbols::replace(), context)?; // 3. If usingIterator is not undefined, then - if !using_iterator.is_undefined() { + if let Some(using_iterator) = using_iterator { // a. Let values be ? IterableToList(firstArgument, usingIterator). - let values = - iterable_to_list(context, first_argument_v, Some(using_iterator))?; + let values = iterable_to_list( + context, + first_argument_v, + Some(using_iterator.into()), + )?; // b. Perform ? InitializeTypedArrayFromList(O, values). TypedArray::initialize_from_list(&o, values, context)?; @@ -233,38 +240,38 @@ impl TypedArray { pub(crate) fn init(context: &mut Context) -> JsObject { let get_species = FunctionBuilder::native(context, Self::get_species) .name("get [Symbol.species]") - .constructable(false) + .constructor(false) .build(); let get_buffer = FunctionBuilder::native(context, Self::buffer) .name("get buffer") - .constructable(false) + .constructor(false) .build(); let get_byte_length = FunctionBuilder::native(context, Self::byte_length) .name("get byteLength") - .constructable(false) + .constructor(false) .build(); let get_byte_offset = FunctionBuilder::native(context, Self::byte_offset) .name("get byteOffset") - .constructable(false) + .constructor(false) .build(); let get_length = FunctionBuilder::native(context, Self::length) .name("get length") - .constructable(false) + .constructor(false) .build(); let get_to_string_tag = FunctionBuilder::native(context, Self::to_string_tag) .name("get [Symbol.toStringTag]") - .constructable(false) + .constructor(false) .build(); let values_function = FunctionBuilder::native(context, Self::values) .name("values") .length(0) - .constructable(false) + .constructor(false) .build(); let object = ConstructorBuilder::with_standard_object( @@ -380,7 +387,7 @@ impl TypedArray { // 1. Let C be the this value. // 2. If IsConstructor(C) is false, throw a TypeError exception. let constructor = match this.as_object() { - Some(obj) if obj.is_constructable() => obj, + Some(obj) if obj.is_constructor() => obj, _ => { return context .throw_type_error("TypedArray.from called on non-constructable value") @@ -409,13 +416,13 @@ impl TypedArray { let this_arg = args.get_or_undefined(2); // 6. If usingIterator is not undefined, then - if !using_iterator.is_undefined() { + if let Some(using_iterator) = using_iterator { // a. Let values be ? IterableToList(source, usingIterator). - let values = iterable_to_list(context, source.clone(), Some(using_iterator))?; + let values = iterable_to_list(context, source.clone(), Some(using_iterator.into()))?; // b. Let len be the number of elements in values. // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let target_obj = Self::create(&constructor, &[values.len().into()], context)?; + let target_obj = Self::create(constructor, &[values.len().into()], context)?; // d. Let k be 0. // e. Repeat, while k < len, @@ -451,7 +458,7 @@ impl TypedArray { let len = array_like.length_of_array_like(context)?; // 10. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let target_obj = Self::create(&constructor, &[len.into()], context)?; + let target_obj = Self::create(constructor, &[len.into()], context)?; // 11. Let k be 0. // 12. Repeat, while k < len, @@ -490,14 +497,14 @@ impl TypedArray { // 2. Let C be the this value. // 3. If IsConstructor(C) is false, throw a TypeError exception. let constructor = match this.as_object() { - Some(obj) if obj.is_constructable() => obj, + Some(obj) if obj.is_constructor() => obj, _ => { return context.throw_type_error("TypedArray.of called on non-constructable value") } }; // 4. Let newObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let new_obj = Self::create(&constructor, &[args.len().into()], context)?; + let new_obj = Self::create(constructor, &[args.len().into()], context)?; // 5. Let k be 0. // 6. Repeat, while k < len, @@ -846,7 +853,7 @@ impl TypedArray { // 3. Return CreateArrayIterator(O, key+value). Ok(ArrayIterator::create_array_iterator( - o, + o.clone(), PropertyNameKind::KeyAndValue, context, )) @@ -1057,7 +1064,7 @@ impl TypedArray { } // 9. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(captured) »). - let a = Self::species_create(&obj, o.typed_array_name(), &[captured.into()], context)?; + let a = Self::species_create(obj, o.typed_array_name(), &[captured.into()], context)?; // 10. Let n be 0. // 11. For each element e of kept, do @@ -1473,7 +1480,7 @@ impl TypedArray { // 3. Return CreateArrayIterator(O, key). Ok(ArrayIterator::create_array_iterator( - o, + o.clone(), PropertyNameKind::Key, context, )) @@ -1616,7 +1623,7 @@ impl TypedArray { }; // 5. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(len) »). - let a = Self::species_create(&obj, o.typed_array_name(), &[len.into()], context)?; + let a = Self::species_create(obj, o.typed_array_name(), &[len.into()], context)?; // 6. Let k be 0. // 7. Repeat, while k < len, @@ -1888,12 +1895,12 @@ impl TypedArray { // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then JsValue::Object(source) if source.is_typed_array() => { // a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source). - Self::set_typed_array_from_typed_array(&target, target_offset, source, context)?; + Self::set_typed_array_from_typed_array(target, target_offset, source, context)?; } // 7. Else, _ => { // a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source). - Self::set_typed_array_from_array_like(&target, target_offset, source, context)?; + Self::set_typed_array_from_array_like(target, target_offset, source, context)?; } } @@ -2287,7 +2294,7 @@ impl TypedArray { let count = std::cmp::max(r#final - k, 0) as usize; // 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »). - let a = Self::species_create(&obj, o.typed_array_name(), &[count.into()], context)?; + let a = Self::species_create(obj, o.typed_array_name(), &[count.into()], context)?; let a_borrow = a.borrow(); let a_array = a_borrow .as_typed_array() @@ -2658,7 +2665,7 @@ impl TypedArray { } // 12. Return obj. - Ok(obj.into()) + Ok(obj.clone().into()) } /// `23.2.3.28 %TypedArray%.prototype.subarray ( begin, end )` @@ -2732,7 +2739,7 @@ impl TypedArray { // 19. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ». // 20. Return ? TypedArraySpeciesCreate(O, argumentsList). Ok(Self::species_create( - &obj, + obj, o.typed_array_name(), &[ buffer.clone().into(), @@ -2768,7 +2775,7 @@ impl TypedArray { // 3. Return CreateArrayIterator(O, value). Ok(ArrayIterator::create_array_iterator( - o, + o.clone(), PropertyNameKind::Value, context, )) @@ -2886,10 +2893,8 @@ impl TypedArray { } } - drop(obj_borrow); - // 4. Return newTypedArray. - Ok(obj) + Ok(obj.clone()) } /// @@ -2942,10 +2947,10 @@ impl TypedArray { let len = values.len(); { let mut o = o.borrow_mut(); - let mut o_inner = o.as_typed_array_mut().expect("expected a TypedArray"); + let o_inner = o.as_typed_array_mut().expect("expected a TypedArray"); // 2. Perform ? AllocateTypedArrayBuffer(O, len). - TypedArray::allocate_buffer(&mut o_inner, len, context)?; + TypedArray::allocate_buffer(o_inner, len, context)?; } // 3. Let k be 0. @@ -3037,10 +3042,6 @@ impl TypedArray { let src_data_obj = src_array .viewed_array_buffer() .expect("Already checked for detached buffer"); - let src_data_obj_b = src_data_obj.borrow(); - let src_data = src_data_obj_b - .as_array_buffer() - .expect("Already checked for detached buffer"); // 3. Let constructorName be the String value of O.[[TypedArrayName]]. // 4. Let elementType be the Element Type value in Table 73 for constructorName. @@ -3069,6 +3070,11 @@ impl TypedArray { let buffer_constructor = src_data_obj.species_constructor(StandardObjects::array_buffer_object, context)?; + let src_data_obj_b = src_data_obj.borrow(); + let src_data = src_data_obj_b + .as_array_buffer() + .expect("Already checked for detached buffer"); + // 14. If elementType is the same as srcType, then let data = if constructor_name == src_name { // a. Let data be ? CloneArrayBuffer(srcData, srcByteOffset, byteLength, bufferConstructor). diff --git a/boa/src/bytecompiler.rs b/boa/src/bytecompiler.rs index fa06b30e1b..7bc0007da8 100644 --- a/boa/src/bytecompiler.rs +++ b/boa/src/bytecompiler.rs @@ -849,7 +849,7 @@ impl ByteCompiler { let mut code = CodeBlock::new(name.unwrap_or("").into(), length, false, true); if let FunctionKind::Arrow = kind { - code.constructable = false; + code.constructor = false; code.this_mode = ThisMode::Lexical; } diff --git a/boa/src/class.rs b/boa/src/class.rs index bc7806eca1..69acd51a56 100644 --- a/boa/src/class.rs +++ b/boa/src/class.rs @@ -110,18 +110,18 @@ impl ClassConstructor for T { )); } - let class_constructor = - if let Some(obj) = context.global_object().get(T::NAME, context)?.as_object() { - obj - } else { - return context.throw_type_error(format!( - "invalid constructor for native class `{}` ", - T::NAME - )); - }; + let class_constructor = context.global_object().get(T::NAME, context)?; + let class_constructor = if let JsValue::Object(ref obj) = class_constructor { + obj + } else { + return context.throw_type_error(format!( + "invalid constructor for native class `{}` ", + T::NAME + )); + }; let class_prototype = - if let Some(obj) = class_constructor.get(PROTOTYPE, context)?.as_object() { - obj + if let JsValue::Object(ref obj) = class_constructor.get(PROTOTYPE, context)? { + obj.clone() } else { return context.throw_type_error(format!( "invalid default prototype for native class `{}`", @@ -131,12 +131,13 @@ impl ClassConstructor for T { let prototype = this .as_object() - .and_then(|obj| { + .cloned() + .map(|obj| { obj.get(PROTOTYPE, context) - .map(|o| o.as_object()) - .transpose() + .map(|val| val.as_object().cloned()) }) .transpose()? + .flatten() .unwrap_or(class_prototype); let native_instance = Self::constructor(this, args, context)?; diff --git a/boa/src/context.rs b/boa/src/context.rs index e4ff82b3b1..636da4b277 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -426,7 +426,8 @@ impl Default for Context { .get("prototype", &mut context) .expect("prototype must exist") .as_object() - .expect("prototype must be object"); + .expect("prototype must be object") + .clone(); context.typed_array_constructor.constructor = typed_array_constructor_constructor; context.typed_array_constructor.prototype = typed_array_constructor_prototype; context.create_intrinsics(); @@ -516,10 +517,9 @@ impl Context { this: &JsValue, args: &[JsValue], ) -> JsResult { - match *f { - JsValue::Object(ref object) if object.is_callable() => object.call(this, args, self), - _ => self.throw_type_error("Value is not callable"), - } + f.as_callable() + .ok_or_else(|| self.construct_type_error("Value is not callable")) + .and_then(|obj| obj.call(this, args, self)) } /// Return the global object. @@ -694,7 +694,7 @@ impl Context { name: N, params: P, mut body: StatementList, - constructable: bool, + constructor: bool, this_mode: ThisMode, ) -> JsResult where @@ -715,7 +715,7 @@ impl Context { let params = params.into(); let params_len = params.len(); let func = Function::Ordinary { - constructable, + constructor, this_mode, body: RcStatementList::from(body), params, @@ -782,7 +782,7 @@ impl Context { let function = FunctionBuilder::native(self, body) .name(name) .length(length) - .constructable(true) + .constructor(true) .build(); self.global_object().insert_property( @@ -825,7 +825,7 @@ impl Context { let function = FunctionBuilder::closure(self, body) .name(name) .length(length) - .constructable(true) + .constructor(true) .build(); self.global_object().insert_property( diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 6439e4c9cf..f09297ad72 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -792,10 +792,12 @@ mod in_operator { let bar_obj = bar_val.as_object().unwrap(); let foo_val = forward_val(&mut context, "Foo").unwrap(); assert_eq!( - &*bar_obj.prototype(), - &foo_val + *bar_obj.prototype(), + foo_val.as_object().and_then(|obj| obj + .get("prototype", &mut context) + .unwrap() .as_object() - .and_then(|obj| obj.get("prototype", &mut context).unwrap().as_object()) + .cloned()) ); } } diff --git a/boa/src/object/internal_methods/bound_function.rs b/boa/src/object/internal_methods/bound_function.rs new file mode 100644 index 0000000000..3949cd93d3 --- /dev/null +++ b/boa/src/object/internal_methods/bound_function.rs @@ -0,0 +1,100 @@ +use crate::{object::JsObject, Context, JsResult, JsValue}; + +use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; + +/// Definitions of the internal object methods for function objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects +pub(crate) static BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = + InternalObjectMethods { + __call__: Some(bound_function_exotic_call), + __construct__: None, + ..ORDINARY_INTERNAL_METHODS + }; + +pub(crate) static BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = + InternalObjectMethods { + __call__: Some(bound_function_exotic_call), + __construct__: Some(bound_function_exotic_construct), + ..ORDINARY_INTERNAL_METHODS + }; + +/// Internal method `[[Call]]` for Bound Function Exotic Objects +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist +#[track_caller] +#[inline] +fn bound_function_exotic_call( + obj: &JsObject, + _: &JsValue, + arguments_list: &[JsValue], + context: &mut Context, +) -> JsResult { + let obj = obj.borrow(); + let bound_function = obj + .as_bound_function() + .expect("bound function exotic method should only be callable from bound function objects"); + + // 1. Let target be F.[[BoundTargetFunction]]. + let target = bound_function.target_function(); + + // 2. Let boundThis be F.[[BoundThis]]. + let bound_this = bound_function.this(); + + // 3. Let boundArgs be F.[[BoundArguments]]. + let bound_args = bound_function.args(); + + // 4. Let args be the list-concatenation of boundArgs and argumentsList. + let mut args = bound_args.to_vec(); + args.extend_from_slice(arguments_list); + + // 5. Return ? Call(target, boundThis, args). + target.call(bound_this, &args, context) +} + +/// Internal method `[[Construct]]` for Bound Function Exotic Objects +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-construct-argumentslist-newtarget +#[track_caller] +#[inline] +fn bound_function_exotic_construct( + obj: &JsObject, + arguments_list: &[JsValue], + new_target: &JsValue, + context: &mut Context, +) -> JsResult { + let object = obj.borrow(); + let bound_function = object + .as_bound_function() + .expect("bound function exotic method should only be callable from bound function objects"); + + // 1. Let target be F.[[BoundTargetFunction]]. + let target = bound_function.target_function(); + + // 2. Assert: IsConstructor(target) is true. + + // 3. Let boundArgs be F.[[BoundArguments]]. + let bound_args = bound_function.args(); + + // 4. Let args be the list-concatenation of boundArgs and argumentsList. + let mut args = bound_args.to_vec(); + args.extend_from_slice(arguments_list); + + // 5. If SameValue(F, newTarget) is true, set newTarget to target. + let new_target = match new_target { + JsValue::Object(new_target) if JsObject::equals(obj, new_target) => target.clone().into(), + _ => new_target.clone(), + }; + + // 6. Return ? Construct(target, args, newTarget). + target.construct(&args, &new_target, context) +} diff --git a/boa/src/object/internal_methods/function.rs b/boa/src/object/internal_methods/function.rs new file mode 100644 index 0000000000..40bcaf527e --- /dev/null +++ b/boa/src/object/internal_methods/function.rs @@ -0,0 +1,314 @@ +use crate::{ + builtins::function::{Captures, ClosureFunctionSignature, Function, NativeFunctionSignature}, + environment::{ + function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, + lexical_environment::Environment, + }, + exec::{Executable, InterpreterState}, + object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + syntax::ast::node::RcStatementList, + Context, JsResult, JsValue, +}; + +use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; +use crate::{builtins::function::arguments::Arguments, context::StandardObjects}; + +/// Definitions of the internal object methods for function objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects +pub(crate) static FUNCTION_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { + __call__: Some(function_call), + __construct__: None, + ..ORDINARY_INTERNAL_METHODS +}; + +pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { + __call__: Some(function_call), + __construct__: Some(function_construct), + ..ORDINARY_INTERNAL_METHODS +}; + +/// Call this object. +/// +/// # Panics +/// +/// Panics if the object is currently mutably borrowed. +// +// +#[track_caller] +#[inline] +fn function_call( + obj: &JsObject, + this: &JsValue, + args: &[JsValue], + context: &mut Context, +) -> JsResult { + call_construct(obj, this, args, context, false) +} + +/// Construct an instance of this object with the specified arguments. +/// +/// # Panics +/// +/// Panics if the object is currently mutably borrowed. +// +#[track_caller] +#[inline] +fn function_construct( + obj: &JsObject, + args: &[JsValue], + new_target: &JsValue, + context: &mut Context, +) -> JsResult { + call_construct(obj, new_target, args, context, true) +} + +/// Internal implementation of [`call`](#method.call) and [`construct`](#method.construct). +/// +/// # Panics +/// +/// Panics if the object is currently mutably borrowed. +/// +/// +/// +/// +/// +#[track_caller] +pub(super) fn call_construct( + obj: &JsObject, + this_target: &JsValue, + args: &[JsValue], + context: &mut Context, + construct: bool, +) -> JsResult { + /// The body of a JavaScript function. + /// + /// This is needed for the call method since we cannot mutate the function itself since we + /// already borrow it so we get the function body clone it then drop the borrow and run the body + enum FunctionBody { + BuiltInFunction(NativeFunctionSignature), + BuiltInConstructor(NativeFunctionSignature), + Closure { + function: Box, + captures: Captures, + }, + Ordinary(RcStatementList), + } + + let this_function_object = obj.clone(); + let mut has_parameter_expressions = false; + + let body = if let Some(function) = obj.borrow().as_function() { + if construct && !function.is_constructor() { + let name = obj.get("name", context)?.display().to_string(); + return context.throw_type_error(format!("{} is not a constructor", name)); + } else { + match function { + Function::Native { + function, + constructor, + } => { + if *constructor || construct { + FunctionBody::BuiltInConstructor(*function) + } else { + FunctionBody::BuiltInFunction(*function) + } + } + Function::Closure { + function, captures, .. + } => FunctionBody::Closure { + function: function.clone(), + captures: captures.clone(), + }, + Function::Ordinary { + constructor: _, + this_mode, + body, + params, + environment, + } => { + let this = if construct { + // If the prototype of the constructor is not an object, then use the default object + // prototype as prototype for the new object + // see + // see + let proto = get_prototype_from_constructor( + this_target, + StandardObjects::object_object, + context, + )?; + JsObject::from_proto_and_data(Some(proto), ObjectData::ordinary()).into() + } else { + this_target.clone() + }; + + // Create a new Function environment whose parent is set to the scope of the function declaration (obj.environment) + // + let local_env = FunctionEnvironmentRecord::new( + this_function_object.clone(), + if construct || !this_mode.is_lexical() { + Some(this.clone()) + } else { + None + }, + Some(environment.clone()), + // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records + if this_mode.is_lexical() { + BindingStatus::Lexical + } else { + BindingStatus::Uninitialized + }, + JsValue::undefined(), + context, + )?; + + let mut arguments_in_parameter_names = false; + let mut is_simple_parameter_list = true; + + for param in params.iter() { + has_parameter_expressions = + has_parameter_expressions || param.init().is_some(); + arguments_in_parameter_names = + arguments_in_parameter_names || param.name() == "arguments"; + is_simple_parameter_list = is_simple_parameter_list + && !param.is_rest_param() + && param.init().is_none() + } + + // Turn local_env into Environment so it can be cloned + let local_env: Environment = local_env.into(); + + // An arguments object is added when all of the following conditions are met + // - If not in an arrow function (10.2.11.16) + // - If the parameter list does not contain `arguments` (10.2.11.17) + // - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18) + // + // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation + if !this_mode.is_lexical() + && !arguments_in_parameter_names + && (has_parameter_expressions + || (!body.lexically_declared_names().contains("arguments") + && !body.function_declared_names().contains("arguments"))) + { + // Add arguments object + let arguments_obj = + if context.strict() || body.strict() || !is_simple_parameter_list { + Arguments::create_unmapped_arguments_object(args, context) + } else { + Arguments::create_mapped_arguments_object( + obj, params, args, &local_env, context, + ) + }; + local_env.create_mutable_binding("arguments", false, true, context)?; + local_env.initialize_binding("arguments", arguments_obj.into(), context)?; + } + + // Push the environment first so that it will be used by default parameters + context.push_environment(local_env.clone()); + + // Add argument bindings to the function environment + for (i, param) in params.iter().enumerate() { + // Rest Parameters + if param.is_rest_param() { + Function::add_rest_param(param, i, args, context, &local_env); + break; + } + + let value = match args.get(i).cloned() { + None | Some(JsValue::Undefined) => param + .init() + .map(|init| init.run(context).ok()) + .flatten() + .unwrap_or_default(), + Some(value) => value, + }; + + Function::add_arguments_to_environment(param, value, &local_env, context); + } + + if has_parameter_expressions { + // Create a second environment when default parameter expressions are used + // This prevents variables declared in the function body from being + // used in default parameter initializers. + // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation + let second_env = FunctionEnvironmentRecord::new( + this_function_object, + if construct || !this_mode.is_lexical() { + Some(this) + } else { + None + }, + Some(local_env), + // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records + if this_mode.is_lexical() { + BindingStatus::Lexical + } else { + BindingStatus::Uninitialized + }, + JsValue::undefined(), + context, + )?; + context.push_environment(second_env); + } + + FunctionBody::Ordinary(body.clone()) + } + #[cfg(feature = "vm")] + Function::VmOrdinary { .. } => { + todo!("vm call") + } + } + } + } else { + return context.throw_type_error("not a function"); + }; + + match body { + FunctionBody::BuiltInConstructor(function) if construct => { + function(this_target, args, context) + } + FunctionBody::BuiltInConstructor(function) => { + function(&JsValue::undefined(), args, context) + } + FunctionBody::BuiltInFunction(function) => function(this_target, args, context), + FunctionBody::Closure { function, captures } => { + (function)(this_target, args, captures, context) + } + FunctionBody::Ordinary(body) => { + let result = body.run(context); + let this = context.get_this_binding(); + + if has_parameter_expressions { + context.pop_environment(); + } + context.pop_environment(); + + if construct { + // https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget + // 12. If result.[[Type]] is return, then + if context.executor().get_current_state() == &InterpreterState::Return { + // a. If Type(result.[[Value]]) is Object, return NormalCompletion(result.[[Value]]). + if let Ok(v) = &result { + if v.is_object() { + return result; + } + } + } + + // 13. Else, ReturnIfAbrupt(result). + result?; + + // 14. Return ? constructorEnv.GetThisBinding(). + this + } else if context.executor().get_current_state() == &InterpreterState::Return { + result + } else { + result?; + Ok(JsValue::undefined()) + } + } + } +} diff --git a/boa/src/object/internal_methods/mod.rs b/boa/src/object/internal_methods/mod.rs index abdfd355eb..e26c9ca259 100644 --- a/boa/src/object/internal_methods/mod.rs +++ b/boa/src/object/internal_methods/mod.rs @@ -17,6 +17,8 @@ use super::{JsPrototype, PROTOTYPE}; pub(super) mod arguments; pub(super) mod array; +pub(super) mod bound_function; +pub(super) mod function; pub(super) mod integer_indexed; pub(super) mod string; @@ -46,7 +48,7 @@ impl JsObject { /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v #[inline] pub(crate) fn __set_prototype_of__( - &mut self, + &self, val: JsPrototype, context: &mut Context, ) -> JsResult { @@ -209,6 +211,50 @@ impl JsObject { let func = self.borrow().data.internal_methods.__own_property_keys__; func(self, context) } + + /// Internal method `[[Call]]` + /// + /// Call this object if it has a `[[Call]]` internal method. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist + #[inline] + #[track_caller] + pub(crate) fn __call__( + &self, + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + let func = self.borrow().data.internal_methods.__call__; + func.expect("called `[[Call]]` for object without a `[[Call]]` internal method")( + self, this, args, context, + ) + } + + /// Internal method `[[Construct]]` + /// + /// Construct a new instance of this object if this object has a `[[Construct]]` internal method. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget + #[inline] + #[track_caller] + pub(crate) fn __construct__( + &self, + args: &[JsValue], + new_target: &JsValue, + context: &mut Context, + ) -> JsResult { + let func = self.borrow().data.internal_methods.__construct__; + func.expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")( + self, args, new_target, context, + ) + } } /// Definitions of the internal object methods for ordinary objects. @@ -234,6 +280,8 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj __set__: ordinary_set, __delete__: ordinary_delete, __own_property_keys__: ordinary_own_property_keys, + __call__: None, + __construct__: None, }; /// The internal representation of the internal methods of a `JsObject`. @@ -244,6 +292,7 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj /// /// For a guide on how to implement exotic internal methods, see `ORDINARY_INTERNAL_METHODS`. #[derive(Clone, Copy)] +#[allow(clippy::type_complexity)] pub(crate) struct InternalObjectMethods { pub(crate) __get_prototype_of__: fn(&JsObject, &mut Context) -> JsResult, pub(crate) __set_prototype_of__: fn(&JsObject, JsPrototype, &mut Context) -> JsResult, @@ -259,6 +308,10 @@ pub(crate) struct InternalObjectMethods { fn(&JsObject, PropertyKey, JsValue, JsValue, &mut Context) -> JsResult, pub(crate) __delete__: fn(&JsObject, &PropertyKey, &mut Context) -> JsResult, pub(crate) __own_property_keys__: fn(&JsObject, &mut Context) -> JsResult>, + pub(crate) __call__: + Option JsResult>, + pub(crate) __construct__: + Option JsResult>, } /// Abstract operation `OrdinaryGetPrototypeOf`. @@ -445,14 +498,12 @@ pub(crate) fn ordinary_has_property( // 4. Let parent be ? O.[[GetPrototypeOf]](). let parent = obj.__get_prototype_of__(context)?; - // 5. If parent is not null, then - if let Some(object) = parent { + parent + // 5. If parent is not null, then // a. Return ? parent.[[HasProperty]](P). - object.__has_property__(key, context) - } else { + .map(|obj| obj.__has_property__(key, context)) // 6. Return false. - Ok(false) - } + .unwrap_or(Ok(false)) } } @@ -880,7 +931,7 @@ where // 2. Let proto be ? Get(constructor, "prototype"). if let Some(object) = constructor.as_object() { if let Some(proto) = object.get(PROTOTYPE, context)?.as_object() { - return Ok(proto); + return Ok(proto.clone()); } } // 3. If Type(proto) is not Object, then diff --git a/boa/src/object/jsobject.rs b/boa/src/object/jsobject.rs index a881cf3e2c..4774edd110 100644 --- a/boa/src/object/jsobject.rs +++ b/boa/src/object/jsobject.rs @@ -18,18 +18,6 @@ use std::{ result::Result as StdResult, }; -#[cfg(not(feature = "vm"))] -use crate::{ - builtins::function::{Captures, ClosureFunctionSignature, Function, NativeFunctionSignature}, - environment::{ - function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, - lexical_environment::Environment, - }, - exec::InterpreterState, - syntax::ast::node::RcStatementList, - Executable, -}; - /// A wrapper type for an immutably borrowed type T. pub type Ref<'a, T> = GcCellRef<'a, T>; @@ -122,266 +110,6 @@ impl JsObject { std::ptr::eq(lhs.as_ref(), rhs.as_ref()) } - /// Internal implementation of [`call`](#method.call) and [`construct`](#method.construct). - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - /// - /// - /// - /// - /// - #[track_caller] - #[cfg(not(feature = "vm"))] - pub(super) fn call_construct( - &self, - this_target: &JsValue, - args: &[JsValue], - context: &mut Context, - construct: bool, - ) -> JsResult { - use crate::{builtins::function::arguments::Arguments, context::StandardObjects}; - - use super::internal_methods::get_prototype_from_constructor; - - /// The body of a JavaScript function. - /// - /// This is needed for the call method since we cannot mutate the function itself since we - /// already borrow it so we get the function body clone it then drop the borrow and run the body - #[cfg(not(feature = "vm"))] - enum FunctionBody { - BuiltInFunction(NativeFunctionSignature), - BuiltInConstructor(NativeFunctionSignature), - Closure { - function: Box, - captures: Captures, - }, - Ordinary(RcStatementList), - } - - let this_function_object = self.clone(); - let mut has_parameter_expressions = false; - - let body = if let Some(function) = self.borrow().as_function() { - if construct && !function.is_constructable() { - let name = self.get("name", context)?.display().to_string(); - return context.throw_type_error(format!("{} is not a constructor", name)); - } else { - match function { - Function::Native { - function, - constructable, - } => { - if *constructable || construct { - FunctionBody::BuiltInConstructor(*function) - } else { - FunctionBody::BuiltInFunction(*function) - } - } - Function::Closure { - function, captures, .. - } => FunctionBody::Closure { - function: function.clone(), - captures: captures.clone(), - }, - Function::Ordinary { - constructable: _, - this_mode, - body, - params, - environment, - } => { - let this = if construct { - // If the prototype of the constructor is not an object, then use the default object - // prototype as prototype for the new object - // see - // see - let proto = get_prototype_from_constructor( - this_target, - StandardObjects::object_object, - context, - )?; - JsObject::from_proto_and_data(Some(proto), ObjectData::ordinary()) - .into() - } else { - this_target.clone() - }; - - // Create a new Function environment whose parent is set to the scope of the function declaration (self.environment) - // - let local_env = FunctionEnvironmentRecord::new( - this_function_object.clone(), - if construct || !this_mode.is_lexical() { - Some(this.clone()) - } else { - None - }, - Some(environment.clone()), - // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records - if this_mode.is_lexical() { - BindingStatus::Lexical - } else { - BindingStatus::Uninitialized - }, - JsValue::undefined(), - context, - )?; - - let mut arguments_in_parameter_names = false; - let mut is_simple_parameter_list = true; - - for param in params.iter() { - has_parameter_expressions = - has_parameter_expressions || param.init().is_some(); - arguments_in_parameter_names = - arguments_in_parameter_names || param.name() == "arguments"; - is_simple_parameter_list = is_simple_parameter_list - && !param.is_rest_param() - && param.init().is_none() - } - - // Turn local_env into Environment so it can be cloned - let local_env: Environment = local_env.into(); - - // An arguments object is added when all of the following conditions are met - // - If not in an arrow function (10.2.11.16) - // - If the parameter list does not contain `arguments` (10.2.11.17) - // - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18) - // - // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation - if !this_mode.is_lexical() - && !arguments_in_parameter_names - && (has_parameter_expressions - || (!body.lexically_declared_names().contains("arguments") - && !body.function_declared_names().contains("arguments"))) - { - // Add arguments object - let arguments_obj = - if context.strict() || body.strict() || !is_simple_parameter_list { - Arguments::create_unmapped_arguments_object(args, context) - } else { - Arguments::create_mapped_arguments_object( - self, params, args, &local_env, context, - ) - }; - local_env.create_mutable_binding("arguments", false, true, context)?; - local_env.initialize_binding( - "arguments", - arguments_obj.into(), - context, - )?; - } - - // Push the environment first so that it will be used by default parameters - context.push_environment(local_env.clone()); - - // Add argument bindings to the function environment - for (i, param) in params.iter().enumerate() { - // Rest Parameters - if param.is_rest_param() { - Function::add_rest_param(param, i, args, context, &local_env); - break; - } - - let value = match args.get(i).cloned() { - None | Some(JsValue::Undefined) => param - .init() - .map(|init| init.run(context).ok()) - .flatten() - .unwrap_or_default(), - Some(value) => value, - }; - - Function::add_arguments_to_environment( - param, value, &local_env, context, - ); - } - - if has_parameter_expressions { - // Create a second environment when default parameter expressions are used - // This prevents variables declared in the function body from being - // used in default parameter initializers. - // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation - let second_env = FunctionEnvironmentRecord::new( - this_function_object, - if construct || !this_mode.is_lexical() { - Some(this) - } else { - None - }, - Some(local_env), - // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records - if this_mode.is_lexical() { - BindingStatus::Lexical - } else { - BindingStatus::Uninitialized - }, - JsValue::undefined(), - context, - )?; - context.push_environment(second_env); - } - - FunctionBody::Ordinary(body.clone()) - } - #[cfg(feature = "vm")] - Function::VmOrdinary { .. } => { - todo!("vm call") - } - } - } - } else { - return context.throw_type_error("not a function"); - }; - - match body { - FunctionBody::BuiltInConstructor(function) if construct => { - function(this_target, args, context) - } - FunctionBody::BuiltInConstructor(function) => { - function(&JsValue::undefined(), args, context) - } - FunctionBody::BuiltInFunction(function) => function(this_target, args, context), - FunctionBody::Closure { function, captures } => { - (function)(this_target, args, captures, context) - } - FunctionBody::Ordinary(body) => { - let result = body.run(context); - let this = context.get_this_binding(); - - if has_parameter_expressions { - context.pop_environment(); - } - context.pop_environment(); - - if construct { - // https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget - // 12. If result.[[Type]] is return, then - if context.executor().get_current_state() == &InterpreterState::Return { - // a. If Type(result.[[Value]]) is Object, return NormalCompletion(result.[[Value]]). - if let Ok(v) = &result { - if v.is_object() { - return result; - } - } - } - - // 13. Else, ReturnIfAbrupt(result). - result?; - - // 14. Return ? constructorEnv.GetThisBinding(). - this - } else if context.executor().get_current_state() == &InterpreterState::Return { - result - } else { - result?; - Ok(JsValue::undefined()) - } - } - } - } - /// Converts an object to a primitive. /// /// Diverges from the spec to prevent a stack overflow when the object is recursive. @@ -435,14 +163,15 @@ impl JsObject { }; // 5. For each name in methodNames in List order, do - let this = JsValue::new(self.clone()); for name in &method_names { // a. Let method be ? Get(O, name). - let method: JsValue = this.get_field(*name, context)?; + let method = self.get(*name, context)?; + // b. If IsCallable(method) is true, then - if method.is_function() { + if let Some(method) = method.as_callable() { // i. Let result be ? Call(method, O). - let result = context.call(&method, &this, &[])?; + let result = method.call(&self.clone().into(), &[], context)?; + // ii. If Type(result) is not Object, return result. if !result.is_object() { return Ok(result); @@ -705,55 +434,6 @@ impl JsObject { self.borrow().is_native_object() } - /// Determines if `value` inherits from the instance object inheritance path. - /// - /// More information: - /// - [EcmaScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance - #[inline] - pub(crate) fn ordinary_has_instance( - &self, - context: &mut Context, - value: &JsValue, - ) -> JsResult { - // 1. If IsCallable(C) is false, return false. - if !self.is_callable() { - return Ok(false); - } - - // TODO: 2. If C has a [[BoundTargetFunction]] internal slot, then - // a. Let BC be C.[[BoundTargetFunction]]. - // b. Return ? InstanceofOperator(O, BC). - - // 3. If Type(O) is not Object, return false. - if let Some(object) = value.as_object() { - // 4. Let P be ? Get(C, "prototype"). - // 5. If Type(P) is not Object, throw a TypeError exception. - if let Some(prototype) = self.get("prototype", context)?.as_object() { - // 6. Repeat, - // a. Set O to ? O.[[GetPrototypeOf]](). - // b. If O is null, return false. - let mut object = object.__get_prototype_of__(context)?; - while let Some(object_prototype) = object { - // c. If SameValue(P, O) is true, return true. - if prototype == object_prototype { - return Ok(true); - } - // a. Set O to ? O.[[GetPrototypeOf]](). - object = object_prototype.__get_prototype_of__(context)?; - } - - Ok(false) - } else { - Err(context - .construct_type_error("function has non-object prototype in instanceof check")) - } - } else { - Ok(false) - } - } - pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult { // 1 is implemented on the method `to_property_descriptor` of value @@ -851,7 +531,7 @@ impl JsObject { /// [spec]: https://tc39.es/ecma262/#sec-copydataproperties #[inline] pub fn copy_data_properties( - &mut self, + &self, source: &JsValue, excluded_keys: Vec, context: &mut Context, @@ -947,7 +627,7 @@ impl JsObject { #[inline] #[track_caller] pub fn is_callable(&self) -> bool { - self.borrow().is_callable() + self.borrow().data.internal_methods.__call__.is_some() } /// It determines if Object is a function object with a `[[Construct]]` internal method. @@ -958,8 +638,8 @@ impl JsObject { /// [spec]: https://tc39.es/ecma262/#sec-isconstructor #[inline] #[track_caller] - pub fn is_constructable(&self) -> bool { - self.borrow().is_constructable() + pub fn is_constructor(&self) -> bool { + self.borrow().data.internal_methods.__construct__.is_some() } /// Returns true if the JsObject is the global for a Realm diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 00c93fb065..a22a970a97 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -5,9 +5,10 @@ use crate::{ array::array_iterator::ArrayIterator, array_buffer::ArrayBuffer, function::arguments::{Arguments, MappedArguments}, - function::{Captures, Function, NativeFunctionSignature}, + function::{BoundFunction, Captures, Function, NativeFunctionSignature}, map::map_iterator::MapIterator, map::ordered_map::OrderedMap, + object::for_in_iterator::ForInIterator, regexp::regexp_string_iterator::RegExpStringIterator, set::ordered_set::OrderedSet, set::set_iterator::SetIterator, @@ -17,7 +18,6 @@ use crate::{ }, context::StandardConstructor, gc::{Finalize, Trace}, - object::internal_methods::arguments::ARGUMENTS_EXOTIC_INTERNAL_METHODS, property::{Attribute, PropertyDescriptor, PropertyKey}, Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue, }; @@ -27,6 +27,22 @@ use std::{ ops::{Deref, DerefMut}, }; +pub use jsobject::{JsObject, RecursionLimiter, Ref, RefMut}; +pub use operations::IntegrityLevel; +pub use property_map::*; + +use self::internal_methods::{ + arguments::ARGUMENTS_EXOTIC_INTERNAL_METHODS, + array::ARRAY_EXOTIC_INTERNAL_METHODS, + bound_function::{ + BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS, BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS, + }, + function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS}, + integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS, + string::STRING_EXOTIC_INTERNAL_METHODS, + InternalObjectMethods, ORDINARY_INTERNAL_METHODS, +}; + #[cfg(test)] mod tests; @@ -35,17 +51,6 @@ mod jsobject; mod operations; mod property_map; -use crate::builtins::object::for_in_iterator::ForInIterator; -use internal_methods::InternalObjectMethods; -pub use jsobject::{JsObject, RecursionLimiter, Ref, RefMut}; -pub use operations::IntegrityLevel; -pub use property_map::*; - -use self::internal_methods::{ - array::ARRAY_EXOTIC_INTERNAL_METHODS, integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS, - string::STRING_EXOTIC_INTERNAL_METHODS, ORDINARY_INTERNAL_METHODS, -}; - /// Static `prototype`, usually set on constructors as a key to point to their respective prototype object. pub static PROTOTYPE: &str = "prototype"; @@ -108,6 +113,7 @@ pub enum ObjectKind { Boolean(bool), ForInIterator(ForInIterator), Function(Function), + BoundFunction(BoundFunction), Set(OrderedSet), SetIterator(SetIterator), String(JsString), @@ -207,8 +213,24 @@ impl ObjectData { /// Create the `Function` object data pub fn function(function: Function) -> Self { Self { + internal_methods: if function.is_constructor() { + &CONSTRUCTOR_INTERNAL_METHODS + } else { + &FUNCTION_INTERNAL_METHODS + }, kind: ObjectKind::Function(function), - internal_methods: &ORDINARY_INTERNAL_METHODS, + } + } + + /// Create the `BoundFunction` object data + pub fn bound_function(bound_function: BoundFunction, constructor: bool) -> Self { + Self { + kind: ObjectKind::BoundFunction(bound_function), + internal_methods: if constructor { + &BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS + } else { + &BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS + }, } } @@ -329,6 +351,7 @@ impl Display for ObjectKind { Self::ArrayBuffer(_) => "ArrayBuffer", Self::ForInIterator(_) => "ForInIterator", Self::Function(_) => "Function", + Self::BoundFunction(_) => "BoundFunction", Self::RegExp(_) => "RegExp", Self::RegExpStringIterator(_) => "RegExpStringIterator", Self::Map(_) => "Map", @@ -380,38 +403,6 @@ impl Object { &self.data.kind } - /// It determines if Object is a callable function with a `[[Call]]` internal method. - /// - /// More information: - /// - [EcmaScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-iscallable - #[inline] - // todo: functions are not the only objects that are callable. - // todo: e.g. https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist - pub fn is_callable(&self) -> bool { - matches!( - self.data, - ObjectData { - kind: ObjectKind::Function(_), - .. - } - ) - } - - /// It determines if Object is a function object with a `[[Construct]]` internal method. - /// - /// More information: - /// - [EcmaScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-isconstructor - #[inline] - // todo: functions are not the only objects that are constructable. - // todo: e.g. https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget - pub fn is_constructable(&self) -> bool { - matches!(self.data, ObjectData{kind: ObjectKind::Function(ref f), ..} if f.is_constructable()) - } - /// Checks if it an `Array` object. #[inline] pub fn is_array(&self) -> bool { @@ -704,6 +695,28 @@ impl Object { } } + #[inline] + pub fn as_function_mut(&mut self) -> Option<&mut Function> { + match self.data { + ObjectData { + kind: ObjectKind::Function(ref mut function), + .. + } => Some(function), + _ => None, + } + } + + #[inline] + pub fn as_bound_function(&self) -> Option<&BoundFunction> { + match self.data { + ObjectData { + kind: ObjectKind::BoundFunction(ref bound_function), + .. + } => Some(bound_function), + _ => None, + } + } + /// Checks if it a Symbol object. #[inline] pub fn is_symbol(&self) -> bool { @@ -1151,7 +1164,7 @@ impl<'context> FunctionBuilder<'context> { context, function: Some(Function::Native { function, - constructable: false, + constructor: false, }), name: JsString::default(), length: 0, @@ -1168,7 +1181,7 @@ impl<'context> FunctionBuilder<'context> { context, function: Some(Function::Closure { function: Box::new(move |this, args, _, context| function(this, args, context)), - constructable: false, + constructor: false, captures: Captures::new(()), }), name: JsString::default(), @@ -1190,16 +1203,19 @@ impl<'context> FunctionBuilder<'context> { ) -> Self where F: Fn(&JsValue, &[JsValue], &mut C, &mut Context) -> JsResult + Copy + 'static, - C: NativeObject + Clone, + C: NativeObject, { Self { context, function: Some(Function::Closure { - function: Box::new(move |this, args, mut captures: Captures, context| { - let data = captures.try_downcast_mut::(context)?; - function(this, args, data, context) + function: Box::new(move |this, args, captures: Captures, context| { + let mut captures = captures.as_mut_any(); + let captures = captures.downcast_mut::().ok_or_else(|| { + context.construct_type_error("cannot downcast `Captures` to given type") + })?; + function(this, args, captures, context) }), - constructable: false, + constructor: false, captures: Captures::new(captures), }), name: JsString::default(), @@ -1234,10 +1250,10 @@ impl<'context> FunctionBuilder<'context> { /// /// The default is `false`. #[inline] - pub fn constructable(&mut self, yes: bool) -> &mut Self { + pub fn constructor(&mut self, yes: bool) -> &mut Self { match self.function.as_mut() { - Some(Function::Native { constructable, .. }) => *constructable = yes, - Some(Function::Closure { constructable, .. }) => *constructable = yes, + Some(Function::Native { constructor, .. }) => *constructor = yes, + Some(Function::Closure { constructor, .. }) => *constructor = yes, _ => unreachable!(), } self @@ -1337,7 +1353,7 @@ impl<'context> ObjectInitializer<'context> { let function = FunctionBuilder::native(self.context, function) .name(binding.name) .length(length) - .constructable(false) + .constructor(false) .build(); self.object.borrow_mut().insert_property( @@ -1383,7 +1399,7 @@ pub struct ConstructorBuilder<'context> { name: JsString, length: usize, callable: bool, - constructable: bool, + constructor: bool, inherit: Option, custom_prototype: Option, } @@ -1397,7 +1413,7 @@ impl Debug for ConstructorBuilder<'_> { .field("prototype", &self.prototype) .field("inherit", &self.inherit) .field("callable", &self.callable) - .field("constructable", &self.constructable) + .field("constructor", &self.constructor) .finish() } } @@ -1414,7 +1430,7 @@ impl<'context> ConstructorBuilder<'context> { length: 0, name: JsString::default(), callable: true, - constructable: true, + constructor: true, inherit: None, custom_prototype: None, } @@ -1434,7 +1450,7 @@ impl<'context> ConstructorBuilder<'context> { length: 0, name: JsString::default(), callable: true, - constructable: true, + constructor: true, inherit: None, custom_prototype: None, } @@ -1455,7 +1471,7 @@ impl<'context> ConstructorBuilder<'context> { let function = FunctionBuilder::native(self.context, function) .name(binding.name) .length(length) - .constructable(false) + .constructor(false) .build(); self.prototype.borrow_mut().insert_property( @@ -1484,7 +1500,7 @@ impl<'context> ConstructorBuilder<'context> { let function = FunctionBuilder::native(self.context, function) .name(binding.name) .length(length) - .constructable(false) + .constructor(false) .build(); self.constructor_object.borrow_mut().insert_property( @@ -1630,8 +1646,8 @@ impl<'context> ConstructorBuilder<'context> { /// /// Default is `true` #[inline] - pub fn constructable(&mut self, constructable: bool) -> &mut Self { - self.constructable = constructable; + pub fn constructor(&mut self, constructor: bool) -> &mut Self { + self.constructor = constructor; self } @@ -1664,7 +1680,7 @@ impl<'context> ConstructorBuilder<'context> { // Create the native function let function = Function::Native { function: self.constructor_function, - constructable: self.constructable, + constructor: self.constructor, }; let length = PropertyDescriptor::builder() diff --git a/boa/src/object/operations.rs b/boa/src/object/operations.rs index 7037093fc1..921029cc12 100644 --- a/boa/src/object/operations.rs +++ b/boa/src/object/operations.rs @@ -266,7 +266,13 @@ impl JsObject { args: &[JsValue], context: &mut Context, ) -> JsResult { - self.call_construct(this, args, context, false) + // 1. If argumentsList is not present, set argumentsList to a new empty List. + // 2. If IsCallable(F) is false, throw a TypeError exception. + if !self.is_callable() { + return context.throw_type_error("not a function"); + } + // 3. Return ? F.[[Call]](V, argumentsList). + self.__call__(this, args, context) } /// Construct an instance of this object with the specified arguments. @@ -284,7 +290,10 @@ impl JsObject { new_target: &JsValue, context: &mut Context, ) -> JsResult { - self.call_construct(new_target, args, context, true) + // 1. If newTarget is not present, set newTarget to F. + // 2. If argumentsList is not present, set argumentsList to a new empty List. + // 3. Return ? F.[[Construct]](argumentsList, newTarget). + self.__construct__(args, new_target, context) } /// Make the object [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen]. @@ -435,7 +444,7 @@ impl JsObject { // 1. Assert: Type(O) is Object. // 2. Let C be ? Get(O, "constructor"). - let c = self.clone().get("constructor", context)?; + let c = self.get("constructor", context)?; // 3. If C is undefined, return defaultConstructor. if c.is_undefined() { @@ -457,14 +466,9 @@ impl JsObject { // 7. If IsConstructor(S) is true, return S. // 8. Throw a TypeError exception. - if let Some(obj) = s.as_object() { - if obj.is_constructable() { - Ok(obj) - } else { - Err(context.construct_type_error("property 'constructor' is not a constructor")) - } - } else { - Err(context.construct_type_error("property 'constructor' is not an object")) + match s.as_object() { + Some(obj) if obj.is_constructor() => Ok(obj.clone()), + _ => Err(context.construct_type_error("property 'constructor' is not a constructor")), } } @@ -580,26 +584,20 @@ impl JsValue { /// - [EcmaScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-getmethod - pub(crate) fn get_method(&self, key: K, context: &mut Context) -> JsResult + pub(crate) fn get_method(&self, key: K, context: &mut Context) -> JsResult> where K: Into, { // 1. Assert: IsPropertyKey(P) is true. // 2. Let func be ? GetV(V, P). - let func = self.get_v(key, context)?; - - // 3. If func is either undefined or null, return undefined. - if func.is_null_or_undefined() { - return Ok(JsValue::undefined()); - } - - // 4. If IsCallable(func) is false, throw a TypeError exception. - if !func.is_callable() { - Err(context - .construct_type_error("value returned for property of object is not a function")) - } else { + match &self.get_v(key, context)? { + // 3. If func is either undefined or null, return undefined. + JsValue::Undefined | JsValue::Null => Ok(None), // 5. Return func. - Ok(func) + JsValue::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())), + // 4. If IsCallable(func) is false, throw a TypeError exception. + _ => Err(context + .construct_type_error("value returned for property of object is not a function")), } } @@ -685,4 +683,67 @@ impl JsValue { // 3. Return ? Call(func, V, argumentsList) context.call(&func, self, args) } + + /// Abstract operation `OrdinaryHasInstance ( C, O )` + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance + pub fn ordinary_has_instance( + function: &JsValue, + object: &JsValue, + context: &mut Context, + ) -> JsResult { + // 1. If IsCallable(C) is false, return false. + let function = if let Some(function) = function.as_callable() { + function + } else { + return Ok(false); + }; + + // 2. If C has a [[BoundTargetFunction]] internal slot, then + if let Some(bound_function) = function.borrow().as_bound_function() { + // a. Let BC be C.[[BoundTargetFunction]]. + // b. Return ? InstanceofOperator(O, BC). + return JsValue::instance_of( + object, + &bound_function.target_function().clone().into(), + context, + ); + } + + let mut object = if let Some(obj) = object.as_object() { + obj.clone() + } else { + // 3. If Type(O) is not Object, return false. + return Ok(false); + }; + + // 4. Let P be ? Get(C, "prototype"). + let prototype = function.get("prototype", context)?; + + let prototype = if let Some(obj) = prototype.as_object() { + obj + } else { + // 5. If Type(P) is not Object, throw a TypeError exception. + return Err(context + .construct_type_error("function has non-object prototype in instanceof check")); + }; + + // 6. Repeat, + loop { + // a. Set O to ? O.[[GetPrototypeOf]](). + object = match object.__get_prototype_of__(context)? { + Some(obj) => obj, + // b. If O is null, return false. + None => return Ok(false), + }; + + // c. If SameValue(P, O) is true, return true. + if JsObject::equals(&object, prototype) { + return Ok(true); + } + } + } } diff --git a/boa/src/syntax/ast/node/declaration/mod.rs b/boa/src/syntax/ast/node/declaration/mod.rs index d6050a0d15..2b926af1aa 100644 --- a/boa/src/syntax/ast/node/declaration/mod.rs +++ b/boa/src/syntax/ast/node/declaration/mod.rs @@ -519,7 +519,7 @@ impl DeclarationPatternObject { // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). // 2. Let restObj be ! OrdinaryObjectCreate(%Object.prototype%). - let mut rest_obj = context.construct_object(); + let rest_obj = context.construct_object(); // 3. Perform ? CopyDataProperties(restObj, value, excludedNames). rest_obj.copy_data_properties(value, excluded_keys.clone(), context)?; diff --git a/boa/src/syntax/ast/node/new/mod.rs b/boa/src/syntax/ast/node/new/mod.rs index 433911a653..2536f373af 100644 --- a/boa/src/syntax/ast/node/new/mod.rs +++ b/boa/src/syntax/ast/node/new/mod.rs @@ -70,13 +70,15 @@ impl Executable for New { } } - match func_object { - JsValue::Object(ref object) => { - object.construct(&v_args, &object.clone().into(), context) - } - _ => context - .throw_type_error(format!("{} is not a constructor", self.expr().to_string(),)), - } + func_object + .as_constructor() + .ok_or_else(|| { + context.construct_type_error(format!( + "{} is not a constructor", + self.expr().to_string(), + )) + }) + .and_then(|cons| cons.construct(&v_args, &cons.clone().into(), context)) } } diff --git a/boa/src/syntax/ast/node/object/mod.rs b/boa/src/syntax/ast/node/object/mod.rs index b54a93acfd..4d06bfb5aa 100644 --- a/boa/src/syntax/ast/node/object/mod.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -89,7 +89,7 @@ impl Object { impl Executable for Object { fn run(&self, context: &mut Context) -> JsResult { let _timer = BoaProfiler::global().start_event("object", "exec"); - let mut obj = context.construct_object(); + let obj = context.construct_object(); // TODO: Implement the rest of the property types. for property in self.properties().iter() { @@ -141,7 +141,7 @@ impl Executable for Object { obj.__define_own_property__( name, PropertyDescriptor::builder() - .maybe_get(func.run(context)?.as_object()) + .maybe_get(func.run(context)?.as_object().cloned()) .maybe_set(set) .enumerable(true) .configurable(true) @@ -159,7 +159,7 @@ impl Executable for Object { name, PropertyDescriptor::builder() .maybe_get(get) - .maybe_set(func.run(context)?.as_object()) + .maybe_set(func.run(context)?.as_object().cloned()) .enumerable(true) .configurable(true) .build(), diff --git a/boa/src/syntax/ast/node/operator/bin_op/mod.rs b/boa/src/syntax/ast/node/operator/bin_op/mod.rs index 54182a2aee..321cb7d605 100644 --- a/boa/src/syntax/ast/node/operator/bin_op/mod.rs +++ b/boa/src/syntax/ast/node/operator/bin_op/mod.rs @@ -1,7 +1,6 @@ use crate::{ exec::Executable, gc::{Finalize, Trace}, - symbol::WellKnownSymbols, syntax::ast::{ node::Node, op::{self, AssignOp, BitOp, CompOp, LogOp, NumOp}, @@ -146,35 +145,7 @@ impl Executable for BinOp { let key = x.to_property_key(context)?; context.has_property(&y, &key)? } - CompOp::InstanceOf => { - // - // TODO: move to a separate instance_of_operator function - // 1. If Type(target) is not Object, throw a TypeError exception. - if !y.is_object() { - return context.throw_type_error(format!( - "right-hand side of 'instanceof' should be an object, got {}", - y.type_of() - )); - } - - // 2. Let instOfHandler be ? GetMethod(target, @@hasInstance). - let inst_of_handler = - y.get_method(WellKnownSymbols::has_instance(), context)?; - - // 3. If instOfHandler is not undefined, then - if !inst_of_handler.is_undefined() { - // a. Return ! ToBoolean(? Call(instOfHandler, target, « V »)). - context.call(&inst_of_handler, &y, &[x])?.to_boolean() - } else if !y.is_callable() { - // 4. If IsCallable(target) is false, throw a TypeError exception. - return Err(context.construct_type_error( - "right-hand side of 'instanceof' is not callable", - )); - } else { - // 5. Return ? OrdinaryHasInstance(target, V). - y.ordinary_has_instance(context, &x)? - } - } + CompOp::InstanceOf => x.instance_of(&y, context)?, })) } op::BinOp::Log(op) => Ok(match op { diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index 7b3dfe327a..7c78eba1c8 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -130,13 +130,40 @@ impl JsValue { } #[inline] - pub fn as_object(&self) -> Option { + pub fn as_object(&self) -> Option<&JsObject> { match *self { - Self::Object(ref o) => Some(o.clone()), + Self::Object(ref o) => Some(o), _ => None, } } + /// It determines if the value is a callable function with a `[[Call]]` internal method. + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-iscallable + #[inline] + pub fn is_callable(&self) -> bool { + matches!(self, Self::Object(obj) if obj.is_callable()) + } + + #[inline] + pub fn as_callable(&self) -> Option<&JsObject> { + self.as_object().filter(|obj| obj.is_callable()) + } + + /// Returns true if the value is a constructor object + #[inline] + pub fn is_constructor(&self) -> bool { + matches!(self, Self::Object(obj) if obj.is_constructor()) + } + + #[inline] + pub fn as_constructor(&self) -> Option<&JsObject> { + self.as_object().filter(|obj| obj.is_constructor()) + } + /// Returns true if the value is a symbol. #[inline] pub fn is_symbol(&self) -> bool { @@ -150,12 +177,6 @@ impl JsValue { } } - /// Returns true if the value is a function - #[inline] - pub fn is_function(&self) -> bool { - matches!(self, Self::Object(o) if o.is_function()) - } - /// Returns true if the value is undefined. #[inline] pub fn is_undefined(&self) -> bool { @@ -396,7 +417,7 @@ impl JsValue { let exotic_to_prim = self.get_method(WellKnownSymbols::to_primitive(), context)?; // b. If exoticToPrim is not undefined, then - if !exotic_to_prim.is_undefined() { + if let Some(exotic_to_prim) = exotic_to_prim { // i. If preferredType is not present, let hint be "default". // ii. Else if preferredType is string, let hint be "string". // iii. Else, @@ -410,7 +431,7 @@ impl JsValue { .into(); // iv. Let result be ? Call(exoticToPrim, input, « hint »). - let result = context.call(&exotic_to_prim, self, &[hint])?; + let result = exotic_to_prim.call(self, &[hint], context)?; // v. If Type(result) is not Object, return result. // vi. Throw a TypeError exception. return if result.is_object() { @@ -950,11 +971,13 @@ impl JsValue { #[inline] pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult { // 1. If Type(Obj) is not Object, throw a TypeError exception. - match self { - JsValue::Object(ref obj) => obj.to_property_descriptor(context), - _ => Err(context - .construct_type_error("Cannot construct a property descriptor from a non-object")), - } + self.as_object() + .ok_or_else(|| { + context.construct_type_error( + "Cannot construct a property descriptor from a non-object", + ) + }) + .and_then(|obj| obj.to_property_descriptor(context)) } /// Converts argument to an integer, +∞, or -∞. @@ -1029,42 +1052,10 @@ impl JsValue { // 3. If argument is a Proxy exotic object, then // b. Let target be argument.[[ProxyTarget]]. // c. Return ? IsArray(target). - // 4. Return false. + // TODO: handle ProxyObjects Ok(object.is_array()) } else { - Ok(false) - } - } - - /// It determines if the value is a callable function with a `[[Call]]` internal method. - /// - /// More information: - /// - [EcmaScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-iscallable - #[track_caller] - pub(crate) fn is_callable(&self) -> bool { - if let Self::Object(obj) = self { - obj.is_callable() - } else { - false - } - } - - /// Determines if `value` inherits from the instance object inheritance path. - /// - /// More information: - /// - [EcmaScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance - pub(crate) fn ordinary_has_instance( - &self, - context: &mut Context, - value: &JsValue, - ) -> JsResult { - if let Self::Object(obj) = self { - obj.ordinary_has_instance(context, value) - } else { + // 4. Return false. Ok(false) } } diff --git a/boa/src/value/operations.rs b/boa/src/value/operations.rs index 23f6dde28c..02f3bbfb56 100644 --- a/boa/src/value/operations.rs +++ b/boa/src/value/operations.rs @@ -388,6 +388,42 @@ impl JsValue { }) } + /// Abstract operation `InstanceofOperator ( V, target )` + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-instanceofoperator + #[inline] + pub fn instance_of(&self, target: &JsValue, context: &mut Context) -> JsResult { + // 1. If Type(target) is not Object, throw a TypeError exception. + if !target.is_object() { + return Err(context.construct_type_error(format!( + "right-hand side of 'instanceof' should be an object, got {}", + target.type_of() + ))); + } + + // 2. Let instOfHandler be ? GetMethod(target, @@hasInstance). + match target.get_method(WellKnownSymbols::has_instance(), context)? { + // 3. If instOfHandler is not undefined, then + Some(instance_of_handler) => { + // a. Return ! ToBoolean(? Call(instOfHandler, target, « V »)). + Ok(instance_of_handler + .call(target, std::slice::from_ref(self), context)? + .to_boolean()) + } + None if target.is_callable() => { + // 5. Return ? OrdinaryHasInstance(target, V). + JsValue::ordinary_has_instance(target, self, context) + } + None => { + // 4. If IsCallable(target) is false, throw a TypeError exception. + Err(context.construct_type_error("right-hand side of 'instanceof' is not callable")) + } + } + } + #[inline] pub fn neg(&self, context: &mut Context) -> JsResult { Ok(match *self { diff --git a/boa/src/vm/code_block.rs b/boa/src/vm/code_block.rs index a6f97cc89e..4fa45f962c 100644 --- a/boa/src/vm/code_block.rs +++ b/boa/src/vm/code_block.rs @@ -45,8 +45,8 @@ pub struct CodeBlock { /// Is this function in strict mode. pub(crate) strict: bool, - /// Is this function constructable. - pub(crate) constructable: bool, + /// Is this function a constructor. + pub(crate) constructor: bool, /// [[ThisMode]] pub(crate) this_mode: ThisMode, @@ -67,7 +67,7 @@ pub struct CodeBlock { } impl CodeBlock { - pub fn new(name: JsString, length: u32, strict: bool, constructable: bool) -> Self { + pub fn new(name: JsString, length: u32, strict: bool, constructor: bool) -> Self { Self { code: Vec::new(), literals: Vec::new(), @@ -76,7 +76,7 @@ impl CodeBlock { name, length, strict, - constructable, + constructor, this_mode: ThisMode::Global, params: Vec::new().into_boxed_slice(), } @@ -359,6 +359,7 @@ pub(crate) enum FunctionBody { }, } +// TODO: this should be modified to not take `exit_on_return` and then moved to `internal_methods` impl JsObject { pub(crate) fn call_internal( &self, @@ -483,8 +484,8 @@ impl JsObject { let this_function_object = self.clone(); // let mut has_parameter_expressions = false; - if !self.is_constructable() { - return context.throw_type_error("not a constructable function"); + if !self.is_constructor() { + return context.throw_type_error("not a constructor function"); } let body = { diff --git a/boa/src/vm/mod.rs b/boa/src/vm/mod.rs index 4e6864e275..80d5694642 100644 --- a/boa/src/vm/mod.rs +++ b/boa/src/vm/mod.rs @@ -3,8 +3,8 @@ //! plus an interpreter to execute those instructions use crate::{ - builtins::Array, environment::lexical_environment::VariableScope, symbol::WellKnownSymbols, - BoaProfiler, Context, JsResult, JsValue, + builtins::Array, environment::lexical_environment::VariableScope, BoaProfiler, Context, + JsResult, JsValue, }; mod call_frame; @@ -215,22 +215,8 @@ impl Context { Opcode::InstanceOf => { let target = self.vm.pop(); let v = self.vm.pop(); - if !target.is_object() { - return Err(self.construct_type_error(format!( - "right-hand side of 'instanceof' should be an object, got {}", - target.type_of() - ))); - }; - let handler = target.get_method(WellKnownSymbols::has_instance(), self)?; - if !handler.is_undefined() { - let value = self.call(&handler, &target, &[v.clone()])?.to_boolean(); - self.vm.push(value); - } - if !target.is_callable() { - return Err(self - .construct_type_error("right-hand side of 'instanceof' is not callable")); - } - let value = target.ordinary_has_instance(self, &v)?; + let value = v.instance_of(&target, self)?; + self.vm.push(value); } Opcode::Void => { @@ -359,7 +345,7 @@ impl Context { let value = self.vm.pop(); let object = if let Some(object) = value.as_object() { - object + object.clone() } else { value.to_object(self)? }; @@ -373,7 +359,7 @@ impl Context { let value = self.vm.pop(); let key = self.vm.pop(); let object = if let Some(object) = value.as_object() { - object + object.clone() } else { value.to_object(self)? }; @@ -389,7 +375,7 @@ impl Context { let object = self.vm.pop(); let value = self.vm.pop(); let object = if let Some(object) = object.as_object() { - object + object.clone() } else { object.to_object(self)? }; @@ -403,7 +389,7 @@ impl Context { let key = self.vm.pop(); let value = self.vm.pop(); let object = if let Some(object) = object.as_object() { - object + object.clone() } else { object.to_object(self)? }; @@ -546,7 +532,7 @@ impl Context { match self.vm.stack.last() { None => "".to_string(), Some(value) => { - if value.is_function() { + if value.is_callable() { "[function]".to_string() } else if value.is_object() { "[object]".to_string() @@ -591,7 +577,7 @@ impl Context { "{:04}{: