diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index 6583a2c84f1..02e63773509 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 50222de9c77..66639838387 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -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_constructor() { - 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_constructor() => 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,13 +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); - let callback = match callback { - JsValue::Object(obj) if obj.is_callable() => obj, - _ => { - 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)?; @@ -1300,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); @@ -1360,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); @@ -1472,11 +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); - let mapper_function = match mapper_function { - JsValue::Object(obj) if obj.is_callable() => obj, - _ => 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)?; @@ -1578,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, @@ -2003,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)?; @@ -2077,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, @@ -2268,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() { @@ -2366,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() { diff --git a/boa/src/builtins/array_buffer/mod.rs b/boa/src/builtins/array_buffer/mod.rs index 7422208180d..c4c3f158117 100644 --- a/boa/src/builtins/array_buffer/mod.rs +++ b/boa/src/builtins/array_buffer/mod.rs @@ -229,11 +229,11 @@ 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 + obj.clone() } else { return context.throw_type_error("ArrayBuffer constructor returned non-object value"); }; diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index 952b0216b02..42667255170 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -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 02c31e2db8a..84aa2fd4868 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 57360aae7af..77db02ccf31 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 a2757019eab..1ca7bba1e8d 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 987ffe13298..c7d5f5559b2 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -19,7 +19,6 @@ use std::{ use dyn_clone::DynClone; -use crate::symbol::WellKnownSymbols; use crate::value::IntegerOrInfinity; use crate::{ builtins::BuiltIn, @@ -33,6 +32,7 @@ use crate::{ syntax::ast::node::{FormalParameter, RcStatementList}, BoaProfiler, Context, JsResult, JsValue, }; +use crate::{object::Object, symbol::WellKnownSymbols}; use crate::{ object::{ConstructorBuilder, FunctionBuilder}, property::PropertyKey, @@ -124,19 +124,11 @@ 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 `Box` 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. /// @@ -144,14 +136,14 @@ dyn_clone::clone_trait_object!(CapturesObject); /// 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); +#[derive(Debug, Trace, Finalize)] +pub struct Captures(Box); impl Captures { /// Creates a new capture context. pub(crate) fn new(captures: T) -> Self where - T: NativeObject + Clone, + T: NativeObject, { Self(Box::new(captures)) } @@ -160,7 +152,7 @@ impl Captures { /// downcasted type if successful or `None` otherwise. pub fn downcast_ref(&self) -> Option<&T> where - T: NativeObject + Clone, + T: NativeObject, { self.0.deref().as_any().downcast_ref::() } @@ -169,7 +161,7 @@ impl Captures { /// mutable reference to the downcasted type if successful or `None` otherwise. pub fn downcast_mut(&mut self) -> Option<&mut T> where - T: NativeObject + Clone, + T: NativeObject, { self.0.deref_mut().as_mut_any().downcast_mut::() } @@ -178,7 +170,7 @@ impl Captures { /// downcasted type if successful or a `TypeError` otherwise. pub fn try_downcast_ref(&self, context: &mut Context) -> JsResult<&T> where - T: NativeObject + Clone, + T: NativeObject, { self.0 .deref() @@ -191,7 +183,7 @@ impl Captures { /// 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, + T: NativeObject, { self.0 .deref_mut() @@ -206,7 +198,7 @@ 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] @@ -389,11 +381,10 @@ impl BuiltInFunctionObject { /// [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 { // 1. Let func be the this value. - let func = match this { - JsValue::Object(obj) if obj.is_callable() => obj, - // 2. If IsCallable(func) is false, throw a TypeError exception. - _ => return context.throw_type_error(format!("{} is not a function", this.display())), - }; + // 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); @@ -430,14 +421,11 @@ impl BuiltInFunctionObject { /// [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. - let target = match this { - JsValue::Object(obj) if obj.is_callable() => obj, - // 2. If IsCallable(Target) is false, throw a TypeError exception. - _ => { - return context - .throw_type_error("cannot bind `this` without a `[[Call]]` internal method") - } - }; + // 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; @@ -514,12 +502,10 @@ impl BuiltInFunctionObject { /// [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. - let func = match this { - JsValue::Object(obj) if obj.is_callable() => obj, - // 2. If IsCallable(func) is false, throw a TypeError exception. - _ => return context.throw_type_error(format!("{} is not a function", this.display())), - }; - + // 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(). @@ -531,10 +517,19 @@ impl BuiltInFunctionObject { #[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 { @@ -542,19 +537,7 @@ 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: _, diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 496f1162d29..7e6124880b3 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 6d72ff17da5..340ff75a292 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 6cf0cd50568..4ae94eb8a15 100644 --- a/boa/src/builtins/map/mod.rs +++ b/boa/src/builtins/map/mod.rs @@ -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 a7a49b88723..5a63c6567b2 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 62892080512..ab1bebf5ce7 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 773ef74b596..f3b4c835d01 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 25518c6c4ff..f8c789e17ac 100644 --- a/boa/src/builtins/reflect/mod.rs +++ b/boa/src/builtins/reflect/mod.rs @@ -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 b7d1f80a554..b96db6a956e 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -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,44 +762,39 @@ 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 = object.get("exec", context)?; + let exec = this.get("exec", context)?; // 4. If IsCallable(exec) is true, then - match exec { - JsValue::Object(ref obj) if obj.is_callable() => { - // a. Let result be ? Call(exec, R, « S »). - let result = context.call(&exec, this, &[input.into()])?; - - // b. If Type(result) is neither Object nor Null, throw a TypeError exception. - if !result.is_object() && !result.is_null() { - return Err(context - .construct_type_error("regexp exec returned neither object nor null")); - } + if let Some(exec) = exec.as_callable() { + // a. Let result be ? Call(exec, R, « S »). + let result = exec.call(&this.clone().into(), &[input.into()], context)?; - // c. Return result. - return Ok(result.as_object()); + // b. If Type(result) is neither Object nor Null, throw a TypeError exception. + if !result.is_object() && !result.is_null() { + return Err( + context.construct_type_error("regexp exec returned neither object nor null") + ); } - _ => {} + + // c. Return result. + 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 )` @@ -821,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> { @@ -1082,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()) @@ -1105,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, @@ -1196,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( @@ -1224,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. @@ -1241,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, ) } @@ -1315,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, @@ -1535,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)?; @@ -1607,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(); @@ -1637,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() { @@ -1660,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 6754a58f5e3..fd8092234f4 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 5b9e7434ebe..c0882d773e6 100644 --- a/boa/src/builtins/set/mod.rs +++ b/boa/src/builtins/set/mod.rs @@ -142,10 +142,9 @@ impl Set { let adder = obj.get("add", context)?; // 6 - let adder = match adder { - JsValue::Object(ref obj) if obj.is_callable() => obj, - _ => 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)?; @@ -338,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)?; @@ -377,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( )` @@ -427,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 d7a0437e047..3720423143d 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 0dd4b757af7..aa9b8d70f86 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 )` @@ -871,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"). @@ -1667,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)?; @@ -1681,7 +1673,6 @@ 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 diff --git a/boa/src/builtins/string/string_iterator.rs b/boa/src/builtins/string/string_iterator.rs index 2adf8e80694..f6147bf11d0 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 4e57032ec07..cc459398110 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -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 36949cacd19..658e821d731 100644 --- a/boa/src/builtins/typed_array/mod.rs +++ b/boa/src/builtins/typed_array/mod.rs @@ -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, @@ -418,7 +422,7 @@ impl TypedArray { // 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, @@ -454,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, @@ -500,7 +504,7 @@ impl TypedArray { }; // 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, @@ -849,7 +853,7 @@ impl TypedArray { // 3. Return CreateArrayIterator(O, key+value). Ok(ArrayIterator::create_array_iterator( - o, + o.clone(), PropertyNameKind::KeyAndValue, context, )) @@ -1060,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 @@ -1476,7 +1480,7 @@ impl TypedArray { // 3. Return CreateArrayIterator(O, key). Ok(ArrayIterator::create_array_iterator( - o, + o.clone(), PropertyNameKind::Key, context, )) @@ -1619,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, @@ -1891,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)?; } } @@ -2290,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() @@ -2661,7 +2665,7 @@ impl TypedArray { } // 12. Return obj. - Ok(obj.into()) + Ok(obj.clone().into()) } /// `23.2.3.28 %TypedArray%.prototype.subarray ( begin, end )` @@ -2735,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(), @@ -2771,7 +2775,7 @@ impl TypedArray { // 3. Return CreateArrayIterator(O, value). Ok(ArrayIterator::create_array_iterator( - o, + o.clone(), PropertyNameKind::Value, context, )) @@ -2889,10 +2893,8 @@ impl TypedArray { } } - drop(obj_borrow); - // 4. Return newTypedArray. - Ok(obj) + Ok(obj.clone()) } /// @@ -3040,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. @@ -3072,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/class.rs b/boa/src/class.rs index bc7806eca1b..69acd51a561 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 b120e06ceb3..740d8c5a04a 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("not a function")) + .and_then(|obj| obj.call(this, args, self)) } /// Return the global object. diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 6439e4c9cf9..f09297ad726 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/function.rs b/boa/src/object/internal_methods/function.rs index 57ac3255d75..38901ef5def 100644 --- a/boa/src/object/internal_methods/function.rs +++ b/boa/src/object/internal_methods/function.rs @@ -1,14 +1,11 @@ use crate::{ - builtins::function::Function, + builtins::{function::Function, JsArgs}, 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, + object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, Context, JsResult, JsValue, }; @@ -86,68 +83,153 @@ pub(super) fn call_construct( 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 since we - /// already borrow it so we get the function body clone it then drop the borrow and run the body - enum FunctionBody { - Ordinary(RcStatementList), - } - + let name = obj.get("name", context)?.display().to_string(); let this_function_object = obj.clone(); let mut has_parameter_expressions = false; - let body = if let Some(function) = obj.borrow_mut().as_mut_function() { + // This is needed for the call method since we cannot mutate the function since we + // already borrow it so we get the function body clone it then drop the borrow and run the body + let body = { + let mut mut_obj = obj.borrow_mut(); + let function = mut_obj + .as_function_mut() + .ok_or_else(|| context.construct_type_error("not a 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 { - return function(this_target, args, context); + } + + match function { + Function::Native { + function, + constructor, + } => { + return if !*constructor || construct { + function(this_target, args, context) + } else { + function(&JsValue::Undefined, args, context) + }; + } + Function::Closure { + function, captures, .. + } => return (function)(this_target, args, captures, context), + 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 { - return function(&JsValue::Undefined, args, context); - } - } - Function::Closure { - function, captures, .. - } => return (function)(this_target, args, captures, context), - 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() + 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 { - this_target.clone() + 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_or_undefined(i).clone() { + JsValue::Undefined => param + .init() + .map(|init| init.run(context).ok()) + .flatten() + .unwrap_or_default(), + value => value, }; - // 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(), + 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.clone()) + Some(this) } else { None }, - Some(environment.clone()), + 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 @@ -157,141 +239,47 @@ pub(super) fn call_construct( 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") + context.push_environment(second_env); } + + body.clone() + } + #[cfg(feature = "vm")] + Function::VmOrdinary { .. } => { + todo!("vm call") } } - } else { - return context.throw_type_error("not a function"); }; - match body { - FunctionBody::Ordinary(body) => { - let result = body.run(context); - let this = context.get_this_binding(); + let result = body.run(context); + let this = context.get_this_binding(); - if has_parameter_expressions { - context.pop_environment(); - } - context.pop_environment(); + 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; - } - } + 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()) } } + + // 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 36e670d6cb0..e26c9ca2594 100644 --- a/boa/src/object/internal_methods/mod.rs +++ b/boa/src/object/internal_methods/mod.rs @@ -48,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 { @@ -498,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)) } } @@ -933,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 4d89d7aacea..4774edd1100 100644 --- a/boa/src/object/jsobject.rs +++ b/boa/src/object/jsobject.rs @@ -18,8 +18,6 @@ use std::{ result::Result as StdResult, }; - - /// A wrapper type for an immutably borrowed type T. pub type Ref<'a, T> = GcCellRef<'a, T>; @@ -168,17 +166,16 @@ impl JsObject { for name in &method_names { // a. Let method be ? Get(O, name). let method = self.get(*name, context)?; + // b. If IsCallable(method) is true, then - match method { - JsValue::Object(ref method) if method.is_callable() => { - // i. Let result be ? Call(method, O). - let result = method.call(&self.clone().into(), &[], context)?; - // ii. If Type(result) is not Object, return result. - if !result.is_object() { - return Ok(result); - } + if let Some(method) = method.as_callable() { + // i. Let result be ? Call(method, O). + let result = method.call(&self.clone().into(), &[], context)?; + + // ii. If Type(result) is not Object, return result. + if !result.is_object() { + return Ok(result); } - _ => {} } } @@ -534,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, diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 6bd68bcdb64..d75a08be14a 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -696,7 +696,7 @@ impl Object { } #[inline] - pub fn as_mut_function(&mut self) -> Option<&mut Function> { + pub fn as_function_mut(&mut self) -> Option<&mut Function> { match self.data { ObjectData { kind: ObjectKind::Function(ref mut function), diff --git a/boa/src/object/operations.rs b/boa/src/object/operations.rs index 7fbf462fc4f..921029cc127 100644 --- a/boa/src/object/operations.rs +++ b/boa/src/object/operations.rs @@ -444,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() { @@ -466,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_constructor() { - 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")), } } @@ -701,9 +696,10 @@ impl JsValue { context: &mut Context, ) -> JsResult { // 1. If IsCallable(C) is false, return false. - let function = match function { - JsValue::Object(obj) if obj.is_callable() => obj, - _ => return Ok(false), + let function = if let Some(function) = function.as_callable() { + function + } else { + return Ok(false); }; // 2. If C has a [[BoundTargetFunction]] internal slot, then @@ -718,7 +714,7 @@ impl JsValue { } let mut object = if let Some(obj) = object.as_object() { - obj + obj.clone() } else { // 3. If Type(O) is not Object, return false. return Ok(false); @@ -745,7 +741,7 @@ impl JsValue { }; // c. If SameValue(P, O) is true, return true. - if JsObject::equals(&object, &prototype) { + 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 d6050a0d153..2b926af1aa2 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 726de3b8058..2536f373af7 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) if object.is_constructor() => { - 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 b54a93acfd0..4d06bfb5aae 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/value/mod.rs b/boa/src/value/mod.rs index 654f063a69c..7c78eba1c81 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 { @@ -944,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 -∞. @@ -1023,27 +1052,13 @@ 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 { + // 4. Return false. 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 - } - } } impl Default for JsValue { diff --git a/boa/src/vm/code_block.rs b/boa/src/vm/code_block.rs index 0dcf5db8279..3514496d209 100644 --- a/boa/src/vm/code_block.rs +++ b/boa/src/vm/code_block.rs @@ -1,7 +1,5 @@ use crate::{ - builtins::function::{ - Captures, ClosureFunctionSignature, Function, NativeFunctionSignature, ThisMode, - }, + builtins::function::{Function, ThisMode}, context::StandardObjects, environment::{ function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, @@ -345,20 +343,6 @@ impl JsVmFunction { } } -pub(crate) enum FunctionBody { - Ordinary { - code: Gc, - environment: Environment, - }, - Native { - function: NativeFunctionSignature, - }, - Closure { - function: Box, - captures: Captures, - }, -} - impl JsObject { pub(crate) fn call_internal( &self, @@ -374,95 +358,77 @@ impl JsObject { return context.throw_type_error("not a callable function"); } - let body = { - let object = self.borrow(); - let function = object.as_function().unwrap(); + let (code, environment) = { + let mut object = self.borrow_mut(); + let function = object.as_function_mut().unwrap(); match function { - Function::Native { function, .. } => FunctionBody::Native { - function: *function, - }, + Function::Native { function, .. } => return function(this, args, context), Function::Closure { function, captures, .. - } => FunctionBody::Closure { - function: function.clone(), - captures: captures.clone(), - }, - Function::VmOrdinary { code, environment } => FunctionBody::Ordinary { - code: code.clone(), - environment: environment.clone(), - }, + } => return (function)(this, args, captures, context), + Function::VmOrdinary { code, environment } => (code.clone(), environment.clone()), Function::Ordinary { .. } => unreachable!(), } }; + let lexical_this_mode = code.this_mode == ThisMode::Lexical; + + // 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, + if !lexical_this_mode { + 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 lexical_this_mode { + BindingStatus::Lexical + } else { + BindingStatus::Uninitialized + }, + JsValue::undefined(), + context, + )?; - match body { - FunctionBody::Native { function } => function(this, args, context), - FunctionBody::Closure { - function, - mut captures, - } => (function)(this, args, &mut captures, context), - FunctionBody::Ordinary { code, environment } => { - let lexical_this_mode = code.this_mode == ThisMode::Lexical; - - // 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, - if !lexical_this_mode { - 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 lexical_this_mode { - BindingStatus::Lexical - } else { - BindingStatus::Uninitialized - }, - JsValue::undefined(), - context, - )?; - - // Turn local_env into Environment so it can be cloned - let local_env: Environment = local_env.into(); + // Turn local_env into Environment so it can be cloned + let local_env: Environment = local_env.into(); - // Push the environment first so that it will be used by default parameters - context.push_environment(local_env.clone()); + // 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 code.params.iter().enumerate() { - // Rest Parameters - if param.is_rest_param() { - todo!("Rest parameter"); - } + // Add argument bindings to the function environment + for (i, param) in code.params.iter().enumerate() { + // Rest Parameters + if param.is_rest_param() { + todo!("Rest parameter"); + } - let value = match args.get(i).cloned() { - None => JsValue::undefined(), - Some(value) => value, - }; + let value = match args.get(i).cloned() { + None => JsValue::undefined(), + Some(value) => value, + }; - Function::add_arguments_to_environment(param, value, &local_env, context); - } + Function::add_arguments_to_environment(param, value, &local_env, context); + } - context.vm.push_frame(CallFrame { - prev: None, - code, - this: this.clone(), - pc: 0, - fp: context.vm.stack.len(), - exit_on_return, - environment: local_env, - }); + context.vm.push_frame(CallFrame { + prev: None, + code, + this: this.clone(), + pc: 0, + fp: context.vm.stack.len(), + exit_on_return, + environment: local_env, + }); - let result = context.run(); + let result = context.run(); - context.pop_environment(); + context.pop_environment(); - result - } - } + result } pub fn call( @@ -488,103 +454,85 @@ impl JsObject { return context.throw_type_error("not a constructable function"); } - let body = { - let object = self.borrow(); - let function = object.as_function().unwrap(); + let (code, environment) = { + let mut object = self.borrow_mut(); + let function = object.as_function_mut().unwrap(); match function { - Function::Native { function, .. } => FunctionBody::Native { - function: *function, - }, + Function::Native { function, .. } => return function(this_target, args, context), Function::Closure { function, captures, .. - } => FunctionBody::Closure { - function: function.clone(), - captures: captures.clone(), - }, - Function::VmOrdinary { code, environment } => FunctionBody::Ordinary { - code: code.clone(), - environment: environment.clone(), - }, + } => return (function)(this_target, args, captures, context), + Function::VmOrdinary { code, environment } => (code.clone(), environment.clone()), Function::Ordinary { .. } => unreachable!(), } }; + let this: JsValue = { + // 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 prototype = get_prototype_from_constructor( + this_target, + StandardObjects::object_object, + context, + )?; + JsObject::from_proto_and_data(prototype, ObjectData::ordinary()).into() + }; + let lexical_this_mode = code.this_mode == ThisMode::Lexical; + + // 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, + Some(this.clone()), + Some(environment), + // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records + if lexical_this_mode { + BindingStatus::Lexical + } else { + BindingStatus::Uninitialized + }, + JsValue::undefined(), + context, + )?; - match body { - FunctionBody::Native { function, .. } => function(this_target, args, context), - FunctionBody::Closure { - function, - mut captures, - } => (function)(this_target, args, &mut captures, context), - FunctionBody::Ordinary { code, environment } => { - let this: JsValue = { - // 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 prototype = get_prototype_from_constructor( - this_target, - StandardObjects::object_object, - context, - )?; - JsObject::from_proto_and_data(prototype, ObjectData::ordinary()).into() - }; - let lexical_this_mode = code.this_mode == ThisMode::Lexical; - - // 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, - Some(this.clone()), - Some(environment), - // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records - if lexical_this_mode { - BindingStatus::Lexical - } else { - BindingStatus::Uninitialized - }, - JsValue::undefined(), - context, - )?; - - // Turn local_env into Environment so it can be cloned - let local_env: Environment = local_env.into(); + // Turn local_env into Environment so it can be cloned + let local_env: Environment = local_env.into(); - // Push the environment first so that it will be used by default parameters - context.push_environment(local_env.clone()); + // 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 code.params.iter().enumerate() { - // Rest Parameters - if param.is_rest_param() { - todo!("Rest parameter"); - } + // Add argument bindings to the function environment + for (i, param) in code.params.iter().enumerate() { + // Rest Parameters + if param.is_rest_param() { + todo!("Rest parameter"); + } - let value = match args.get(i).cloned() { - None => JsValue::undefined(), - Some(value) => value, - }; + let value = match args.get(i).cloned() { + None => JsValue::undefined(), + Some(value) => value, + }; - Function::add_arguments_to_environment(param, value, &local_env, context); - } + Function::add_arguments_to_environment(param, value, &local_env, context); + } - context.vm.push_frame(CallFrame { - prev: None, - code, - this, - pc: 0, - fp: context.vm.stack.len(), - exit_on_return, - environment: local_env, - }); + context.vm.push_frame(CallFrame { + prev: None, + code, + this, + pc: 0, + fp: context.vm.stack.len(), + exit_on_return, + environment: local_env, + }); - let _result = context.run(); + let _result = context.run(); - context.pop_environment(); + context.pop_environment(); - context.get_this_binding() - } - } + context.get_this_binding() } pub fn construct( diff --git a/boa/src/vm/mod.rs b/boa/src/vm/mod.rs index 4245b464813..80d56946423 100644 --- a/boa/src/vm/mod.rs +++ b/boa/src/vm/mod.rs @@ -345,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)? }; @@ -359,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)? }; @@ -375,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)? }; @@ -389,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)? };