diff --git a/Cargo.lock b/Cargo.lock index f63b5e72ad..9d4dfd29e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,6 +74,7 @@ dependencies = [ "boa_interner", "boa_profiler", "boa_unicode", + "cfg-if", "chrono", "criterion", "dyn-clone", @@ -91,6 +92,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", + "num_enum", "once_cell", "rand", "regress", @@ -98,6 +100,7 @@ dependencies = [ "ryu-js", "serde", "serde_json", + "sptr", "sys-locale", "tap", "unicode-normalization", @@ -992,6 +995,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "once_cell" version = "1.14.0" @@ -1136,6 +1160,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1459,6 +1493,12 @@ dependencies = [ "serde", ] +[[package]] +name = "sptr" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4365121b91e8522958da77ce29c004333daeaf0711225c4727c8c5f49941" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1618,6 +1658,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + [[package]] name = "unicode-general-category" version = "0.5.1" diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index cd01b62990..fcb485267f 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -93,7 +93,7 @@ struct Opt { #[clap(long, short = 'a', value_name = "FORMAT", ignore_case = true, arg_enum)] dump_ast: Option>, - /// Dump the AST to stdout with the given format. + /// Dump the compiled bytecode and trace the execution stack #[clap(long = "trace", short = 't')] trace: bool, diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index 3b4daab8ed..8de4200122 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -23,6 +23,7 @@ intl = [ "dep:icu_testdata", "dep:sys-locale" ] +nan_boxing = ["dep:sptr", "dep:num_enum"] # Enable Boa's WHATWG console object implementation. console = [] @@ -50,6 +51,9 @@ unicode-normalization = "0.1.21" dyn-clone = "1.0.9" once_cell = "1.14.0" tap = "1.0.1" +cfg-if = "1.0.0" +sptr = { version = "0.3.1", optional = true } +num_enum = { version = "0.5.7", optional = true } icu_locale_canonicalizer = { version = "0.6.0", features = ["serde"], optional = true } icu_locid = { version = "0.6.0", features = ["serde"], optional = true } icu_datetime = { version = "0.6.0", features = ["serde"], optional = true } diff --git a/boa_engine/src/bigint.rs b/boa_engine/src/bigint.rs index b06b921ea3..02536a6b1c 100644 --- a/boa_engine/src/bigint.rs +++ b/boa_engine/src/bigint.rs @@ -1,10 +1,11 @@ //! This module implements the JavaScript bigint primitive rust type. -use crate::{builtins::Number, Context, JsValue}; +use crate::{builtins::Number, value::PointerType, Context, JsValue}; use num_integer::Integer; use num_traits::{pow::Pow, FromPrimitive, One, ToPrimitive, Zero}; use std::{ fmt::{self, Display}, + mem::{self, ManuallyDrop}, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, rc::Rc, }; @@ -22,6 +23,16 @@ pub struct JsBigInt { inner: Rc, } +unsafe impl PointerType for JsBigInt { + unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop { + ManuallyDrop::new(mem::transmute(ptr)) + } + + unsafe fn into_void_ptr(bigint: ManuallyDrop) -> *mut () { + mem::transmute(bigint) + } +} + impl JsBigInt { /// Create a new [`JsBigInt`]. #[inline] diff --git a/boa_engine/src/builtins/array/array_iterator.rs b/boa_engine/src/builtins/array/array_iterator.rs index 882a47c425..d1c6fefae7 100644 --- a/boa_engine/src/builtins/array/array_iterator.rs +++ b/boa_engine/src/builtins/array/array_iterator.rs @@ -68,7 +68,8 @@ impl ArrayIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut array_iterator = this.as_object().map(JsObject::borrow_mut); + let array_iterator = this.as_object(); + let mut array_iterator = array_iterator.as_deref().map(JsObject::borrow_mut); let array_iterator = array_iterator .as_mut() .and_then(|obj| obj.as_array_iterator_mut()) diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 54f2280029..4887f3e2dd 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -29,7 +29,7 @@ use crate::{ }, property::{Attribute, PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, - value::{IntegerOrInfinity, JsValue}, + value::{IntegerOrInfinity, JsValue, JsVariant}, Context, JsResult, JsString, }; use std::cmp::{max, min, Ordering}; @@ -374,7 +374,7 @@ impl Array { // 7. If IsConstructor(C) is false, throw a TypeError exception. if let Some(c) = c.as_constructor() { // 8. Return ? Construct(C, « 𝔽(length) »). - c.construct(&[JsValue::new(length)], Some(c), context) + c.construct(&[JsValue::new(length)], Some(&c), context) } else { context.throw_type_error("Symbol.species must be a constructor") } @@ -404,9 +404,9 @@ impl Array { // 3. Else, // a. If IsCallable(mapfn) is false, throw a TypeError exception. // b. Let mapping be true. - let mapping = match mapfn { - JsValue::Undefined => None, - JsValue::Object(o) if o.is_callable() => Some(o), + let mapping = match mapfn.variant() { + JsVariant::Undefined => None, + JsVariant::Object(o) if o.is_callable() => Some(o), _ => return context.throw_type_error(format!("{} is not a function", mapfn.type_of())), }; @@ -423,7 +423,8 @@ impl Array { // b. Else, // i. Let A be ? ArrayCreate(0en). let a = match this.as_constructor() { - Some(constructor) => constructor.construct(&[], None, context)?, + Some(constructor) => constructor + .construct(&[], None, context)?, _ => Self::array_create(0, None, context)?, }; @@ -455,9 +456,9 @@ impl Array { let next_value = next.value(context)?; // vi. If mapping is true, then - let mapped_value = if let Some(mapfn) = mapping { + let mapped_value = if let Some(ref mapfn) = mapping { // 1. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »). - let mapped_value = mapfn.call(this_arg, &[next_value, k.into()], context); + let mapped_value = mapfn.call(&this_arg, &[next_value, k.into()], context); // 2. IfAbruptCloseIterator(mappedValue, iteratorRecord). if_abrupt_close_iterator!(mapped_value, iterator_record, context) @@ -496,7 +497,8 @@ impl Array { // 10. Else, // a. Let A be ? ArrayCreate(len). let a = match this.as_constructor() { - Some(constructor) => constructor.construct(&[len.into()], None, context)?, + Some(constructor) => constructor + .construct(&[len.into()], None, context)?, _ => Self::array_create(len, None, context)?, }; @@ -509,10 +511,10 @@ impl Array { // b. Let kValue be ? Get(arrayLike, Pk). let k_value = array_like.get(k, context)?; - let mapped_value = if let Some(mapfn) = mapping { + let mapped_value = if let Some(ref mapfn) = mapping { // c. If mapping is true, then // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). - mapfn.call(this_arg, &[k_value, k.into()], context)? + mapfn.call(&this_arg, &[k_value, k.into()], context)? } else { // d. Else, let mappedValue be kValue. k_value @@ -572,7 +574,8 @@ impl Array { // 5. Else, // a. Let A be ? ArrayCreate(len). let a = match this.as_constructor() { - Some(constructor) => constructor.construct(&[len.into()], None, context)?, + Some(constructor) => constructor + .construct(&[len.into()], None, context)?, _ => Self::array_create(len as u64, None, context)?, }; @@ -810,7 +813,8 @@ 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).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.forEach: invalid callback function") })?; // 4. Let k be 0. @@ -826,7 +830,7 @@ impl Array { let k_value = o.get(pk, context)?; // ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). let this_arg = args.get_or_undefined(1); - callback.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?; + callback.call(&this_arg, &[k_value, k.into(), o.clone().into()], context)?; } // d. Set k to k + 1. } @@ -1151,7 +1155,8 @@ 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).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.every: callback is not callable") })?; @@ -1169,7 +1174,7 @@ impl Array { let k_value = o.get(k, context)?; // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). let test_result = callback - .call(this_arg, &[k_value, k.into(), o.clone().into()], context)? + .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? .to_boolean(); // iii. If testResult is false, return false. if !test_result { @@ -1203,7 +1208,8 @@ 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).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.map: Callbackfn is not callable") })?; @@ -1224,7 +1230,7 @@ impl Array { let k_value = o.get(k, context)?; // ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). let mapped_value = - callback.call(this_arg, &[k_value, k.into(), this.into()], context)?; + callback.call(&this_arg, &[k_value, k.into(), this.into()], context)?; // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). a.create_data_property_or_throw(k, mapped_value, context)?; } @@ -1376,7 +1382,7 @@ impl Array { let element_k = o.get(k, context)?; // ii. Let same be IsStrictlyEqual(searchElement, elementK). // iii. If same is true, return 𝔽(k). - if JsValue::strict_equals(search_element, &element_k) { + if JsValue::strict_equals(&search_element, &element_k) { return Ok(JsValue::new(k)); } } @@ -1411,7 +1417,8 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let predicate = args.get_or_undefined(0); + let predicate = predicate.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.find: predicate is not callable") })?; @@ -1428,7 +1435,7 @@ impl Array { // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). let test_result = predicate .call( - this_arg, + &this_arg, &[k_value.clone(), k.into(), o.clone().into()], context, )? @@ -1468,7 +1475,8 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let predicate = args.get_or_undefined(0); + let predicate = predicate.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.findIndex: predicate is not callable") })?; @@ -1484,7 +1492,7 @@ impl Array { let k_value = o.get(pk, context)?; // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). let test_result = predicate - .call(this_arg, &[k_value, k.into(), o.clone().into()], context)? + .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? .to_boolean(); // d. If testResult is true, return 𝔽(k). if test_result { @@ -1519,7 +1527,8 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let predicate = args.get_or_undefined(0); + let predicate = predicate.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.findLast: predicate is not callable") })?; @@ -1534,7 +1543,7 @@ impl Array { // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). let test_result = predicate .call( - this_arg, + &this_arg, &[k_value.clone(), k.into(), this.clone()], context, )? @@ -1571,7 +1580,8 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let predicate = args.get_or_undefined(0); + let predicate = predicate.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.findLastIndex: predicate is not callable") })?; @@ -1585,7 +1595,7 @@ impl Array { let k_value = o.get(k, context)?; // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). let test_result = predicate - .call(this_arg, &[k_value, k.into(), this.clone()], context)? + .call(&this_arg, &[k_value, k.into(), this.clone()], context)? .to_boolean(); // d. If testResult is true, return 𝔽(k). if test_result { @@ -1676,7 +1686,8 @@ 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).as_callable().ok_or_else(|| { + let mapper_function = args.get_or_undefined(0); + let mapper_function = mapper_function.as_callable().ok_or_else(|| { context.construct_type_error("flatMap mapper function is not callable") })?; @@ -1690,8 +1701,8 @@ impl Array { source_len, 0, 1, - Some(mapper_function), - args.get_or_undefined(1), + Some(&mapper_function), + &args.get_or_undefined(1), context, )?; @@ -1780,7 +1791,7 @@ impl Array { // 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth) target_index = Self::flatten_into_array( target, - element, + &element, element_len, target_index, new_depth, @@ -1924,7 +1935,7 @@ impl Array { // a. Let elementK be ? Get(O, ! ToString(𝔽(k))). let element_k = o.get(k, context)?; // b. If SameValueZero(searchElement, elementK) is true, return true. - if JsValue::same_value_zero(search_element, &element_k) { + if JsValue::same_value_zero(&search_element, &element_k) { return Ok(JsValue::new(true)); } // c. Set k to k + 1. @@ -2205,7 +2216,8 @@ impl Array { let length = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.filter: `callback` must be callable") })?; let this_arg = args.get_or_undefined(1); @@ -2228,7 +2240,7 @@ impl Array { let args = [element.clone(), JsValue::new(idx), JsValue::new(o.clone())]; // ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). - let selected = callback.call(this_arg, &args, context)?.to_boolean(); + let selected = callback.call(&this_arg, &args, context)?.to_boolean(); // iii. If selected is true, then if selected { @@ -2269,7 +2281,8 @@ 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).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.some: callback is not callable") })?; @@ -2286,7 +2299,7 @@ impl Array { // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). let this_arg = args.get_or_undefined(1); let test_result = callback - .call(this_arg, &[k_value, k.into(), o.clone().into()], context)? + .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? .to_boolean(); // iii. If testResult is true, return true. if test_result { @@ -2317,9 +2330,10 @@ impl Array { context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - let comparefn = match args.get_or_undefined(0) { - JsValue::Object(ref obj) if obj.is_callable() => Some(obj), - JsValue::Undefined => None, + let comparefn = args.get_or_undefined(0); + let comparefn = match comparefn.variant() { + JsVariant::Object(obj) if obj.is_callable() => Some(obj), + JsVariant::Undefined => None, _ => { return context.throw_type_error( "The comparison function must be either a function or undefined", @@ -2346,11 +2360,11 @@ impl Array { } // 4. If comparefn is not undefined, then - if let Some(cmp) = comparefn { + if let Some(ref cmp) = comparefn { let args = [x.clone(), y.clone()]; // a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)). let v = cmp - .call(&JsValue::Undefined, &args, context)? + .call(&JsValue::undefined(), &args, context)? .to_number(context)?; // b. If v is NaN, return +0𝔽. // c. Return v. @@ -2454,7 +2468,8 @@ impl Array { 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).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context .construct_type_error("Array.prototype.reduce: callback function is not callable") })?; @@ -2549,7 +2564,8 @@ impl Array { 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).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context.construct_type_error( "Array.prototype.reduceRight: callback function is not callable", ) diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 576bd53b2c..0d776367a0 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -262,7 +262,7 @@ impl ArrayBuffer { // 20. If SameValue(new, O) is true, throw a TypeError exception. if this .as_object() - .map(|obj| JsObject::equals(obj, &new)) + .map(|obj| JsObject::equals(&obj, &new)) .unwrap_or_default() { return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer"); @@ -334,7 +334,7 @@ impl ArrayBuffer { obj.borrow_mut().data = ObjectData::array_buffer(Self { array_buffer_data: Some(block), array_buffer_byte_length: byte_length, - array_buffer_detach_key: JsValue::Undefined, + array_buffer_detach_key: JsValue::undefined(), }); // 5. Return obj. diff --git a/boa_engine/src/builtins/async_generator/mod.rs b/boa_engine/src/builtins/async_generator/mod.rs index 021141007a..75a9aeb666 100644 --- a/boa_engine/src/builtins/async_generator/mod.rs +++ b/boa_engine/src/builtins/async_generator/mod.rs @@ -203,7 +203,7 @@ impl AsyncGenerator { } // 7. Let completion be NormalCompletion(value). - let completion = (Ok(args.get_or_undefined(0).clone()), false); + let completion = (Ok(args.get_or_undefined(0)), false); // 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). generator.enqueue(completion.clone(), promise_capability.clone()); @@ -221,7 +221,7 @@ impl AsyncGenerator { drop(generator_obj_mut); Self::resume( - generator_object, + &generator_object, state, &generator_context, completion, @@ -272,7 +272,7 @@ impl AsyncGenerator { if_abrupt_reject_promise!(generator, promise_capability, context); // 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. - let completion = (Ok(args.get_or_undefined(0).clone()), true); + let completion = (Ok(args.get_or_undefined(0)), true); // 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). generator.enqueue(completion.clone(), promise_capability.clone()); @@ -305,7 +305,7 @@ impl AsyncGenerator { drop(generator_obj_mut); Self::resume( - generator_object, + &generator_object, state, &generator_context, completion, @@ -376,7 +376,7 @@ impl AsyncGenerator { .reject() .call( &JsValue::undefined(), - &[args.get_or_undefined(0).clone()], + &[args.get_or_undefined(0)], context, ) .expect("cannot fail per spec"); @@ -386,7 +386,7 @@ impl AsyncGenerator { } // 8. Let completion be ThrowCompletion(exception). - let completion = (Err(args.get_or_undefined(0).clone()), false); + let completion = (Err(args.get_or_undefined(0)), false); // 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). generator.enqueue(completion.clone(), promise_capability.clone()); @@ -401,7 +401,7 @@ impl AsyncGenerator { // a. Perform AsyncGeneratorResume(generator, completion). Self::resume( - generator_object, + &generator_object, state, &generator_context, completion, @@ -622,7 +622,7 @@ impl AsyncGenerator { gen.state = AsyncGeneratorState::Completed; // b. Let result be NormalCompletion(value). - let result = Ok(args.get_or_undefined(0).clone()); + let result = Ok(args.get_or_undefined(0)); // c. Perform AsyncGeneratorCompleteStep(generator, result, true). let next = gen.queue.pop_front().expect("must have one entry"); @@ -655,7 +655,7 @@ impl AsyncGenerator { gen.state = AsyncGeneratorState::Completed; // b. Let result be ThrowCompletion(reason). - let result = Err(args.get_or_undefined(0).clone()); + let result = Err(args.get_or_undefined(0)); // c. Perform AsyncGeneratorCompleteStep(generator, result, true). let next = gen.queue.pop_front().expect("must have one entry"); diff --git a/boa_engine/src/builtins/bigint/mod.rs b/boa_engine/src/builtins/bigint/mod.rs index dc1ddd3485..9225dd5325 100644 --- a/boa_engine/src/builtins/bigint/mod.rs +++ b/boa_engine/src/builtins/bigint/mod.rs @@ -133,6 +133,7 @@ impl BigInt { value // 1. If Type(value) is BigInt, return value. .as_bigint() + .as_deref() .cloned() // 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then // a. Assert: Type(value.[[BigIntData]]) is BigInt. diff --git a/boa_engine/src/builtins/boolean/tests.rs b/boa_engine/src/builtins/boolean/tests.rs index 3d6ae4a3de..cc0e18ba0e 100644 --- a/boa_engine/src/builtins/boolean/tests.rs +++ b/boa_engine/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().cloned() + &bool_prototype.as_object().as_deref().cloned() ); } diff --git a/boa_engine/src/builtins/console/mod.rs b/boa_engine/src/builtins/console/mod.rs index dd2687c474..f73d407a2b 100644 --- a/boa_engine/src/builtins/console/mod.rs +++ b/boa_engine/src/builtins/console/mod.rs @@ -569,7 +569,7 @@ impl Console { #[allow(clippy::unnecessary_wraps)] pub(crate) fn dir(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { logger( - LogMessage::Info(display_obj(args.get_or_undefined(0), true)), + LogMessage::Info(display_obj(&args.get_or_undefined(0), true)), context.console(), ); Ok(JsValue::undefined()) diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index 0128d9c4bb..5b3f4d99c2 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -100,8 +100,8 @@ impl DataView { ) -> JsResult { let byte_length = args.get_or_undefined(2); - let buffer_obj = args - .get_or_undefined(0) + let buffer_obj = args.get_or_undefined(0); + let buffer_obj = buffer_obj .as_object() .ok_or_else(|| context.construct_type_error("buffer must be an ArrayBuffer"))?; @@ -194,7 +194,8 @@ impl DataView { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). - let dataview = this.as_object().map(JsObject::borrow); + let dataview = this.as_object(); + let dataview = dataview.as_deref().map(JsObject::borrow); let dataview = dataview .as_ref() .and_then(|obj| obj.as_data_view()) @@ -223,7 +224,8 @@ impl DataView { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). - let dataview = this.as_object().map(JsObject::borrow); + let dataview = this.as_object(); + let dataview = dataview.as_deref().map(JsObject::borrow); let dataview = dataview .as_ref() .and_then(|obj| obj.as_data_view()) @@ -262,7 +264,8 @@ impl DataView { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). - let dataview = this.as_object().map(JsObject::borrow); + let dataview = this.as_object(); + let dataview = dataview.as_deref().map(JsObject::borrow); let dataview = dataview .as_ref() .and_then(|obj| obj.as_data_view()) @@ -302,7 +305,8 @@ impl DataView { ) -> JsResult { // 1. Perform ? RequireInternalSlot(view, [[DataView]]). // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. - let view = view.as_object().map(JsObject::borrow); + let view = view.as_object(); + let view = view.as_deref().map(JsObject::borrow); let view = view .as_ref() .and_then(|obj| obj.as_data_view()) @@ -373,8 +377,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::BigInt64, context, ) @@ -402,8 +406,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::BigUint64, context, ) @@ -431,8 +435,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Float32, context, ) @@ -460,8 +464,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Float64, context, ) @@ -489,8 +493,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Int8, context, ) @@ -518,8 +522,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Int16, context, ) @@ -547,8 +551,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Int32, context, ) @@ -576,8 +580,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Uint8, context, ) @@ -605,8 +609,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Uint16, context, ) @@ -634,8 +638,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Uint32, context, ) @@ -661,7 +665,8 @@ impl DataView { ) -> JsResult { // 1. Perform ? RequireInternalSlot(view, [[DataView]]). // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. - let view = view.as_object().map(JsObject::borrow); + let view = view.as_object(); + let view = view.as_deref().map(JsObject::borrow); let view = view .as_ref() .and_then(|obj| obj.as_data_view()) @@ -742,10 +747,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::BigInt64, - value, + &value, context, ) } @@ -773,10 +778,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::BigUint64, - value, + &value, context, ) } @@ -804,10 +809,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float32, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Float32, - value, + &value, context, ) } @@ -835,10 +840,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float64, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Float64, - value, + &value, context, ) } @@ -866,10 +871,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int8, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Int8, - value, + &value, context, ) } @@ -897,10 +902,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int16, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Int16, - value, + &value, context, ) } @@ -928,10 +933,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int32, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Int32, - value, + &value, context, ) } @@ -959,10 +964,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint8, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Uint8, - value, + &value, context, ) } @@ -990,10 +995,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint16, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Uint16, - value, + &value, context, ) } @@ -1021,10 +1026,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint32, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Uint32, - value, + &value, context, ) } diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index 95fc226d9e..f46c28cf4a 100644 --- a/boa_engine/src/builtins/date/mod.rs +++ b/boa_engine/src/builtins/date/mod.rs @@ -9,7 +9,7 @@ use crate::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, }, symbol::WellKnownSymbols, - value::{JsValue, PreferredType}, + value::{JsValue, JsVariant, PreferredType}, Context, JsResult, JsString, }; use boa_profiler::Profiler; @@ -390,24 +390,25 @@ impl Date { context: &mut Context, ) -> JsResult { let value = &args[0]; - let tv = match this_time_value(value, context) { - Ok(dt) => dt.0, - _ => match value.to_primitive(context, PreferredType::Default)? { - JsValue::String(ref str) => match chrono::DateTime::parse_from_rfc3339(str) { + let tv = if let Ok(dt) = this_time_value(value, context) { + dt.0 + } else { + let tv = value.to_primitive(context, PreferredType::Default)?; + if let JsVariant::String(ref str) = tv.variant() { + match chrono::DateTime::parse_from_rfc3339(str) { Ok(dt) => Some(dt.naive_utc()), _ => None, - }, - tv => { - let tv = tv.to_number(context)?; - if tv.is_nan() { - None - } else { - let secs = (tv / 1_000f64) as i64; - let nano_secs = ((tv % 1_000f64) * 1_000_000f64) as u32; - NaiveDateTime::from_timestamp_opt(secs, nano_secs) - } } - }, + } else { + let tv = tv.to_number(context)?; + if tv.is_nan() { + None + } else { + let secs = (tv / 1_000f64) as i64; + let nano_secs = ((tv % 1_000f64) * 1_000_000f64) as u32; + NaiveDateTime::from_timestamp_opt(secs, nano_secs) + } + } }; let tv = tv.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some()); @@ -512,7 +513,7 @@ impl Date { let hint = args.get_or_undefined(0); - let try_first = match hint.as_string().map(JsString::as_str) { + let try_first = match hint.as_string().as_deref().map(JsString::as_str) { // 3. If hint is "string" or "default", then // a. Let tryFirst be string. Some("string" | "default") => PreferredType::String, @@ -1848,7 +1849,7 @@ impl Date { match DateTime::parse_from_rfc3339(&args[0].to_string(context)?) { Ok(v) => Ok(JsValue::new(v.naive_utc().timestamp_millis() as f64)), - _ => Ok(JsValue::new(f64::NAN)), + _ => Ok(JsValue::nan()), } } diff --git a/boa_engine/src/builtins/date/tests.rs b/boa_engine/src/builtins/date/tests.rs index 057fd576fd..7918cae085 100644 --- a/boa_engine/src/builtins/date/tests.rs +++ b/boa_engine/src/builtins/date/tests.rs @@ -13,7 +13,7 @@ fn forward_dt_utc(context: &mut Context, src: &str) -> Option { panic!("expected success") }; - if let JsValue::Object(ref date_time) = date_time { + if let Some(date_time) = date_time.as_object() { if let Some(date_time) = date_time.borrow().as_date() { date_time.0 } else { diff --git a/boa_engine/src/builtins/error/aggregate.rs b/boa_engine/src/builtins/error/aggregate.rs index 5c384177c1..42ccb98c94 100644 --- a/boa_engine/src/builtins/error/aggregate.rs +++ b/boa_engine/src/builtins/error/aggregate.rs @@ -86,11 +86,11 @@ impl AggregateError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(2), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(2), context)?; // 5. Let errorsList be ? IterableToList(errors). let errors = args.get_or_undefined(0); - let errors_list = iterable_to_list(context, errors, None)?; + let errors_list = iterable_to_list(context, &errors, None)?; // 6. Perform ! DefinePropertyOrThrow(O, "errors", // PropertyDescriptor { // [[Configurable]]: true, diff --git a/boa_engine/src/builtins/error/eval.rs b/boa_engine/src/builtins/error/eval.rs index 563749207c..c99a0e70ff 100644 --- a/boa_engine/src/builtins/error/eval.rs +++ b/boa_engine/src/builtins/error/eval.rs @@ -83,7 +83,7 @@ impl EvalError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/error/mod.rs b/boa_engine/src/builtins/error/mod.rs index c68d185444..30312aa9a5 100644 --- a/boa_engine/src/builtins/error/mod.rs +++ b/boa_engine/src/builtins/error/mod.rs @@ -120,7 +120,7 @@ impl Error { } // 4. Perform ? InstallErrorCause(O, options). - Self::install_error_cause(&o, args.get_or_undefined(1), context)?; + Self::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/error/range.rs b/boa_engine/src/builtins/error/range.rs index 1fa1258b0a..675fffe34d 100644 --- a/boa_engine/src/builtins/error/range.rs +++ b/boa_engine/src/builtins/error/range.rs @@ -81,7 +81,7 @@ impl RangeError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/error/reference.rs b/boa_engine/src/builtins/error/reference.rs index e130678b95..8353d23cb6 100644 --- a/boa_engine/src/builtins/error/reference.rs +++ b/boa_engine/src/builtins/error/reference.rs @@ -87,7 +87,7 @@ impl ReferenceError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/error/syntax.rs b/boa_engine/src/builtins/error/syntax.rs index c7dfb012d3..c017f1654b 100644 --- a/boa_engine/src/builtins/error/syntax.rs +++ b/boa_engine/src/builtins/error/syntax.rs @@ -86,7 +86,7 @@ impl SyntaxError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 1f39077784..604b5c75ec 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -87,7 +87,7 @@ impl TypeError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/error/uri.rs b/boa_engine/src/builtins/error/uri.rs index 21469f338d..04fa7a5ffd 100644 --- a/boa_engine/src/builtins/error/uri.rs +++ b/boa_engine/src/builtins/error/uri.rs @@ -82,7 +82,7 @@ impl UriError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index 71b82951fa..8938f2ecf2 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -50,7 +50,7 @@ impl Eval { /// [spec]: https://tc39.es/ecma262/#sec-eval-x fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result { // 1. Return ? PerformEval(x, false, false). - Self::perform_eval(args.get_or_undefined(0), false, false, context) + Self::perform_eval(&args.get_or_undefined(0), false, false, context) } /// `19.2.1.1 PerformEval ( x, strictCaller, direct )` diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index ab1626a87c..a8a8c2e3e0 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -196,7 +196,6 @@ impl Arguments { // In the case of duplicate parameter names, the last one is bound as the environment binding. // // The following logic implements the steps 17-19 adjusted for our environment structure. - let mut bindings = FxHashMap::default(); let mut property_index = 0; 'outer: for formal in formals.parameters.iter() { diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index d5c23ea1bc..a93ec0b4ea 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -682,7 +682,7 @@ impl BuiltInFunctionObject { // TODO?: 3.a. PrepareForTailCall // b. Return ? Call(func, thisArg). - return func.call(this_arg, &[], context); + return func.call(&this_arg, &[], context); } // 4. Let argList be ? CreateListFromArrayLike(argArray). @@ -692,7 +692,7 @@ impl BuiltInFunctionObject { // TODO?: 5. PrepareForTailCall // 6. Return ? Call(func, thisArg, argList). - func.call(this_arg, &arg_list, context) + func.call(&this_arg, &arg_list, context) } /// `Function.prototype.bind ( thisArg, ...args )` @@ -714,7 +714,7 @@ impl BuiltInFunctionObject { context.construct_type_error("cannot bind `this` without a `[[Call]]` internal method") })?; - let this_arg = args.get_or_undefined(0).clone(); + let this_arg = args.get_or_undefined(0); let bound_args = args.get(1..).unwrap_or(&[]).to_vec(); let arg_count = bound_args.len() as i64; @@ -769,7 +769,9 @@ impl BuiltInFunctionObject { // 9. If Type(targetName) is not String, set targetName to the empty String. let target_name = target_name .as_string() - .map_or(JsString::new(""), Clone::clone); + .as_deref() + .cloned() + .unwrap_or_default(); // 10. Perform SetFunctionName(F, targetName, "bound"). set_function_name(&f, &target_name.into(), Some("bound"), context); @@ -800,12 +802,13 @@ impl BuiltInFunctionObject { // TODO?: 3. Perform PrepareForTailCall // 4. Return ? Call(func, thisArg, args). - func.call(this_arg, args.get(1..).unwrap_or(&[]), context) + func.call(&this_arg, args.get(1..).unwrap_or(&[]), context) } #[allow(clippy::wrong_self_convention)] fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let object = this.as_object().map(JsObject::borrow); + let object = this.as_object(); + let object = object.as_deref().map(JsObject::borrow); let function = object .as_deref() .and_then(Object::as_function) @@ -855,7 +858,7 @@ impl BuiltInFunctionObject { fn has_instance(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let F be the this value. // 2. Return ? OrdinaryHasInstance(F, V). - Ok(JsValue::ordinary_has_instance(this, args.get_or_undefined(0), context)?.into()) + Ok(JsValue::ordinary_has_instance(this, &args.get_or_undefined(0), context)?.into()) } #[allow(clippy::unnecessary_wraps)] diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index f183343973..eaa9d6cc5e 100644 --- a/boa_engine/src/builtins/function/tests.rs +++ b/boa_engine/src/builtins/function/tests.rs @@ -129,7 +129,7 @@ fn function_prototype_call() { "#; let value = forward_val(&mut context, func).unwrap(); assert!(value.is_string()); - assert_eq!(value.as_string().unwrap(), "[object Error]"); + assert_eq!(*value.as_string().unwrap(), "[object Error]"); } #[test] @@ -246,7 +246,7 @@ fn closure_capture_clone() { object .__get_own_property__(&"key".into(), context)? .and_then(|prop| prop.value().cloned()) - .and_then(|val| val.as_string().cloned()) + .and_then(|val| val.as_string().as_deref().cloned()) .ok_or_else(|| context.construct_type_error("invalid `key` property"))?, ); Ok(hw.into()) diff --git a/boa_engine/src/builtins/generator/mod.rs b/boa_engine/src/builtins/generator/mod.rs index c446078500..0fbf0a1523 100644 --- a/boa_engine/src/builtins/generator/mod.rs +++ b/boa_engine/src/builtins/generator/mod.rs @@ -148,8 +148,8 @@ impl Generator { ) -> JsResult { // 1. Return ? GeneratorResume(this value, value, empty). match this.as_object() { - Some(obj) if obj.is_generator() => { - Self::generator_resume(obj, args.get_or_undefined(0), context) + Some(ref obj) if obj.is_generator() => { + Self::generator_resume(obj, &args.get_or_undefined(0), context) } _ => context.throw_type_error("Generator.prototype.next called on non generator"), } @@ -173,7 +173,7 @@ impl Generator { // 1. Let g be the this value. // 2. Let C be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. // 3. Return ? GeneratorResumeAbrupt(g, C, empty). - Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0).clone()), context) + Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0)), context) } /// `Generator.prototype.throw ( exception )` @@ -195,7 +195,7 @@ impl Generator { // 1. Let g be the this value. // 2. Let C be ThrowCompletion(exception). // 3. Return ? GeneratorResumeAbrupt(g, C, empty). - Self::generator_resume_abrupt(this, Err(args.get_or_undefined(0).clone()), context) + Self::generator_resume_abrupt(this, Err(args.get_or_undefined(0)), context) } /// `27.5.3.3 GeneratorResume ( generator, value, generatorBrand )` diff --git a/boa_engine/src/builtins/intl/mod.rs b/boa_engine/src/builtins/intl/mod.rs index 116dbfbe70..6ab7613819 100644 --- a/boa_engine/src/builtins/intl/mod.rs +++ b/boa_engine/src/builtins/intl/mod.rs @@ -77,7 +77,7 @@ impl Intl { let ll = canonicalize_locale_list(args, context)?; // 2. Return CreateArrayFromList(ll). - Ok(JsValue::Object(Array::create_array_from_list( + Ok(JsValue::new(Array::create_array_from_list( ll.into_iter().map(|loc| loc.to_string().into()), context, ))) @@ -595,7 +595,7 @@ fn resolve_locale( // e. Let value be keyLocaleData[0]. // TODO f. Assert: Type(value) is either String or Null. let mut value = match key_locale_data.get(0) { - Some(first_elt) => JsValue::String(first_elt.clone()), + Some(first_elt) => JsValue::from(first_elt.clone()), None => JsValue::null(), }; @@ -616,7 +616,7 @@ fn resolve_locale( // a. If keyLocaleData contains requestedValue, then if key_locale_data.contains(requested_value) { // i. Let value be requestedValue. - value = JsValue::String(JsString::new(requested_value)); + value = JsValue::from(JsString::new(requested_value)); // ii. Let supportedExtensionAddition be the string-concatenation // of "-", key, "-", and value. supported_extension_addition = @@ -625,7 +625,7 @@ fn resolve_locale( // 4. Else if keyLocaleData contains "true", then } else if key_locale_data.contains(&JsString::new("true")) { // a. Let value be "true". - value = JsValue::String(JsString::new("true")); + value = JsValue::from(JsString::new("true")); // b. Let supportedExtensionAddition be the string-concatenation of "-" and key. supported_extension_addition = JsString::concat_array(&["-", key]); } @@ -659,7 +659,7 @@ fn resolve_locale( if let Some(options_val_str) = options_value.as_string() { if options_val_str.is_empty() { // a. Let optionsValue be "true". - options_value = JsValue::String(JsString::new("true")); + options_value = JsValue::from(JsString::new("true")); } } } @@ -747,13 +747,13 @@ pub(crate) fn get_option( // 7. If values is not undefined and values does not contain an element equal to value, // throw a RangeError exception. value = match r#type { - GetOptionType::Boolean => JsValue::Boolean(value.to_boolean()), + GetOptionType::Boolean => JsValue::from(value.to_boolean()), GetOptionType::String => { let string_value = value.to_string(context)?; if !values.is_empty() && !values.contains(&string_value) { return context.throw_range_error("GetOption: values array does not contain value"); } - JsValue::String(string_value) + JsValue::from(string_value) } }; diff --git a/boa_engine/src/builtins/intl/tests.rs b/boa_engine/src/builtins/intl/tests.rs index 6abfb0c600..75fcf7940f 100644 --- a/boa_engine/src/builtins/intl/tests.rs +++ b/boa_engine/src/builtins/intl/tests.rs @@ -284,7 +284,7 @@ fn get_opt() { let mut context = Context::default(); let values = Vec::::new(); - let fallback = JsValue::String(JsString::new("fallback")); + let fallback = JsValue::from(JsString::new("fallback")); let options_obj = JsObject::empty(); let option_type = GetOptionType::String; let get_option_result = get_option( @@ -299,9 +299,9 @@ fn get_opt() { assert_eq!(get_option_result, fallback); let values = Vec::::new(); - let fallback = JsValue::String(JsString::new("fallback")); + let fallback = JsValue::from(JsString::new("fallback")); let options_obj = JsObject::empty(); - let locale_value = JsValue::String(JsString::new("en-US")); + let locale_value = JsValue::from(JsString::new("en-US")); options_obj .set("Locale", locale_value.clone(), true, &mut context) .expect("Setting a property should not fail"); @@ -317,10 +317,10 @@ fn get_opt() { .expect("GetOption should not fail on string test"); assert_eq!(get_option_result, locale_value); - let fallback = JsValue::String(JsString::new("fallback")); + let fallback = JsValue::from(JsString::new("fallback")); let options_obj = JsObject::empty(); let locale_string = JsString::new("en-US"); - let locale_value = JsValue::String(locale_string.clone()); + let locale_value = JsValue::from(locale_string.clone()); let values = vec![locale_string]; options_obj .set("Locale", locale_value.clone(), true, &mut context) @@ -356,9 +356,9 @@ fn get_opt() { .expect("GetOption should not fail on boolean test"); assert_eq!(get_option_result, boolean_value); - let fallback = JsValue::String(JsString::new("fallback")); + let fallback = JsValue::from(JsString::new("fallback")); let options_obj = JsObject::empty(); - let locale_value = JsValue::String(JsString::new("en-US")); + let locale_value = JsValue::from(JsString::new("en-US")); let other_locale_str = JsString::new("de-DE"); let values = vec![other_locale_str]; options_obj @@ -473,7 +473,7 @@ fn to_date_time_opts() { ) .expect("toDateTimeOptions should not fail in date test"); - let numeric_jsstring = JsValue::String(JsString::new("numeric")); + let numeric_jsstring = JsValue::from(JsString::new("numeric")); assert_eq!( date_time_opts.get("year", &mut context), Ok(numeric_jsstring.clone()) @@ -495,7 +495,7 @@ fn to_date_time_opts() { ) .expect("toDateTimeOptions should not fail in time test"); - let numeric_jsstring = JsValue::String(JsString::new("numeric")); + let numeric_jsstring = JsValue::from(JsString::new("numeric")); assert_eq!( date_time_opts.get("hour", &mut context), Ok(numeric_jsstring.clone()) @@ -517,7 +517,7 @@ fn to_date_time_opts() { ) .expect("toDateTimeOptions should not fail when testing required = 'any'"); - let numeric_jsstring = JsValue::String(JsString::new("numeric")); + let numeric_jsstring = JsValue::from(JsString::new("numeric")); assert_eq!( date_time_opts.get("year", &mut context), Ok(numeric_jsstring.clone()) diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index 78bbfc97f4..de38df46de 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -153,7 +153,7 @@ impl JsValue { // 1. Let syncMethod be ? GetMethod(obj, @@iterator). let sync_method = self .get_method(WellKnownSymbols::iterator(), context)? - .map_or(Self::Undefined, Self::from); + .map_or(Self::undefined(), Self::from); // 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod). let _sync_iterator_record = self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method)); @@ -163,7 +163,7 @@ impl JsValue { } else { // b. Otherwise, set method to ? GetMethod(obj, @@iterator). self.get_method(WellKnownSymbols::iterator(), context)? - .map_or(Self::Undefined, Self::from) + .map_or(Self::undefined(), Self::from) } }; diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index e51e10110c..3653bdd899 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -95,7 +95,7 @@ impl Json { let unfiltered = context.eval(script_string.as_bytes())?; // 11. If IsCallable(reviver) is true, then - if let Some(obj) = args.get_or_undefined(1).as_callable() { + if let Some(ref obj) = args.get_or_undefined(1).as_callable() { // a. Let root be ! OrdinaryObjectCreate(%Object.prototype%). let root = context.construct_object(); @@ -129,7 +129,7 @@ impl Json { let val = holder.get(name.clone(), context)?; // 2. If Type(val) is Object, then - if let Some(obj) = val.as_object() { + if let Some(ref obj) = val.as_object() { // a. Let isArray be ? IsArray(val). // b. If isArray is true, then if obj.is_array_abstract(context)? { @@ -280,7 +280,7 @@ impl Json { } } - let mut space = args.get_or_undefined(2).clone(); + let mut space = args.get_or_undefined(2); // 5. If Type(space) is Object, then if let Some(space_obj) = space.as_object() { @@ -332,7 +332,7 @@ impl Json { // 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value). wrapper - .create_data_property_or_throw("", args.get_or_undefined(0).clone(), context) + .create_data_property_or_throw("", args.get_or_undefined(0), context) .expect("CreateDataPropertyOrThrow should never fail here"); // 11. Let state be the Record { [[ReplacerFunction]]: ReplacerFunction, [[Stack]]: stack, [[Indent]]: indent, [[Gap]]: gap, [[PropertyList]]: PropertyList }. @@ -388,7 +388,7 @@ impl Json { } // 4. If Type(value) is Object, then - if let Some(obj) = value.as_object().cloned() { + if let Some(obj) = value.as_object().as_deref().cloned() { // a. If value has a [[NumberData]] internal slot, then if obj.is_number() { // i. Set value to ? ToNumber(value). @@ -427,7 +427,7 @@ impl Json { } // 8. If Type(value) is String, return QuoteJSONString(value). - if let Some(s) = value.as_string() { + if let Some(ref s) = value.as_string() { return Ok(Some(Self::quote_json_string(s))); } @@ -452,7 +452,7 @@ impl Json { } // 11. If Type(value) is Object and IsCallable(value) is false, then - if let Some(obj) = value.as_object() { + if let Some(ref obj) = value.as_object() { if !obj.is_callable() { // a. Let isArray be ? IsArray(value). // b. If isArray is true, return ? SerializeJSONArray(state, value). diff --git a/boa_engine/src/builtins/map/map_iterator.rs b/boa_engine/src/builtins/map/map_iterator.rs index 80cc1697ce..f1877a627b 100644 --- a/boa_engine/src/builtins/map/map_iterator.rs +++ b/boa_engine/src/builtins/map/map_iterator.rs @@ -72,7 +72,8 @@ impl MapIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut map_iterator = this.as_object().map(JsObject::borrow_mut); + let map_iterator = this.as_object(); + let mut map_iterator = map_iterator.as_deref().map(JsObject::borrow_mut); let map_iterator = map_iterator .as_mut() .and_then(|obj| obj.as_map_iterator_mut()) diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index 8e8920744b..d2db963e64 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -21,6 +21,7 @@ use crate::{ }, property::{Attribute, PropertyNameKind}, symbol::WellKnownSymbols, + value::JsVariant, Context, JsResult, JsValue, }; use boa_profiler::Profiler; @@ -141,7 +142,7 @@ impl Map { let adder = map.get("set", context)?; // 6. Return ? AddEntriesFromIterable(map, iterable, adder). - add_entries_from_iterable(&map, iterable, &adder, context) + add_entries_from_iterable(&map, &iterable, &adder, context) } /// `get Map [ @@species ]` @@ -219,11 +220,11 @@ impl Map { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. if let Some(map) = object.borrow_mut().as_map_mut() { - let key = match key { - JsValue::Rational(r) => { + let key = match key.variant() { + JsVariant::Float64(r) => { // 5. If key is -0𝔽, set key to +0𝔽. if r.is_zero() { - JsValue::Rational(0f64) + JsValue::new(0f64) } else { key.clone() } @@ -235,7 +236,7 @@ impl Map { // i. Set p.[[Value]] to value. // 6. Let p be the Record { [[Key]]: key, [[Value]]: value }. // 7. Append p as the last element of entries. - map.insert(key, value.clone()); + map.insert(key, value); // ii. Return M. // 8. Return M. return Ok(this.clone()); @@ -303,7 +304,7 @@ impl Map { // ii. Set p.[[Value]] to empty. // iii. Return true. // 5. Return false. - return Ok(map.remove(key).is_some().into()); + return Ok(map.remove(&key).is_some().into()); } } context.throw_type_error("'this' is not a Map") @@ -324,29 +325,21 @@ impl Map { args: &[JsValue], context: &mut Context, ) -> JsResult { - const JS_ZERO: &JsValue = &JsValue::Rational(0f64); - let key = args.get_or_undefined(0); - let key = match key { - JsValue::Rational(r) => { - if r.is_zero() { - JS_ZERO - } else { - key - } - } - _ => key, + let key = match key.variant() { + JsVariant::Float64(r) if r.is_zero() => JsValue::new(0f64), + _ => key.clone(), }; // 1. Let M be the this value. - if let JsValue::Object(ref object) = this { + if let Some(object) = this.as_object() { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. if let Some(map) = object.borrow().as_map_ref() { // 4. For each Record { [[Key]], [[Value]] } p of entries, do // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return p.[[Value]]. // 5. Return undefined. - return Ok(map.get(key).cloned().unwrap_or_default()); + return Ok(map.get(&key).cloned().unwrap_or_default()); } } @@ -396,29 +389,21 @@ impl Map { args: &[JsValue], context: &mut Context, ) -> JsResult { - const JS_ZERO: &JsValue = &JsValue::Rational(0f64); - let key = args.get_or_undefined(0); - let key = match key { - JsValue::Rational(r) => { - if r.is_zero() { - JS_ZERO - } else { - key - } - } - _ => key, + let key = match key.variant() { + JsVariant::Float64(r) if r.is_zero() => JsValue::new(0f64), + _ => key.clone(), }; // 1. Let M be the this value. - if let JsValue::Object(ref object) = this { + if let Some(object) = this.as_object() { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. if let Some(map) = object.borrow().as_map_ref() { // 4. For each Record { [[Key]], [[Value]] } p of entries, do // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return true. // 5. Return false. - return Ok(map.contains_key(key).into()); + return Ok(map.contains_key(&key).into()); } } @@ -489,7 +474,7 @@ impl Map { // a. If e.[[Key]] is not empty, then if let Some(arguments) = arguments { // i. Perform ? Call(callbackfn, thisArg, « e.[[Value]], e.[[Key]], M »). - callback.call(this_arg, &arguments, context)?; + callback.call(&this_arg, &arguments, context)?; } index += 1; diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 89d8f32fed..e8e55f4316 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -211,16 +211,11 @@ pub trait JsArgs { /// Call this if you are thinking of calling something similar to /// `args.get(n).cloned().unwrap_or_default()` or /// `args.get(n).unwrap_or(&undefined)`. - /// - /// This returns a reference for efficiency, in case - /// you only need to call methods of `JsValue`, so - /// try to minimize calling `clone`. - fn get_or_undefined(&self, index: usize) -> &JsValue; + fn get_or_undefined(&self, index: usize) -> JsValue; } impl JsArgs for [JsValue] { - fn get_or_undefined(&self, index: usize) -> &JsValue { - const UNDEFINED: &JsValue = &JsValue::Undefined; - self.get(index).unwrap_or(UNDEFINED) + fn get_or_undefined(&self, index: usize) -> JsValue { + self.get(index).cloned().unwrap_or_else(JsValue::undefined) } } diff --git a/boa_engine/src/builtins/number/mod.rs b/boa_engine/src/builtins/number/mod.rs index e39ade669c..b32b896486 100644 --- a/boa_engine/src/builtins/number/mod.rs +++ b/boa_engine/src/builtins/number/mod.rs @@ -21,7 +21,7 @@ use crate::{ JsObject, ObjectData, }, property::Attribute, - value::{AbstractRelation, IntegerOrInfinity, JsValue}, + value::{AbstractRelation, IntegerOrInfinity, JsValue, JsVariant}, Context, JsResult, }; use boa_profiler::Profiler; @@ -220,7 +220,8 @@ impl Number { // 1. Let x be ? thisNumberValue(this value). let this_num = Self::this_number_value(this, context)?; let precision = match args.get(0) { - None | Some(JsValue::Undefined) => None, + None => None, + Some(n) if n.is_undefined() => None, // 2. Let f be ? ToIntegerOrInfinity(fractionDigits). Some(n) => Some(n.to_integer_or_infinity(context)?), }; @@ -992,9 +993,9 @@ impl Number { _ctx: &mut Context, ) -> JsResult { Ok(JsValue::new(if let Some(val) = args.get(0) { - match val { - JsValue::Integer(_) => true, - JsValue::Rational(number) => number.is_finite(), + match val.variant() { + JsVariant::Float64(number) => number.is_finite(), + JsVariant::Integer32(_) => true, _ => false, } } else { @@ -1041,13 +1042,7 @@ impl Number { args: &[JsValue], _ctx: &mut Context, ) -> JsResult { - Ok(JsValue::new( - if let Some(&JsValue::Rational(number)) = args.get(0) { - number.is_nan() - } else { - false - }, - )) + Ok(JsValue::new(args.get(0).map_or(false, JsValue::is_nan))) } /// `Number.isSafeInteger( number )` @@ -1070,9 +1065,9 @@ impl Number { args: &[JsValue], _ctx: &mut Context, ) -> JsResult { - Ok(JsValue::new(match args.get(0) { - Some(JsValue::Integer(_)) => true, - Some(JsValue::Rational(number)) if Self::is_float_integer(*number) => { + Ok(JsValue::new(match args.get(0).map(JsValue::variant) { + Some(JsVariant::Integer32(_)) => true, + Some(JsVariant::Float64(number)) if Self::is_float_integer(number) => { number.abs() <= Self::MAX_SAFE_INTEGER } _ => false, @@ -1087,9 +1082,9 @@ impl Number { /// [spec]: https://tc39.es/ecma262/#sec-isinteger #[inline] pub(crate) fn is_integer(val: &JsValue) -> bool { - match val { - JsValue::Integer(_) => true, - JsValue::Rational(number) => Self::is_float_integer(*number), + match val.variant() { + JsVariant::Integer32(_) => true, + JsVariant::Float64(number) => Self::is_float_integer(number), _ => false, } } diff --git a/boa_engine/src/builtins/object/for_in_iterator.rs b/boa_engine/src/builtins/object/for_in_iterator.rs index 612daa0b49..4b1d366a61 100644 --- a/boa_engine/src/builtins/object/for_in_iterator.rs +++ b/boa_engine/src/builtins/object/for_in_iterator.rs @@ -67,7 +67,8 @@ impl ForInIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut iterator = this.as_object().map(JsObject::borrow_mut); + let iterator = this.as_object(); + let mut iterator = iterator.as_deref().map(JsObject::borrow_mut); let iterator = iterator .as_mut() .and_then(|obj| obj.as_for_in_iterator_mut()) diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 891360ded7..a641fdb145 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -23,7 +23,7 @@ use crate::{ }, property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, - value::JsValue, + value::{JsValue, JsVariant}, Context, JsResult, JsString, }; use boa_profiler::Profiler; @@ -155,7 +155,7 @@ impl Object { // 2. Return ? O.[[GetPrototypeOf]](). let proto = obj.__get_prototype_of__(context)?; - Ok(proto.map_or(JsValue::Null, JsValue::new)) + Ok(proto.map_or(JsValue::null(), JsValue::new)) } /// `set Object.prototype.__proto__` @@ -178,16 +178,17 @@ impl Object { let this = this.require_object_coercible(context)?; // 2. If Type(proto) is neither Object nor Null, return undefined. - let proto = match args.get_or_undefined(0) { - JsValue::Object(proto) => Some(proto.clone()), - JsValue::Null => None, + let proto = match args.get_or_undefined(0).variant() { + JsVariant::Object(proto) => Some(proto.clone()), + JsVariant::Null => None, _ => return Ok(JsValue::undefined()), }; // 3. If Type(O) is not Object, return undefined. - let object = match this { - JsValue::Object(object) => object, - _ => return Ok(JsValue::undefined()), + let object = if let Some(o) = this.as_object() { + o + } else { + return Ok(JsValue::undefined()); }; // 4. Let status be ? O.[[SetPrototypeOf]](proto). @@ -389,9 +390,9 @@ impl Object { let prototype = args.get_or_undefined(0); let properties = args.get_or_undefined(1); - let obj = match prototype { - JsValue::Object(_) | JsValue::Null => JsObject::from_proto_and_data( - prototype.as_object().cloned(), + let obj = match prototype.variant() { + JsVariant::Object(_) | JsVariant::Null => JsObject::from_proto_and_data( + prototype.as_object().as_deref().cloned(), ObjectData::ordinary(), ), _ => { @@ -403,7 +404,7 @@ impl Object { }; if !properties.is_undefined() { - object_define_properties(&obj, properties, context)?; + object_define_properties(&obj, &properties, context)?; return Ok(obj.into()); } @@ -480,7 +481,7 @@ impl Object { } // 5. Return descriptors. - Ok(descriptors.into()) + Ok(JsValue::new(descriptors)) } /// The abstract operation `FromPropertyDescriptor`. @@ -553,7 +554,7 @@ impl Object { let x = args.get_or_undefined(0); let y = args.get_or_undefined(1); - Ok(JsValue::same_value(x, y).into()) + Ok(JsValue::same_value(&x, &y).into()) } /// Get the `prototype` of an object. @@ -574,7 +575,7 @@ impl Object { // 2. Return ? obj.[[GetPrototypeOf]](). Ok(obj .__get_prototype_of__(ctx)? - .map_or(JsValue::Null, JsValue::new)) + .map_or(JsValue::null(), JsValue::new)) } /// Set the `prototype` of an object. @@ -582,9 +583,13 @@ impl Object { /// [More information][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof - pub fn set_prototype_of(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult { + pub fn set_prototype_of( + _: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { if args.len() < 2 { - return ctx.throw_type_error(format!( + return context.throw_type_error(format!( "Object.setPrototypeOf: At least 2 arguments required, but only {} passed", args.len() )); @@ -595,16 +600,19 @@ impl Object { .get(0) .cloned() .unwrap_or_default() - .require_object_coercible(ctx)? + .require_object_coercible(context)? .clone(); - let proto = match args.get_or_undefined(1) { - JsValue::Object(obj) => Some(obj.clone()), - JsValue::Null => None, + let proto = args.get_or_undefined(1); + let proto = match proto.variant() { + JsVariant::Object(obj) => Some(obj.clone()), + JsVariant::Null => None, // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception. - val => { - return ctx - .throw_type_error(format!("expected an object or null, got {}", val.type_of())) + _ => { + return context.throw_type_error(format!( + "expected an object or null, got {}", + proto.type_of() + )) } }; @@ -616,11 +624,11 @@ impl Object { }; // 4. Let status be ? O.[[SetPrototypeOf]](proto). - let status = obj.__set_prototype_of__(proto, ctx)?; + let status = obj.__set_prototype_of__(proto, context)?; // 5. If status is false, throw a TypeError exception. if !status { - return ctx.throw_type_error("can't set prototype of this object"); + return context.throw_type_error("can't set prototype of this object"); } // 6. Return O. @@ -646,7 +654,7 @@ impl Object { if !v.is_object() { return Ok(JsValue::new(false)); } - let mut v = v.clone(); + let mut v = v; let o = JsValue::new(this.to_object(context)?); loop { v = Self::get_prototype_of(this, &[v], context)?; @@ -666,14 +674,14 @@ impl Object { context: &mut Context, ) -> JsResult { let object = args.get_or_undefined(0); - if let JsValue::Object(object) = object { + if let Some(object) = object.as_object() { let key = args .get(1) - .unwrap_or(&JsValue::Undefined) + .unwrap_or(&JsValue::undefined()) .to_property_key(context)?; let desc = args .get(2) - .unwrap_or(&JsValue::Undefined) + .unwrap_or(&JsValue::undefined()) .to_property_descriptor(context)?; object.define_property_or_throw(key, desc, context)?; @@ -700,9 +708,9 @@ impl Object { context: &mut Context, ) -> JsResult { let arg = args.get_or_undefined(0); - if let JsValue::Object(obj) = arg { + if let Some(ref obj) = arg.as_object() { let props = args.get_or_undefined(1); - object_define_properties(obj, props, context)?; + object_define_properties(obj, &props, context)?; Ok(arg.clone()) } else { context.throw_type_error("Expected an object") @@ -778,7 +786,8 @@ impl Object { let tag = o.get(WellKnownSymbols::to_string_tag(), context)?; // 16. If Type(tag) is not String, set tag to builtinTag. - let tag_str = tag.as_string().map_or(builtin_tag, JsString::as_str); + let tag_str = tag.as_string(); + let tag_str = tag_str.as_deref().map_or(builtin_tag, JsString::as_str); // 17. Return the string-concatenation of "[object ", tag, and "]". Ok(format!("[object {tag_str}]").into()) @@ -1152,7 +1161,7 @@ impl Object { ) -> JsResult { // 1. Return ? GetOwnPropertyKeys(O, string). let o = args.get_or_undefined(0); - get_own_property_keys(o, PropertyKeyType::String, context) + get_own_property_keys(&o, PropertyKeyType::String, context) } /// `Object.getOwnPropertySymbols( object )` @@ -1170,7 +1179,7 @@ impl Object { ) -> JsResult { // 1. Return ? GetOwnPropertyKeys(O, symbol). let o = args.get_or_undefined(0); - get_own_property_keys(o, PropertyKeyType::Symbol, context) + get_own_property_keys(&o, PropertyKeyType::Symbol, context) } /// `Object.hasOwn( object, property )` @@ -1202,7 +1211,8 @@ impl Object { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries pub fn from_entries(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Perform ? RequireObjectCoercible(iterable). - let iterable = args.get_or_undefined(0).require_object_coercible(context)?; + let iterable = args.get_or_undefined(0); + let iterable = iterable.require_object_coercible(context)?; // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 3. Assert: obj is an extensible ordinary object with no own properties. diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index 42189781a6..2350054365 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -144,13 +144,13 @@ impl PromiseCapability { let reject = args.get_or_undefined(1); // c. Set promiseCapability.[[Resolve]] to resolve. - promise_capability.resolve = resolve.clone(); + promise_capability.resolve = resolve; // d. Set promiseCapability.[[Reject]] to reject. - promise_capability.reject = reject.clone(); + promise_capability.reject = reject; // e. Return undefined. - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) }, promise_capability.clone(), ) @@ -170,6 +170,7 @@ impl PromiseCapability { // 7. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception. let resolve = resolve .as_object() + .as_deref() .cloned() .and_then(JsFunction::from_object) .ok_or_else(|| { @@ -180,6 +181,7 @@ impl PromiseCapability { // 8. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception. let reject = reject .as_object() + .as_deref() .cloned() .and_then(JsFunction::from_object) .ok_or_else(|| { @@ -318,8 +320,8 @@ impl Promise { // 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ). let completion = context.call( - executor, - &JsValue::Undefined, + &executor, + &JsValue::undefined(), &[ resolving_functions.resolve, resolving_functions.reject.clone(), @@ -329,7 +331,7 @@ impl Promise { // 10. If completion is an abrupt completion, then if let Err(value) = completion { // a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »). - context.call(&resolving_functions.reject, &JsValue::Undefined, &[value])?; + context.call(&resolving_functions.reject, &JsValue::undefined(), &[value])?; } // 11. Return promise. @@ -359,7 +361,7 @@ impl Promise { let c = c.as_object().expect("must be a constructor"); // 3. Let promiseResolve be Completion(GetPromiseResolve(C)). - let promise_resolve = Self::get_promise_resolve(c, context); + let promise_resolve = Self::get_promise_resolve(&c, context); // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). if_abrupt_reject_promise!(promise_resolve, promise_capability, context); @@ -374,7 +376,7 @@ impl Promise { // 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). let mut result = Self::perform_promise_all( &mut iterator_record, - c, + &c, &promise_capability, &promise_resolve, context, @@ -489,7 +491,7 @@ impl Promise { }; // h. Append undefined to values. - values.borrow_mut().push(JsValue::Undefined); + values.borrow_mut().push(JsValue::undefined()); // i. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). let next_promise = @@ -523,7 +525,7 @@ impl Promise { // 7. Let remainingElementsCount be F.[[RemainingElements]]. // 8. Set values[index] to x. - captures.values.borrow_mut()[captures.index] = args.get_or_undefined(0).clone(); + captures.values.borrow_mut()[captures.index] = args.get_or_undefined(0); // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. captures @@ -600,7 +602,7 @@ impl Promise { let c = c.as_object().expect("must be a constructor"); // 3. Let promiseResolve be Completion(GetPromiseResolve(C)). - let promise_resolve = Self::get_promise_resolve(c, context); + let promise_resolve = Self::get_promise_resolve(&c, context); // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). if_abrupt_reject_promise!(promise_resolve, promise_capability, context); @@ -615,7 +617,7 @@ impl Promise { // 7. Let result be Completion(PerformPromiseAllSettled(iteratorRecord, C, promiseCapability, promiseResolve)). let mut result = Self::perform_promise_all_settled( &mut iterator_record, - c, + &c, &promise_capability, &promise_resolve, context, @@ -931,7 +933,7 @@ impl Promise { let c = c.as_object().expect("must be a constructor"); // 3. Let promiseResolve be Completion(GetPromiseResolve(C)). - let promise_resolve = Self::get_promise_resolve(c, context); + let promise_resolve = Self::get_promise_resolve(&c, context); // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). if_abrupt_reject_promise!(promise_resolve, promise_capability, context); @@ -946,7 +948,7 @@ impl Promise { // 7. Let result be Completion(PerformPromiseAny(iteratorRecord, C, promiseCapability, promiseResolve)). let mut result = Self::perform_promise_any( &mut iterator_record, - c, + &c, &promise_capability, &promise_resolve, context, @@ -1112,7 +1114,7 @@ impl Promise { // 7. Let remainingElementsCount be F.[[RemainingElements]]. // 8. Set errors[index] to x. - captures.errors.borrow_mut()[captures.index] = args.get_or_undefined(0).clone(); + captures.errors.borrow_mut()[captures.index] = args.get_or_undefined(0); // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. captures @@ -1232,7 +1234,7 @@ impl Promise { // 5. If alreadyResolved.[[Value]] is true, return undefined. if already_resolved.get() { - return Ok(JsValue::Undefined); + return Ok(JsValue::undefined()); } // 6. Set alreadyResolved.[[Value]] to true. @@ -1241,7 +1243,7 @@ impl Promise { let resolution = args.get_or_undefined(0); // 7. If SameValue(resolution, promise) is true, then - if JsValue::same_value(resolution, &promise.clone().into()) { + if JsValue::same_value(&resolution, &promise.clone().into()) { // a. Let selfResolutionError be a newly created TypeError object. let self_resolution_error = context.construct_type_error("SameValue(resolution, promise) is true"); @@ -1254,7 +1256,7 @@ impl Promise { .reject_promise(&self_resolution_error, context); // c. Return undefined. - return Ok(JsValue::Undefined); + return Ok(JsValue::undefined()); } let then = if let Some(resolution) = resolution.as_object() { @@ -1267,10 +1269,10 @@ impl Promise { .borrow_mut() .as_promise_mut() .expect("Expected promise to be a Promise") - .fulfill_promise(resolution, context)?; + .fulfill_promise(&resolution, context)?; // b. Return undefined. - return Ok(JsValue::Undefined); + return Ok(JsValue::undefined()); }; let then_action = match then { @@ -1284,7 +1286,7 @@ impl Promise { .reject_promise(&value, context); // b. Return undefined. - return Ok(JsValue::Undefined); + return Ok(JsValue::undefined()); } // 11. Let thenAction be then.[[Value]]. Ok(then) => then, @@ -1299,10 +1301,10 @@ impl Promise { .borrow_mut() .as_promise_mut() .expect("Expected promise to be a Promise") - .fulfill_promise(resolution, context)?; + .fulfill_promise(&resolution, context)?; // b. Return undefined. - return Ok(JsValue::Undefined); + return Ok(JsValue::undefined()); } }; @@ -1321,7 +1323,7 @@ impl Promise { context.host_enqueue_promise_job(job); // 16. Return undefined. - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) }, resolve_captures, ) @@ -1356,7 +1358,7 @@ impl Promise { // 5. If alreadyResolved.[[Value]] is true, return undefined. if already_resolved.get() { - return Ok(JsValue::Undefined); + return Ok(JsValue::undefined()); } // 6. Set alreadyResolved.[[Value]] to true. @@ -1368,10 +1370,10 @@ impl Promise { .borrow_mut() .as_promise_mut() .expect("Expected promise to be a Promise") - .reject_promise(args.get_or_undefined(0), context); + .reject_promise(&args.get_or_undefined(0), context); // 8. Return undefined. - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) }, reject_captures, ) @@ -1523,7 +1525,7 @@ impl Promise { // 3. Let promiseResolve be Completion(GetPromiseResolve(C)). let promise_resolve = - Self::get_promise_resolve(c.as_object().expect("this was not an object"), context); + Self::get_promise_resolve(&c.as_object().expect("this was not an object"), context); // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). if_abrupt_reject_promise!(promise_resolve, promise_capability, context); @@ -1648,7 +1650,7 @@ impl Promise { context.call( &promise_capability.reject.clone().into(), &JsValue::undefined(), - &[r.clone()], + &[r], )?; // 4. Return promiseCapability.[[Promise]]. @@ -1671,7 +1673,7 @@ impl Promise { if let Some(c) = c.as_object() { // 3. Return ? PromiseResolve(C, x). - Self::promise_resolve(c.clone(), x.clone(), context) + Self::promise_resolve(c.clone(), x, context) } else { // 2. If Type(C) is not Object, throw a TypeError exception. context.throw_type_error("Promise.resolve() called on a non-object") @@ -1710,7 +1712,7 @@ impl Promise { // 2. Return ? Invoke(promise, "then", « undefined, onRejected »). promise.invoke( "then", - &[JsValue::undefined(), on_rejected.clone()], + &[JsValue::undefined(), on_rejected], context, ) } @@ -1777,7 +1779,7 @@ impl Promise { Ok(captures.value.clone()) }, ReturnValueCaptures { - value: value.clone(), + value, }, ); @@ -1822,7 +1824,7 @@ impl Promise { Err(captures.reason.clone()) }, ThrowReasonCaptures { - reason: reason.clone(), + reason, }, ); @@ -1833,7 +1835,7 @@ impl Promise { promise.invoke("then", &[thrower.into()], context) }, FinallyCaptures { - on_finally: on_finally.clone(), + on_finally, c, }, ); @@ -1846,7 +1848,7 @@ impl Promise { // 6. Else, // a. Let thenFinally be onFinally. // b. Let catchFinally be onFinally. - (on_finally.clone(), on_finally.clone()) + (on_finally.clone(), on_finally) }; // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »). @@ -1881,11 +1883,11 @@ impl Promise { let on_rejected = args.get_or_undefined(1); // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability). + let mut promise_obj = promise_obj.borrow_mut(); promise_obj - .borrow_mut() .as_promise_mut() .expect("IsPromise(promise) is false") - .perform_promise_then(on_fulfilled, on_rejected, Some(result_capability), context) + .perform_promise_then(&on_fulfilled, &on_rejected, Some(result_capability), context) .pipe(Ok) } @@ -1999,7 +2001,7 @@ impl Promise { match result_capability { // 13. If resultCapability is undefined, then // a. Return undefined. - None => JsValue::Undefined, + None => JsValue::undefined(), // 14. Else, // a. Return resultCapability.[[Promise]]. diff --git a/boa_engine/src/builtins/promise/promise_job.rs b/boa_engine/src/builtins/promise/promise_job.rs index 30b486e93e..f065b0ee95 100644 --- a/boa_engine/src/builtins/promise/promise_job.rs +++ b/boa_engine/src/builtins/promise/promise_job.rs @@ -55,7 +55,7 @@ impl PromiseJob { }, // e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)). Some(handler) => { - handler.call_job_callback(&JsValue::Undefined, &[argument.clone()], context) + handler.call_job_callback(&JsValue::undefined(), &[argument.clone()], context) } }; @@ -69,7 +69,7 @@ impl PromiseJob { ); // ii. Return empty. - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) } Some(promise_capability_record) => { // g. Assert: promiseCapability is a PromiseCapability Record. @@ -83,13 +83,13 @@ impl PromiseJob { // h. If handlerResult is an abrupt completion, then Err(value) => { // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). - context.call(&reject.clone().into(), &JsValue::Undefined, &[value]) + context.call(&reject.clone().into(), &JsValue::undefined(), &[value]) } // i. Else, Ok(value) => { // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). - context.call(&resolve.clone().into(), &JsValue::Undefined, &[value]) + context.call(&resolve.clone().into(), &JsValue::undefined(), &[value]) } } } @@ -149,7 +149,7 @@ impl PromiseJob { // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). return context.call( &resolving_functions.reject, - &JsValue::Undefined, + &JsValue::undefined(), &[value], ); } diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index 91f58fa272..31a2bba416 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -81,7 +81,12 @@ impl Proxy { } // 2. Return ? ProxyCreate(target, handler). - Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context).map(JsValue::from) + Self::create( + &args.get_or_undefined(0), + &args.get_or_undefined(1), + context, + ) + .map(JsValue::from) } // `10.5.14 ProxyCreate ( target, handler )` @@ -162,7 +167,11 @@ impl Proxy { /// [spec]: https://tc39.es/ecma262/#sec-proxy.revocable fn revocable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let p be ? ProxyCreate(target, handler). - let p = Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context)?; + let p = Self::create( + &args.get_or_undefined(0), + &args.get_or_undefined(1), + context, + )?; // Revoker creation steps on `Proxy::revoker` let revoker = Self::revoker(p.clone(), context); diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index 52da25618f..1d7f869199 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -16,6 +16,7 @@ use crate::{ object::ObjectInitializer, property::Attribute, symbol::WellKnownSymbols, + value::JsVariant, Context, JsResult, JsValue, }; use boa_profiler::Profiler; @@ -86,7 +87,7 @@ impl Reflect { return context.throw_type_error("target must be a function"); } let args = args_list.create_list_from_array_like(&[], context)?; - target.call(this_arg, &args, context) + target.call(&this_arg, &args, context) } /// Calls a target function as a constructor with arguments. @@ -104,20 +105,21 @@ impl Reflect { ) -> JsResult { // 1. If IsConstructor(target) is false, throw a TypeError exception. let target = args - .get_or_undefined(0) + .get_or_undefined(0); + let target = target .as_constructor() .ok_or_else(|| context.construct_type_error("target must be a constructor"))?; let new_target = if let Some(new_target) = args.get(2) { // 3. Else if IsConstructor(newTarget) is false, throw a TypeError exception. if let Some(new_target) = new_target.as_constructor() { - new_target + new_target.clone() } else { return context.throw_type_error("newTarget must be a constructor"); } } else { // 2. If newTarget is not present, set newTarget to target. - target + target.clone() }; // 4. Let args be ? CreateListFromArrayLike(argumentsList). @@ -127,7 +129,7 @@ impl Reflect { // 5. Return ? Construct(target, args, newTarget). target - .construct(&args, Some(new_target), context) + .construct(&args, Some(&new_target), context) .map(JsValue::from) } @@ -151,7 +153,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().cloned()) + .and_then(|v| v.as_object().as_deref().cloned()) .ok_or_else(|| context.construct_type_error("property descriptor must be an object"))? .into(); @@ -254,7 +256,7 @@ impl Reflect { .ok_or_else(|| context.construct_type_error("target must be an object"))?; Ok(target .__get_prototype_of__(context)? - .map_or(JsValue::Null, JsValue::new)) + .map_or(JsValue::null(), JsValue::new)) } /// Returns `true` if the object has the property, `false` otherwise. @@ -365,9 +367,7 @@ impl Reflect { } else { target.clone().into() }; - Ok(target - .__set__(key, value.clone(), receiver, context)? - .into()) + Ok(target.__set__(key, value, receiver, context)?.into()) } /// Sets the prototype of an object. @@ -387,9 +387,9 @@ impl Reflect { .get(0) .and_then(JsValue::as_object) .ok_or_else(|| context.construct_type_error("target must be an object"))?; - let proto = match args.get_or_undefined(1) { - JsValue::Object(obj) => Some(obj.clone()), - JsValue::Null => None, + let proto = match args.get_or_undefined(1).variant() { + JsVariant::Object(obj) => Some(obj.clone()), + JsVariant::Null => None, _ => return context.throw_type_error("proto must be an object or null"), }; Ok(target.__set_prototype_of__(proto, context)?.into()) diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index 8ca3f71ea9..367fd1ede7 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -179,13 +179,13 @@ impl RegExp { if new_target.is_undefined() { // a. Let newTarget be the active function object. // b. If patternIsRegExp is true and flags is undefined, then - if let Some(pattern) = pattern_is_regexp { + if let Some(ref pattern) = pattern_is_regexp { if flags.is_undefined() { // i. Let patternConstructor be ? Get(pattern, "constructor"). let pattern_constructor = pattern.get("constructor", context)?; // ii. If SameValue(newTarget, patternConstructor) is true, return pattern. if JsValue::same_value(new_target, &pattern_constructor) { - return Ok(pattern.clone().into()); + return Ok((&**pattern).clone().into()); } } } @@ -208,12 +208,12 @@ impl RegExp { JsValue::new(regexp.original_flags.clone()), ) } else { - (JsValue::new(regexp.original_source.clone()), flags.clone()) + (JsValue::new(regexp.original_source.clone()), flags) } } else { // a. Let P be pattern. // b. Let F be flags. - (pattern.clone(), flags.clone()) + (pattern.clone(), flags) }; // 7. Let O be ? RegExpAlloc(newTarget). @@ -352,7 +352,7 @@ impl RegExp { #[inline] fn regexp_has_flag(this: &JsValue, flag: u8, context: &mut Context) -> JsResult { - if let Some(object) = this.as_object() { + if let Some(ref object) = this.as_object() { if let Some(regexp) = object.borrow().as_regexp() { return Ok(JsValue::new(match flag { b'd' => regexp.flags.contains(RegExpFlags::HAS_INDICES), @@ -695,7 +695,7 @@ impl RegExp { .to_string(context)?; // 4. Let match be ? RegExpExec(R, string). - let m = Self::abstract_exec(this, arg_str, context)?; + let m = Self::abstract_exec(&this, arg_str, context)?; // 5. If match is not null, return true; else return false. if m.is_some() { @@ -735,7 +735,7 @@ impl RegExp { 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)? { + if let Some(v) = Self::abstract_builtin_exec(&obj, &arg_str, context)? { Ok(v.into()) } else { Ok(JsValue::null()) @@ -770,7 +770,7 @@ impl RegExp { } // c. Return result. - return Ok(result.as_object().cloned()); + return Ok(result.as_object().as_deref().cloned()); } // 5. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]). @@ -1050,7 +1050,7 @@ impl RegExp { #[allow(clippy::if_not_else)] if !global { // a. Return ? RegExpExec(rx, S). - if let Some(v) = Self::abstract_exec(rx, arg_str, context)? { + if let Some(v) = Self::abstract_exec(&rx, arg_str, context)? { Ok(v.into()) } else { Ok(JsValue::null()) @@ -1075,7 +1075,7 @@ impl RegExp { // f. Repeat, loop { // i. Let result be ? RegExpExec(rx, S). - let result = Self::abstract_exec(rx, arg_str.clone(), context)?; + let result = Self::abstract_exec(&rx, arg_str.clone(), context)?; // ii. If result is null, then // iii. Else, @@ -1244,9 +1244,10 @@ impl RegExp { let length_arg_str = arg_str.encode_utf16().count(); // 5. Let functionalReplace be IsCallable(replaceValue). - let mut replace_value = args.get_or_undefined(1).clone(); + let mut replace_value = args.get_or_undefined(1); let functional_replace = replace_value .as_object() + .as_deref() .map(JsObject::is_callable) .unwrap_or_default(); @@ -1276,7 +1277,7 @@ impl RegExp { // 11. Repeat, while done is false, loop { // a. Let result be ? RegExpExec(rx, S). - let result = Self::abstract_exec(rx, 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, @@ -1498,7 +1499,7 @@ impl RegExp { } // 6. Let result be ? RegExpExec(rx, S). - let result = Self::abstract_exec(rx, 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)?; diff --git a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs index 620d26640b..49c065b8b6 100644 --- a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs +++ b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs @@ -81,7 +81,8 @@ impl RegExpStringIterator { } pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut iterator = this.as_object().map(JsObject::borrow_mut); + let iterator = this.as_object(); + let mut iterator = iterator.as_deref().map(JsObject::borrow_mut); let iterator = iterator .as_mut() .and_then(|obj| obj.as_regexp_string_iterator_mut()) diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index 282f2f7d3d..e827427740 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -142,7 +142,7 @@ impl Set { })?; // 7. Let iteratorRecord be ? GetIterator(iterable). - let iterator_record = iterable.clone().get_iterator(context, None, None)?; + let iterator_record = iterable.get_iterator(context, None, None)?; // 8. Repeat, // a. Let next be ? IteratorStep(iteratorRecord). @@ -224,9 +224,9 @@ impl Set { if let Some(object) = this.as_object() { if let Some(set) = object.borrow_mut().as_set_mut() { set.add(if value.as_number().map_or(false, |n| n == -0f64) { - JsValue::Integer(0) + JsValue::new(0) } else { - value.clone() + value }); } else { return context.throw_type_error("'this' is not a Set"); @@ -281,7 +281,7 @@ impl Set { let res = if let Some(object) = this.as_object() { if let Some(set) = object.borrow_mut().as_set_mut() { - set.delete(value) + set.delete(&value) } else { return context.throw_type_error("'this' is not a Set"); } @@ -352,7 +352,7 @@ impl Set { let this_arg = if this_arg.is_undefined() { context.global_object().clone().into() } else { - this_arg.clone() + this_arg }; let mut index = 0; @@ -375,7 +375,7 @@ impl Set { index += 1; } - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) } /// `Map.prototype.has( key )` @@ -399,7 +399,7 @@ impl Set { .and_then(|obj| { obj.borrow() .as_set_ref() - .map(|set| set.contains(value).into()) + .map(|set| set.contains(&value).into()) }) .ok_or_else(|| context.construct_type_error("'this' is not a Set")) } diff --git a/boa_engine/src/builtins/set/set_iterator.rs b/boa_engine/src/builtins/set/set_iterator.rs index 4d3e86fc1b..0f2e58cea1 100644 --- a/boa_engine/src/builtins/set/set_iterator.rs +++ b/boa_engine/src/builtins/set/set_iterator.rs @@ -67,7 +67,8 @@ impl SetIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut set_iterator = this.as_object().map(JsObject::borrow_mut); + let set_iterator = this.as_object(); + let mut set_iterator = set_iterator.as_deref().map(JsObject::borrow_mut); let set_iterator = set_iterator .as_mut() @@ -86,7 +87,8 @@ impl SetIterator { )); } - let entries = m.as_object().map(JsObject::borrow); + let entries = m.as_object(); + let entries = entries.as_deref().map(JsObject::borrow); let entries = entries .as_ref() .and_then(|obj| obj.as_set_ref()) diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index f77315dd40..075b63b8b5 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -179,8 +179,12 @@ impl String { let string = match args.get(0) { // 2. Else, // a. If NewTarget is undefined and Type(value) is Symbol, return SymbolDescriptiveString(value). - Some(JsValue::Symbol(ref sym)) if new_target.is_undefined() => { - return Ok(sym.descriptive_string().into()) + Some(value) if new_target.is_undefined() && value.is_symbol() => { + return Ok(value + .as_symbol() + .expect("Already checked for a symbol") + .descriptive_string() + .into()) } // b. Let s be ? ToString(value). Some(value) => value.to_string(context)?, @@ -244,6 +248,7 @@ impl String { fn this_string_value(this: &JsValue, context: &mut Context) -> JsResult { // 1. If Type(value) is String, return value. this.as_string() + .as_deref() .cloned() // 2. If Type(value) is Object and value has a [[StringData]] internal slot, then // a. Let s be value.[[StringData]]. @@ -395,7 +400,7 @@ impl String { // If codeUnits is empty, the empty String is returned. let s = std::string::String::from_utf16_lossy(elements.as_slice()); - Ok(JsValue::String(JsString::new(s))) + Ok(JsValue::new(JsString::new(s))) } /// `String.prototype.toString ( )` @@ -780,7 +785,7 @@ impl String { // 3. Let isRegExp be ? IsRegExp(searchString). // 4. If isRegExp is true, throw a TypeError exception. - if is_reg_exp(search_string, context)? { + if is_reg_exp(&search_string, context)? { context.throw_type_error( "First argument to String.prototype.startsWith must not be a regular expression", )?; @@ -793,9 +798,11 @@ impl String { let len = string.encode_utf16().count() as i64; // 7. If position is undefined, let pos be 0; else let pos be ? ToIntegerOrInfinity(position). - let pos = match args.get_or_undefined(1) { - &JsValue::Undefined => IntegerOrInfinity::Integer(0), - position => position.to_integer_or_infinity(context)?, + let pos = args.get_or_undefined(1); + let pos = if pos.is_undefined() { + IntegerOrInfinity::Integer(0) + } else { + pos.to_integer_or_infinity(context)? }; // 8. Let start be the result of clamping pos between 0 and len. @@ -850,7 +857,7 @@ impl String { let search_str = match args.get_or_undefined(0) { // 3. Let isRegExp be ? IsRegExp(searchString). // 4. If isRegExp is true, throw a TypeError exception. - search_string if is_reg_exp(search_string, context)? => { + search_string if is_reg_exp(&search_string, context)? => { return context.throw_type_error( "First argument to String.prototype.endsWith must not be a regular expression", ); @@ -919,7 +926,7 @@ impl String { let search_str = match args.get_or_undefined(0) { // 3. Let isRegExp be ? IsRegExp(searchString). - search_string if is_reg_exp(search_string, context)? => { + search_string if is_reg_exp(&search_string, context)? => { return context.throw_type_error( // 4. If isRegExp is true, throw a TypeError exception. "First argument to String.prototype.includes must not be a regular expression", @@ -979,7 +986,7 @@ impl String { if let Some(replacer) = replacer { // i. Return ? Call(replacer, searchValue, « O, replaceValue »). return replacer.call( - search_value, + &search_value, &[this.clone(), replace_value.clone()], context, ); @@ -995,6 +1002,7 @@ impl String { // 5. Let functionalReplace be IsCallable(replaceValue). let functional_replace = replace_value .as_object() + .as_deref() .map(JsObject::is_callable) .unwrap_or_default(); @@ -1023,7 +1031,7 @@ impl String { // a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)). context .call( - replace_value, + &replace_value, &JsValue::undefined(), &[search_str.into(), position.into(), this_str.clone().into()], )? @@ -1088,7 +1096,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(ref obj) = search_value.as_object() { // b. If isRegExp is true, then if is_reg_exp_object(obj, context)? { // i. Let flags be ? Get(searchValue, "flags"). @@ -1112,7 +1120,7 @@ impl String { // d. If replacer is not undefined, then if let Some(replacer) = replacer { // i. Return ? Call(replacer, searchValue, « O, replaceValue »). - return replacer.call(search_value, &[o.into(), replace_value.clone()], context); + return replacer.call(&search_value, &[o.into(), replace_value.clone()], context); } } @@ -1125,6 +1133,7 @@ impl String { // 5. Let functionalReplace be IsCallable(replaceValue). let functional_replace = replace_value .as_object() + .as_deref() .map(JsObject::is_callable) .unwrap_or_default(); @@ -1196,7 +1205,7 @@ impl String { // i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)). context .call( - replace_value, + &replace_value, &JsValue::undefined(), &[ search_string.clone().into(), @@ -1372,7 +1381,7 @@ impl String { // b. If matcher is not undefined, then if let Some(matcher) = matcher { // i. Return ? Call(matcher, regexp, « O »). - return matcher.call(regexp, &[o.clone()], context); + return matcher.call(®exp, &[o.clone()], context); } } @@ -1380,7 +1389,7 @@ impl String { let s = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, undefined). - let rx = RegExp::create(regexp, &JsValue::Undefined, context)?; + let rx = RegExp::create(®exp, &JsValue::undefined(), context)?; // 5. Return ? Invoke(rx, @@match, « S »). rx.invoke(WellKnownSymbols::r#match(), &[JsValue::new(s)], context) @@ -1485,7 +1494,7 @@ impl String { let fill_string = args.get_or_undefined(1); // 2. Return ? StringPad(O, maxLength, fillString, end). - Self::string_pad(this, max_length, fill_string, Placement::End, context) + Self::string_pad(this, &max_length, &fill_string, Placement::End, context) } /// `String.prototype.padStart( targetLength [, padString] )` @@ -1512,7 +1521,7 @@ impl String { let fill_string = args.get_or_undefined(1); // 2. Return ? StringPad(O, maxLength, fillString, start). - Self::string_pad(this, max_length, fill_string, Placement::Start, context) + Self::string_pad(this, &max_length, &fill_string, Placement::Start, context) } /// String.prototype.trim() @@ -1676,9 +1685,11 @@ impl String { let int_start = args.get_or_undefined(0).to_integer_or_infinity(context)?; // 5. If end is undefined, let intEnd be len; else let intEnd be ? ToIntegerOrInfinity(end). - let int_end = match args.get_or_undefined(1) { - &JsValue::Undefined => IntegerOrInfinity::Integer(len), - end => end.to_integer_or_infinity(context)?, + let end = args.get_or_undefined(1); + let int_end = if end.is_undefined() { + IntegerOrInfinity::Integer(len) + } else { + end.to_integer_or_infinity(context)? }; // 6. Let finalStart be the result of clamping intStart between 0 and len. @@ -1733,9 +1744,11 @@ impl String { // 7. If length is undefined, let intLength be size; otherwise let intLength be ? ToIntegerOrInfinity(length). // Moved it before to ensure an error throws before returning the empty string on `match int_start` - let int_length = match args.get_or_undefined(1) { - &JsValue::Undefined => IntegerOrInfinity::Integer(size), - val => val.to_integer_or_infinity(context)?, + let length = args.get_or_undefined(1); + let int_length = if length.is_undefined() { + IntegerOrInfinity::Integer(size) + } else { + length.to_integer_or_infinity(context)? }; let int_start = match int_start { @@ -1798,7 +1811,7 @@ impl String { // b. If splitter is not undefined, then if let Some(splitter) = splitter { // i. Return ? Call(splitter, separator, « O, limit »). - return splitter.call(separator, &[this.clone(), limit.clone()], context); + return splitter.call(&separator, &[this.clone(), limit.clone()], context); } } @@ -1983,7 +1996,7 @@ impl String { let matcher = regexp.get_method(WellKnownSymbols::match_all(), context)?; // d. If matcher is not undefined, then if let Some(matcher) = matcher { - return matcher.call(regexp, &[o.clone()], context); + return matcher.call(®exp, &[o.clone()], context); } } @@ -1991,7 +2004,7 @@ impl String { let s = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, "g"). - let rx = RegExp::create(regexp, &JsValue::new("g"), context)?; + let rx = RegExp::create(®exp, &JsValue::new("g"), context)?; // 5. Return ? Invoke(rx, @@matchAll, « S »). rx.invoke(WellKnownSymbols::match_all(), &[JsValue::new(s)], context) @@ -2072,7 +2085,7 @@ impl String { // b. If searcher is not undefined, then if let Some(searcher) = searcher { // i. Return ? Call(searcher, regexp, « O »). - return searcher.call(regexp, &[o.clone()], context); + return searcher.call(®exp, &[o.clone()], context); } } @@ -2080,7 +2093,7 @@ impl String { let string = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, undefined). - let rx = RegExp::create(regexp, &JsValue::Undefined, context)?; + let rx = RegExp::create(®exp, &JsValue::undefined(), context)?; // 5. Return ? Invoke(rx, @@search, « string »). rx.invoke(WellKnownSymbols::search(), &[JsValue::new(string)], context) @@ -2193,7 +2206,7 @@ pub(crate) fn get_substitution( result.push(second); result.push(*third); } else if let Some(capture) = captures.get(nn - 1) { - if let Some(s) = capture.as_string() { + if let Some(ref s) = capture.as_string() { result.push_str(s); } } @@ -2214,7 +2227,7 @@ pub(crate) fn get_substitution( result.push('$'); result.push(second); } else if let Some(capture) = captures.get(n - 1) { - if let Some(s) = capture.as_string() { + if let Some(ref s) = capture.as_string() { result.push_str(s); } } @@ -2324,12 +2337,13 @@ fn split_match(s_str: &str, q: usize, r_str: &str) -> Option { /// [spec]: https://tc39.es/ecma262/#sec-isregexp fn is_reg_exp(argument: &JsValue, context: &mut Context) -> JsResult { // 1. If Type(argument) is not Object, return false. - let argument = match argument { - JsValue::Object(o) => o, - _ => return Ok(false), + let argument = if let Some(o) = argument.as_object() { + o + } else { + return Ok(false); }; - is_reg_exp_object(argument, context) + is_reg_exp_object(&argument, context) } fn is_reg_exp_object(argument: &JsObject, context: &mut Context) -> JsResult { // 2. Let matcher be ? Get(argument, @@match). diff --git a/boa_engine/src/builtins/string/string_iterator.rs b/boa_engine/src/builtins/string/string_iterator.rs index 09983f532a..1530decb8e 100644 --- a/boa_engine/src/builtins/string/string_iterator.rs +++ b/boa_engine/src/builtins/string/string_iterator.rs @@ -37,7 +37,8 @@ impl StringIterator { } pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut string_iterator = this.as_object().map(JsObject::borrow_mut); + let string_iterator = this.as_object(); + let mut string_iterator = string_iterator.as_deref().map(JsObject::borrow_mut); let string_iterator = string_iterator .as_mut() .and_then(|obj| obj.as_string_iterator_mut()) diff --git a/boa_engine/src/builtins/symbol/mod.rs b/boa_engine/src/builtins/symbol/mod.rs index 3dd58078e4..f1a85f4760 100644 --- a/boa_engine/src/builtins/symbol/mod.rs +++ b/boa_engine/src/builtins/symbol/mod.rs @@ -192,6 +192,8 @@ impl Symbol { fn this_symbol_value(value: &JsValue, context: &mut Context) -> JsResult { value .as_symbol() + .as_deref() + .cloned() .or_else(|| value.as_object().and_then(|obj| obj.borrow().as_symbol())) .ok_or_else(|| context.construct_type_error("'this' is not a Symbol")) } @@ -236,7 +238,7 @@ impl Symbol { ) -> JsResult { // 1. Return ? thisSymbolValue(this value). let symbol = Self::this_symbol_value(this, context)?; - Ok(JsValue::Symbol(symbol)) + Ok(JsValue::new(symbol)) } /// `get Symbol.prototype.description` @@ -307,14 +309,14 @@ impl Symbol { ) -> JsResult { let sym = args.get_or_undefined(0); // 1. If Type(sym) is not Symbol, throw a TypeError exception. - if let Some(sym) = sym.as_symbol() { + if let Some(ref sym) = sym.as_symbol() { // 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do // a. If SameValue(e.[[Symbol]], sym) is true, return e.[[Key]]. // 3. Assert: GlobalSymbolRegistry does not currently contain an entry for sym. // 4. Return undefined. let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| { let registry = registry.borrow(); - registry.get_symbol(&sym) + registry.get_symbol(sym) }); Ok(symbol.map(JsValue::from).unwrap_or_default()) diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 0315cf0693..5233ca7ac0 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -26,7 +26,7 @@ use crate::{ }, property::{Attribute, PropertyNameKind}, symbol::WellKnownSymbols, - value::{IntegerOrInfinity, JsValue}, + value::{IntegerOrInfinity, JsValue, JsVariant}, Context, JsResult, JsString, }; use boa_profiler::Profiler; @@ -161,7 +161,7 @@ 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, context)?; } else if first_argument.is_array_buffer() { // iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then @@ -175,8 +175,8 @@ macro_rules! typed_array { TypedArray::initialize_from_array_buffer( &o, first_argument.clone(), - byte_offset, - length, + &byte_offset, + &length, context, )?; } else { @@ -407,7 +407,9 @@ impl TypedArray { let mapping = match args.get(1) { // 3. If mapfn is undefined, let mapping be false. - None | Some(JsValue::Undefined) => None, + None => None, + Some(v) if v.is_undefined() => None, + // 4. Else, Some(v) => match v.as_object() { // b. Let mapping be true. @@ -429,11 +431,11 @@ impl TypedArray { // 6. If usingIterator is not undefined, then if let Some(using_iterator) = using_iterator { // a. Let values be ? IterableToList(source, usingIterator). - let values = iterable_to_list(context, source, Some(using_iterator.into()))?; + let values = iterable_to_list(context, &source, Some(using_iterator.into()))?; // b. Let len be the number of elements in values. // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let target_obj = Self::create(constructor, &[values.len().into()], context)?; + let target_obj = Self::create(&constructor, &[values.len().into()], context)?; // d. Let k be 0. // e. Repeat, while k < len, @@ -443,7 +445,7 @@ impl TypedArray { // iii. If mapping is true, then let mapped_value = if let Some(map_fn) = &mapping { // 1. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). - map_fn.call(this_arg, &[k_value.clone(), k.into()], context)? + map_fn.call(&this_arg, &[k_value.clone(), k.into()], context)? } // iv. Else, let mappedValue be kValue. else { @@ -469,7 +471,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, @@ -481,7 +483,7 @@ impl TypedArray { // c. If mapping is true, then let mapped_value = if let Some(map_fn) = &mapping { // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). - map_fn.call(this_arg, &[k_value, k.into()], context)? + map_fn.call(&this_arg, &[k_value, k.into()], context)? } // d. Else, let mappedValue be kValue. else { @@ -515,7 +517,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, @@ -612,7 +614,7 @@ impl TypedArray { // 5. Return buffer. Ok(typed_array .viewed_array_buffer() - .map_or_else(JsValue::undefined, |buffer| buffer.clone().into())) + .map_or(JsValue::undefined(), |buffer| buffer.clone().into())) } /// `23.2.3.3 get %TypedArray%.prototype.byteLength` @@ -906,7 +908,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { + let callback_fn = args.get_or_undefined(0); + let callback_fn = match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -925,7 +928,7 @@ impl TypedArray { // c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). let test_result = callback_fn .call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value, k.into(), this.clone()], context, )? @@ -1053,7 +1056,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { + let callback_fn = args.get_or_undefined(0); + let callback_fn = match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -1078,7 +1082,7 @@ impl TypedArray { // c. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).# let selected = callback_fn .call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value.clone(), k.into(), this.clone()], context, )? @@ -1095,7 +1099,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 @@ -1138,7 +1142,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = match args.get_or_undefined(0).as_object() { + let predicate = args.get_or_undefined(0); + let predicate = match predicate.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -1158,7 +1163,7 @@ impl TypedArray { // d. If testResult is true, return kValue. if predicate .call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value.clone(), k.into(), this.clone()], context, )? @@ -1200,8 +1205,9 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(predicate) is false, throw a TypeError exception. + let predicate = args.get_or_undefined(0); let predicate = - match args.get_or_undefined(0).as_object() { + match predicate.as_object() { Some(obj) if obj.is_callable() => obj, _ => return context.throw_type_error( "TypedArray.prototype.findindex called with non-callable predicate function", @@ -1219,7 +1225,7 @@ impl TypedArray { // d. If testResult is true, return 𝔽(k). if predicate .call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value.clone(), k.into(), this.clone()], context, )? @@ -1261,7 +1267,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { + let callback_fn = args.get_or_undefined(0); + let callback_fn = match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -1279,7 +1286,7 @@ impl TypedArray { // c. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). callback_fn.call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value, k.into(), this.clone()], context, )?; @@ -1354,7 +1361,7 @@ impl TypedArray { let element_k = obj.get(k, context).expect("Get cannot fail here"); // b. If SameValueZero(searchElement, elementK) is true, return true. - if JsValue::same_value_zero(args.get_or_undefined(0), &element_k) { + if JsValue::same_value_zero(&args.get_or_undefined(0), &element_k) { return Ok(true.into()); } @@ -1680,7 +1687,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { + let callback_fn = args.get_or_undefined(0); + let callback_fn = match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -1690,7 +1698,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, @@ -1701,7 +1709,7 @@ impl TypedArray { // c. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). let mapped_value = callback_fn.call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value, k.into(), this.clone()], context, )?; @@ -1742,7 +1750,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { + let callback_fn = args.get_or_undefined(0); + let callback_fn = match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -1823,8 +1832,9 @@ impl TypedArray { let len = o.array_length() as i64; // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = args.get_or_undefined(0); let callback_fn = - match args.get_or_undefined(0).as_object() { + match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => return context.throw_type_error( "TypedArray.prototype.reduceright called with non-callable callback function", @@ -1974,16 +1984,16 @@ impl TypedArray { } let source = args.get_or_undefined(0); - match source { + match source.variant() { // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then - JsValue::Object(source) if source.is_typed_array() => { + JsVariant::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)?; } } @@ -2380,7 +2390,7 @@ impl TypedArray { let count = std::cmp::max(r#final - k, 0) as u64; // 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() @@ -2517,7 +2527,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { + let callback_fn = args.get_or_undefined(0); + let callback_fn = match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -2537,7 +2548,7 @@ impl TypedArray { // d. If testResult is true, return true. if callback_fn .call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value, k.into(), this.clone()], context, )? @@ -2563,9 +2574,9 @@ impl TypedArray { context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - let compare_fn = match args.get(0) { - None | Some(JsValue::Undefined) => None, - Some(JsValue::Object(obj)) if obj.is_callable() => Some(obj), + let compare_fn = match args.get(0).map(JsValue::variant) { + None | Some(JsVariant::Undefined) => None, + Some(JsVariant::Object(obj)) if obj.is_callable() => Some(obj), _ => { return context .throw_type_error("TypedArray.sort called with non-callable comparefn") @@ -2649,7 +2660,7 @@ impl TypedArray { return Ok(v.partial_cmp(&0.0).unwrap_or(Ordering::Equal)); } - if let (JsValue::BigInt(x), JsValue::BigInt(y)) = (x, y) { + if let (Some(x), Some(y)) = (x.as_bigint(), y.as_bigint()) { // 6. If x < y, return -1𝔽. if x < y { return Ok(Ordering::Less); @@ -2734,7 +2745,7 @@ impl TypedArray { let mut sort_err = Ok(()); items.sort_by(|x, y| { if sort_err.is_ok() { - sort_compare(x, y, compare_fn, context).unwrap_or_else(|err| { + sort_compare(x, y, compare_fn.as_deref(), context).unwrap_or_else(|err| { sort_err = Err(err); Ordering::Equal }) @@ -2838,7 +2849,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(), @@ -2901,7 +2912,7 @@ impl TypedArray { .as_typed_array() .map(|o| o.typed_array_name().name().into()) }) - .unwrap_or(JsValue::Undefined)) + .unwrap_or(JsValue::undefined())) } /// `23.2.4.1 TypedArraySpeciesCreate ( exemplar, argumentList )` diff --git a/boa_engine/src/class.rs b/boa_engine/src/class.rs index cbbb8a2eda..750b3c180f 100644 --- a/boa_engine/src/class.rs +++ b/boa_engine/src/class.rs @@ -111,7 +111,7 @@ impl ClassConstructor for T { } let class_constructor = context.global_object().clone().get(T::NAME, context)?; - let class_constructor = if let JsValue::Object(ref obj) = class_constructor { + let class_constructor = if let Some(obj) = class_constructor.as_object() { obj } else { return context.throw_type_error(format!( @@ -120,8 +120,8 @@ impl ClassConstructor for T { )); }; let class_prototype = - if let JsValue::Object(ref obj) = class_constructor.get(PROTOTYPE, context)? { - obj.clone() + if let Some(object) = class_constructor.get(PROTOTYPE, context)?.as_object() { + object.clone() } else { return context.throw_type_error(format!( "invalid default prototype for native class `{}`", @@ -131,10 +131,10 @@ impl ClassConstructor for T { let prototype = this .as_object() - .cloned() + .as_deref() .map(|obj| { obj.get(PROTOTYPE, context) - .map(|val| val.as_object().cloned()) + .map(|val| val.as_object().as_deref().cloned()) }) .transpose()? .flatten() diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index d2d35e873a..09626cb060 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -743,7 +743,7 @@ impl Context { /// Runs all the jobs in the job queue. fn run_queued_jobs(&mut self) -> JsResult<()> { while let Some(job) = self.promise_job_queue.pop_front() { - job.call_job_callback(&JsValue::Undefined, &[], self)?; + job.call_job_callback(&JsValue::undefined(), &[], self)?; } Ok(()) } diff --git a/boa_engine/src/environments/compile.rs b/boa_engine/src/environments/compile.rs index 9bea4ba6f2..2aff17505a 100644 --- a/boa_engine/src/environments/compile.rs +++ b/boa_engine/src/environments/compile.rs @@ -300,7 +300,7 @@ impl Context { self.global_bindings_mut().insert( name_str, PropertyDescriptor::builder() - .value(JsValue::Undefined) + .value(JsValue::undefined()) .writable(true) .enumerable(true) .configurable(true) diff --git a/boa_engine/src/environments/runtime.rs b/boa_engine/src/environments/runtime.rs index 21ecf3aed0..56469c3e7f 100644 --- a/boa_engine/src/environments/runtime.rs +++ b/boa_engine/src/environments/runtime.rs @@ -402,7 +402,7 @@ impl DeclarativeEnvironmentStack { let this = if let Some(this) = this { this } else { - JsValue::Null + JsValue::null() }; self.stack.push(Gc::new(DeclarativeEnvironment { diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index 9e6e3ea26c..676d1fcc7f 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -5,6 +5,8 @@ //! - **serde** - Enables serialization and deserialization of the AST (Abstract Syntax Tree). //! - **console** - Enables `boa`'s [WHATWG `console`][whatwg] object implementation. //! - **profiler** - Enables profiling with measureme (this is mostly internal). +//! - **nan_boxing** - Enables `boa`'s nan-boxed [`JsValue`] implementation +//! (Only available on x86_64 platforms, does nothing on incompatible platforms). //! - **intl** - Enables `boa`'s [ECMA-402 Internationalization API][ecma-402] (`Intl` object) //! //! [whatwg]: https://console.spec.whatwg.org @@ -70,11 +72,11 @@ // Ignore because `write!(string, ...)` instead of `string.push_str(&format!(...))` can fail. // We only use it in `ToInternedString` where performance is not an issue. clippy::format_push_string, - // TODO deny once false positive are fixed (https://github.com/rust-lang/rust-clippy/issues/9076). - clippy::trait_duplication_in_bounds, rustdoc::missing_doc_code_examples )] +pub mod value; // In the top to give priority to `JsValue` docs + pub mod bigint; pub mod builtins; pub mod bytecompiler; @@ -88,7 +90,6 @@ pub mod realm; pub mod string; pub mod symbol; pub mod syntax; -pub mod value; pub mod vm; #[cfg(test)] diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs index c70175923d..7f8a1465e9 100644 --- a/boa_engine/src/object/internal_methods/proxy.rs +++ b/boa_engine/src/object/internal_methods/proxy.rs @@ -2,7 +2,7 @@ use crate::{ builtins::{array, object::Object}, object::{InternalObjectMethods, JsObject, JsPrototype}, property::{PropertyDescriptor, PropertyKey}, - value::Type, + value::{JsVariant, Type}, Context, JsResult, JsValue, }; use rustc_hash::FxHashSet; @@ -76,9 +76,9 @@ pub(crate) fn proxy_exotic_get_prototype_of( let handler_proto = trap.call(&handler.into(), &[target.clone().into()], context)?; // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception. - let handler_proto = match &handler_proto { - JsValue::Object(obj) => Some(obj.clone()), - JsValue::Null => None, + let handler_proto = match handler_proto.variant() { + JsVariant::Object(obj) => Some(obj.clone()), + JsVariant::Null => None, _ => return context.throw_type_error("Proxy trap result is neither object nor null"), }; @@ -138,7 +138,7 @@ pub(crate) fn proxy_exotic_set_prototype_of( &handler.into(), &[ target.clone().into(), - val.clone().map_or(JsValue::Null, Into::into), + val.clone().map_or(JsValue::null(), Into::into), ], context, )? @@ -679,7 +679,11 @@ pub(crate) fn proxy_exotic_set( if target_desc.is_accessor_descriptor() { // i. If targetDesc.[[Set]] is undefined, throw a TypeError exception. match target_desc.set() { - None | Some(&JsValue::Undefined) => { + None => { + return context + .throw_type_error("Proxy trap set unexpected accessor descriptor"); + } + Some(v) if v.is_undefined() => { return context .throw_type_error("Proxy trap set unexpected accessor descriptor"); } @@ -800,8 +804,8 @@ pub(crate) fn proxy_exotic_own_property_keys( let mut unchecked_result_keys: FxHashSet = FxHashSet::default(); let mut trap_result = Vec::new(); for value in &trap_result_raw { - match value { - JsValue::String(s) => { + match value.variant() { + JsVariant::String(s) => { if !unchecked_result_keys.insert(s.clone().into()) { return context.throw_type_error( "Proxy trap result contains duplicate string property keys", @@ -809,7 +813,7 @@ pub(crate) fn proxy_exotic_own_property_keys( } trap_result.push(s.clone().into()); } - JsValue::Symbol(s) => { + JsVariant::Symbol(s) => { if !unchecked_result_keys.insert(s.clone().into()) { return context.throw_type_error( "Proxy trap result contains duplicate symbol property keys", @@ -984,10 +988,10 @@ fn proxy_exotic_construct( )?; // 10. If Type(newObj) is not Object, throw a TypeError exception. - let new_obj = new_obj.as_object().cloned().ok_or_else(|| { + let new_obj = new_obj.as_object().ok_or_else(|| { context.construct_type_error("Proxy trap constructor returned non-object value") })?; // 11. Return newObj. - Ok(new_obj) + Ok(new_obj.clone()) } diff --git a/boa_engine/src/object/jsarray.rs b/boa_engine/src/object/jsarray.rs index ddb7fcedd5..66652afa18 100644 --- a/boa_engine/src/object/jsarray.rs +++ b/boa_engine/src/object/jsarray.rs @@ -109,6 +109,7 @@ impl JsArray { pub fn concat(&self, items: &[JsValue], context: &mut Context) -> JsResult { let object = Array::concat(&self.inner.clone().into(), items, context)? .as_object() + .as_deref() .cloned() .expect("Array.prototype.filter should always return object"); @@ -124,6 +125,7 @@ impl JsArray { ) .map(|x| { x.as_string() + .as_deref() .cloned() .expect("Array.prototype.join always returns string") }) @@ -231,6 +233,7 @@ impl JsArray { context, )? .as_object() + .as_deref() .cloned() .expect("Array.prototype.filter should always return object"); @@ -250,6 +253,7 @@ impl JsArray { context, )? .as_object() + .as_deref() .cloned() .expect("Array.prototype.map should always return object"); @@ -316,6 +320,7 @@ impl JsArray { context, )? .as_object() + .as_deref() .cloned() .expect("Array.prototype.slice should always return object"); diff --git a/boa_engine/src/object/jsarraybuffer.rs b/boa_engine/src/object/jsarraybuffer.rs index 95d6514af0..002e459d8c 100644 --- a/boa_engine/src/object/jsarraybuffer.rs +++ b/boa_engine/src/object/jsarraybuffer.rs @@ -69,7 +69,7 @@ impl JsArrayBuffer { obj.borrow_mut().data = ObjectData::array_buffer(ArrayBuffer { array_buffer_data: Some(block), array_buffer_byte_length: byte_length as u64, - array_buffer_detach_key: JsValue::Undefined, + array_buffer_detach_key: JsValue::undefined(), }); Ok(Self { inner: obj }) diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 4f1b2f4809..171d07ee9b 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -6,7 +6,7 @@ use super::{JsPrototype, NativeObject, Object, PropertyMap}; use crate::{ object::{ObjectData, ObjectKind}, property::{PropertyDescriptor, PropertyKey}, - value::PreferredType, + value::{PointerType, PreferredType}, Context, JsResult, JsValue, }; use boa_gc::{self, Finalize, Gc, Trace}; @@ -16,6 +16,7 @@ use std::{ collections::HashMap, error::Error, fmt::{self, Debug, Display}, + mem::{self, ManuallyDrop}, result::Result as StdResult, }; @@ -31,6 +32,16 @@ pub struct JsObject { inner: Gc>, } +unsafe impl PointerType for JsObject { + unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop { + ManuallyDrop::new(mem::transmute(ptr)) + } + + unsafe fn into_void_ptr(object: ManuallyDrop) -> *mut () { + mem::transmute(object) + } +} + impl JsObject { /// Create a new `JsObject` from an internal `Object`. #[inline] diff --git a/boa_engine/src/object/jsset.rs b/boa_engine/src/object/jsset.rs index 4f521a6637..663cc9b280 100644 --- a/boa_engine/src/object/jsset.rs +++ b/boa_engine/src/object/jsset.rs @@ -5,7 +5,7 @@ use boa_gc::{Finalize, Trace}; use crate::{ builtins::Set, object::{JsFunction, JsObject, JsObjectType, JsSetIterator}, - Context, JsResult, JsValue, + Context, JsResult, JsValue, value::JsVariant, }; // This is an wrapper for `JsSet` @@ -61,7 +61,7 @@ impl JsSet { /// Same as JavaScript's `set.clear()`. #[inline] pub fn clear(&self, context: &mut Context) -> JsResult { - Set::clear(&self.inner.clone().into(), &[JsValue::Null], context) + Set::clear(&self.inner.clone().into(), &[JsValue::null()], context) } /// Removes the element associated to the value. @@ -74,9 +74,9 @@ impl JsSet { where T: Into, { - match Set::delete(&self.inner.clone().into(), &[value.into()], context)? { - JsValue::Boolean(bool) => Ok(bool), - _ => Err(JsValue::Undefined), + match Set::delete(&self.inner.clone().into(), &[value.into()], context)?.variant() { + JsVariant::Boolean(bool) => Ok(bool), + _ => Err(JsValue::undefined()), } } @@ -89,9 +89,9 @@ impl JsSet { where T: Into, { - match Set::has(&self.inner.clone().into(), &[value.into()], context)? { - JsValue::Boolean(bool) => Ok(bool), - _ => Err(JsValue::Undefined), + match Set::has(&self.inner.clone().into(), &[value.into()], context)?.variant() { + JsVariant::Boolean(bool) => Ok(bool), + _ => Err(JsValue::undefined()), } } @@ -101,7 +101,7 @@ impl JsSet { /// Same as JavaScript's `set.values()`. #[inline] pub fn values(&self, context: &mut Context) -> JsResult { - let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)? + let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::null()], context)? .get_iterator(context, None, None)?; JsSetIterator::from_object(iterator_object.iterator().clone(), context) @@ -114,7 +114,7 @@ impl JsSet { /// Same as JavaScript's `set.keys()`. #[inline] pub fn keys(&self, context: &mut Context) -> JsResult { - let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)? + let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::null()], context)? .get_iterator(context, None, None)?; JsSetIterator::from_object(iterator_object.iterator().clone(), context) diff --git a/boa_engine/src/object/jsset_iterator.rs b/boa_engine/src/object/jsset_iterator.rs index 64f06ad0f8..ddd30a3ba5 100644 --- a/boa_engine/src/object/jsset_iterator.rs +++ b/boa_engine/src/object/jsset_iterator.rs @@ -26,7 +26,7 @@ impl JsSetIterator { } /// Advances the `JsSetIterator` and gets the next result in the `JsSet`. pub fn next(&self, context: &mut Context) -> JsResult { - SetIterator::next(&self.inner.clone().into(), &[JsValue::Null], context) + SetIterator::next(&self.inner.clone().into(), &[JsValue::null()], context) } } diff --git a/boa_engine/src/object/jstypedarray.rs b/boa_engine/src/object/jstypedarray.rs index 6fb5bc60bf..13e29ed512 100644 --- a/boa_engine/src/object/jstypedarray.rs +++ b/boa_engine/src/object/jstypedarray.rs @@ -10,7 +10,7 @@ use std::ops::Deref; /// JavaScript `TypedArray` rust object. #[derive(Debug, Clone, Trace, Finalize)] pub struct JsTypedArray { - inner: JsValue, + inner: JsObject, } impl JsTypedArray { @@ -20,9 +20,7 @@ impl JsTypedArray { #[inline] pub fn from_object(object: JsObject, context: &mut Context) -> JsResult { if object.borrow().is_typed_array() { - Ok(Self { - inner: object.into(), - }) + Ok(Self { inner: object }) } else { context.throw_type_error("object is not a TypedArray") } @@ -33,7 +31,7 @@ impl JsTypedArray { /// Same a `array.length` in JavaScript. #[inline] pub fn length(&self, context: &mut Context) -> JsResult { - Ok(TypedArray::length(&self.inner, &[], context)? + Ok(TypedArray::length(&self.clone().into(), &[], context)? .as_number() .map(|x| x as usize) .expect("length should return a number")) @@ -50,12 +48,12 @@ impl JsTypedArray { where T: Into, { - TypedArray::at(&self.inner, &[index.into().into()], context) + TypedArray::at(&self.clone().into(), &[index.into().into()], context) } #[inline] pub fn byte_length(&self, context: &mut Context) -> JsResult { - Ok(TypedArray::byte_length(&self.inner, &[], context)? + Ok(TypedArray::byte_length(&self.clone().into(), &[], context)? .as_number() .map(|x| x as usize) .expect("byteLength should return a number")) @@ -63,7 +61,7 @@ impl JsTypedArray { #[inline] pub fn byte_offset(&self, context: &mut Context) -> JsResult { - Ok(TypedArray::byte_offset(&self.inner, &[], context)? + Ok(TypedArray::byte_offset(&self.clone().into(), &[], context)? .as_number() .map(|x| x as usize) .expect("byteLength should return a number")) @@ -81,7 +79,7 @@ impl JsTypedArray { T: Into, { TypedArray::fill( - &self.inner, + &self.clone().into(), &[ value.into(), start.into_or_undefined(), @@ -99,7 +97,7 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { let result = TypedArray::every( - &self.inner, + &self.clone().into(), &[predicate.into(), this_arg.into_or_undefined()], context, )? @@ -117,7 +115,7 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { let result = TypedArray::some( - &self.inner, + &self.clone().into(), &[callback.into(), this_arg.into_or_undefined()], context, )? @@ -129,7 +127,11 @@ impl JsTypedArray { #[inline] pub fn sort(&self, compare_fn: Option, context: &mut Context) -> JsResult { - TypedArray::sort(&self.inner, &[compare_fn.into_or_undefined()], context)?; + TypedArray::sort( + &self.clone().into(), + &[compare_fn.into_or_undefined()], + context, + )?; Ok(self.clone()) } @@ -142,10 +144,14 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { let object = TypedArray::filter( - &self.inner, + &self.clone().into(), &[callback.into(), this_arg.into_or_undefined()], context, - )?; + )? + .as_object() + .as_deref() + .cloned() + .expect("Filter must always return an array object on success"); Ok(Self { inner: object }) } @@ -158,10 +164,14 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { let object = TypedArray::map( - &self.inner, + &self.clone().into(), &[callback.into(), this_arg.into_or_undefined()], context, - )?; + )? + .as_object() + .as_deref() + .cloned() + .expect("Filter must always return an array object on success"); Ok(Self { inner: object }) } @@ -174,7 +184,7 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { TypedArray::reduce( - &self.inner, + &self.clone().into(), &[callback.into(), initial_value.into_or_undefined()], context, ) @@ -188,7 +198,7 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { TypedArray::reduceright( - &self.inner, + &self.clone().into(), &[callback.into(), initial_value.into_or_undefined()], context, ) @@ -196,7 +206,7 @@ impl JsTypedArray { #[inline] pub fn reverse(&self, context: &mut Context) -> JsResult { - TypedArray::reverse(&self.inner, &[], context)?; + TypedArray::reverse(&self.clone().into(), &[], context)?; Ok(self.clone()) } @@ -208,10 +218,14 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { let object = TypedArray::slice( - &self.inner, + &self.clone().into(), &[start.into_or_undefined(), end.into_or_undefined()], context, - )?; + )? + .as_object() + .as_deref() + .cloned() + .expect("Filter must always return an array object on success"); Ok(Self { inner: object }) } @@ -224,7 +238,7 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { TypedArray::find( - &self.inner, + &self.clone().into(), &[predicate.into(), this_arg.into_or_undefined()], context, ) @@ -241,7 +255,7 @@ impl JsTypedArray { T: Into, { let index = TypedArray::index_of( - &self.inner, + &self.clone().into(), &[search_element.into(), from_index.into_or_undefined()], context, )? @@ -267,7 +281,7 @@ impl JsTypedArray { T: Into, { let index = TypedArray::last_index_of( - &self.inner, + &self.clone().into(), &[search_element.into(), from_index.into_or_undefined()], context, )? @@ -284,8 +298,14 @@ impl JsTypedArray { #[inline] pub fn join(&self, separator: Option, context: &mut Context) -> JsResult { - TypedArray::join(&self.inner, &[separator.into_or_undefined()], context).map(|x| { + TypedArray::join( + &self.clone().into(), + &[separator.into_or_undefined()], + context, + ) + .map(|x| { x.as_string() + .as_deref() .cloned() .expect("TypedArray.prototype.join always returns string") }) @@ -295,17 +315,14 @@ impl JsTypedArray { impl From for JsObject { #[inline] fn from(o: JsTypedArray) -> Self { - o.inner - .as_object() - .expect("should always be an object") - .clone() + o.inner.clone() } } impl From for JsValue { #[inline] fn from(o: JsTypedArray) -> Self { - o.inner.clone() + o.inner.clone().into() } } @@ -314,7 +331,7 @@ impl Deref for JsTypedArray { #[inline] fn deref(&self) -> &Self::Target { - self.inner.as_object().expect("should always be an object") + &self.inner } } @@ -392,18 +409,14 @@ macro_rules! JsTypedArrayType { impl From<$name> for JsObject { #[inline] fn from(o: $name) -> Self { - o.inner - .inner - .as_object() - .expect("should always be an object") - .clone() + o.inner.inner.clone() } } impl From<$name> for JsValue { #[inline] fn from(o: $name) -> Self { - o.inner.inner.clone() + o.inner.inner.clone().into() } } diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index 85d852edec..dc76b1744d 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -4,7 +4,7 @@ use crate::{ object::JsObject, property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, - value::Type, + value::{JsVariant, Type}, Context, JsResult, JsValue, }; @@ -594,11 +594,14 @@ impl JsObject { // 1. Assert: IsPropertyKey(P) is true. // 2. Let func be ? GetV(V, P). - match &self.__get__(&key.into(), self.clone().into(), context)? { + match self + .__get__(&key.into(), self.clone().into(), context)? + .variant() + { // 3. If func is either undefined or null, return undefined. - JsValue::Undefined | JsValue::Null => Ok(None), + JsVariant::Undefined | JsVariant::Null => Ok(None), // 5. Return func. - JsValue::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())), + JsVariant::Object(object) if object.is_callable() => Ok(Some(object.clone())), // 4. If IsCallable(func) is false, throw a TypeError exception. _ => { context.throw_type_error("value returned for property of object is not a function") @@ -833,7 +836,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_engine/src/string.rs b/boa_engine/src/string.rs index b8c76e631c..de2f5354c8 100644 --- a/boa_engine/src/string.rs +++ b/boa_engine/src/string.rs @@ -1,4 +1,4 @@ -use crate::builtins::string::is_trimmable_whitespace; +use crate::{builtins::string::is_trimmable_whitespace, value::PointerType}; use boa_gc::{unsafe_empty_trace, Finalize, Trace}; use rustc_hash::{FxHashMap, FxHasher}; use std::{ @@ -8,6 +8,7 @@ use std::{ hash::BuildHasherDefault, hash::{Hash, Hasher}, marker::PhantomData, + mem::ManuallyDrop, ops::Deref, ptr::{copy_nonoverlapping, NonNull}, rc::Rc, @@ -635,12 +636,26 @@ pub struct JsString { _marker: PhantomData>, } +unsafe impl PointerType for JsString { + unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop { + let string = Self { + inner: TaggedInner(NonNull::new_unchecked(ptr.cast())), + _marker: PhantomData, + }; + + ManuallyDrop::new(string) + } + + unsafe fn into_void_ptr(string: ManuallyDrop) -> *mut () { + string.inner.0.as_ptr().cast() + } +} + // Safety: JsString does not contain any objects which needs to be traced, // so this is safe. unsafe impl Trace for JsString { unsafe_empty_trace!(); } - /// This struct uses a technique called tagged pointer to benefit from the fact that newly /// allocated pointers are always word aligned on 64-bits platforms, making it impossible /// to have a LSB equal to 1. More details about this technique on the article of Wikipedia diff --git a/boa_engine/src/symbol.rs b/boa_engine/src/symbol.rs index bbd013978f..3c13f359c2 100644 --- a/boa_engine/src/symbol.rs +++ b/boa_engine/src/symbol.rs @@ -15,12 +15,13 @@ //! [spec]: https://tc39.es/ecma262/#sec-symbol-value //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol -use crate::JsString; +use crate::{value::PointerType, JsString}; use boa_gc::{unsafe_empty_trace, Finalize, Trace}; use std::{ cell::Cell, fmt::{self, Display}, hash::{Hash, Hasher}, + mem::{self, ManuallyDrop}, rc::Rc, }; @@ -253,12 +254,23 @@ pub struct JsSymbol { inner: Rc, } +unsafe impl PointerType for JsSymbol { + unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop { + ManuallyDrop::new(mem::transmute(ptr)) + } + + unsafe fn into_void_ptr(symbol: ManuallyDrop) -> *mut () { + mem::transmute(symbol) + } +} + // Safety: JsSymbol does not contain any objects which needs to be traced, // so this is safe. unsafe impl Trace for JsSymbol { unsafe_empty_trace!(); } + impl JsSymbol { /// Create a new symbol. #[inline] diff --git a/boa_engine/src/tests.rs b/boa_engine/src/tests.rs index 24cbb02005..0486cf89bc 100644 --- a/boa_engine/src/tests.rs +++ b/boa_engine/src/tests.rs @@ -874,6 +874,7 @@ mod in_operator { .get("prototype", &mut context) .unwrap() .as_object() + .as_deref() .cloned()) ); } @@ -1057,7 +1058,7 @@ fn to_integer_or_infinity() { fn to_length() { let mut context = Context::default(); - assert_eq!(JsValue::new(f64::NAN).to_length(&mut context).unwrap(), 0); + assert_eq!(JsValue::nan().to_length(&mut context).unwrap(), 0); assert_eq!( JsValue::new(f64::NEG_INFINITY) .to_length(&mut context) diff --git a/boa_engine/src/value/conversions.rs b/boa_engine/src/value/conversions.rs index 771acbbf8c..ae3c63e54e 100644 --- a/boa_engine/src/value/conversions.rs +++ b/boa_engine/src/value/conversions.rs @@ -1,4 +1,8 @@ -use super::{Display, JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler}; +use boa_profiler::Profiler; + +use crate::{object::JsObject, JsBigInt, JsString, JsSymbol}; + +use super::{Display, JsValue}; impl From<&Self> for JsValue { #[inline] @@ -15,7 +19,7 @@ where fn from(value: T) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::String(value.into()) + Self::string(value.into()) } } @@ -29,16 +33,7 @@ impl From for JsValue { impl From for JsValue { #[inline] fn from(value: JsSymbol) -> Self { - Self::Symbol(value) - } -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub struct TryFromCharError; - -impl Display for TryFromCharError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Could not convert value to a char type") + Self::symbol(value) } } @@ -49,7 +44,7 @@ impl From for JsValue { // if value as i32 as f64 == value { // Self::Integer(value as i32) // } else { - Self::Rational(value.into()) + Self::float64(value.into()) // } } } @@ -61,7 +56,7 @@ impl From for JsValue { // if value as i32 as f64 == value { // Self::Integer(value as i32) // } else { - Self::Rational(value) + Self::float64(value) // } } } @@ -69,28 +64,28 @@ impl From for JsValue { impl From for JsValue { #[inline] fn from(value: u8) -> Self { - Self::Integer(value.into()) + Self::integer32(value.into()) } } impl From for JsValue { #[inline] fn from(value: i8) -> Self { - Self::Integer(value.into()) + Self::integer32(value.into()) } } impl From for JsValue { #[inline] fn from(value: u16) -> Self { - Self::Integer(value.into()) + Self::integer32(value.into()) } } impl From for JsValue { #[inline] fn from(value: i16) -> Self { - Self::Integer(value.into()) + Self::integer32(value.into()) } } @@ -98,9 +93,9 @@ impl From for JsValue { #[inline] fn from(value: u32) -> Self { if let Ok(integer) = i32::try_from(value) { - Self::Integer(integer) + Self::integer32(integer) } else { - Self::Rational(value.into()) + Self::float64(value.into()) } } } @@ -108,14 +103,14 @@ impl From for JsValue { impl From for JsValue { #[inline] fn from(value: i32) -> Self { - Self::Integer(value) + Self::integer32(value) } } impl From for JsValue { #[inline] fn from(value: JsBigInt) -> Self { - Self::BigInt(value) + Self::bigint(value) } } @@ -123,9 +118,9 @@ impl From for JsValue { #[inline] fn from(value: usize) -> Self { if let Ok(value) = i32::try_from(value) { - Self::Integer(value) + Self::integer32(value) } else { - Self::Rational(value as f64) + Self::float64(value as f64) } } } @@ -134,9 +129,9 @@ impl From for JsValue { #[inline] fn from(value: u64) -> Self { if let Ok(value) = i32::try_from(value) { - Self::Integer(value) + Self::integer32(value) } else { - Self::Rational(value as f64) + Self::float64(value as f64) } } } @@ -145,9 +140,9 @@ impl From for JsValue { #[inline] fn from(value: i64) -> Self { if let Ok(value) = i32::try_from(value) { - Self::Integer(value) + Self::integer32(value) } else { - Self::Rational(value as f64) + Self::float64(value as f64) } } } @@ -155,7 +150,7 @@ impl From for JsValue { impl From for JsValue { #[inline] fn from(value: bool) -> Self { - Self::Boolean(value) + Self::boolean(value) } } @@ -163,7 +158,7 @@ impl From for JsValue { #[inline] fn from(object: JsObject) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::Object(object) + Self::object(object) } } @@ -174,6 +169,25 @@ impl From<()> for JsValue { } } +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct TryFromObjectError; + +impl Display for TryFromObjectError { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Could not convert value to an Object type") + } +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct TryFromCharError; + +impl Display for TryFromCharError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Could not convert value to a char type") + } +} + pub(crate) trait IntoOrUndefined { fn into_or_undefined(self) -> JsValue; } @@ -183,6 +197,6 @@ where T: Into, { fn into_or_undefined(self) -> JsValue { - self.map_or_else(JsValue::undefined, Into::into) + self.map_or(JsValue::undefined(), Into::into) } } diff --git a/boa_engine/src/value/display.rs b/boa_engine/src/value/display.rs index 5430cc09bb..1d7cb6d395 100644 --- a/boa_engine/src/value/display.rs +++ b/boa_engine/src/value/display.rs @@ -1,6 +1,6 @@ use crate::{object::ObjectKind, property::PropertyDescriptor}; -use super::{fmt, Display, HashSet, JsValue, PropertyKey}; +use super::{fmt, Display, HashSet, JsValue, JsVariant, PropertyKey}; /// This object is used for displaying a `Value`. #[derive(Debug, Clone, Copy)] @@ -55,7 +55,7 @@ macro_rules! print_obj_value { vec![format!( "{:>width$}: {}", "__proto__", - JsValue::Null.display(), + JsValue::null().display(), width = $indent, )] } @@ -96,9 +96,9 @@ macro_rules! print_obj_value { } pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children: bool) -> String { - match x { + match x.variant() { // We don't want to print private (compiler) or prototype properties - JsValue::Object(ref v) => { + JsVariant::Object(v) => { // Can use the private "type" field of an Object to match on // which type of Object it represents for special printing match v.borrow().kind() { @@ -196,7 +196,7 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children _ => display_obj(x, print_internals), } } - JsValue::Symbol(ref symbol) => symbol.to_string(), + JsVariant::Symbol(ref symbol) => symbol.to_string(), _ => x.display().to_string(), } } @@ -216,7 +216,7 @@ pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String { indent: usize, print_internals: bool, ) -> String { - if let JsValue::Object(ref v) = *data { + if let Some(v) = data.as_object() { // The in-memory address of the current object let addr = address_of(v.as_ref()); @@ -254,20 +254,20 @@ pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String { // in-memory address in this set let mut encounters = HashSet::new(); - if let JsValue::Object(object) = v { + if let Some(object) = v.as_object() { if object.borrow().is_error() { let name = v .get_property("name") .as_ref() .and_then(PropertyDescriptor::value) - .unwrap_or(&JsValue::Undefined) + .unwrap_or(&JsValue::undefined()) .display() .to_string(); let message = v .get_property("message") .as_ref() .and_then(PropertyDescriptor::value) - .unwrap_or(&JsValue::Undefined) + .unwrap_or(&JsValue::undefined()) .display() .to_string(); return format!("{name}: {message}"); @@ -279,21 +279,21 @@ pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String { impl Display for ValueDisplay<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.value { - JsValue::Null => write!(f, "null"), - JsValue::Undefined => write!(f, "undefined"), - JsValue::Boolean(v) => write!(f, "{v}"), - JsValue::Symbol(ref symbol) => match symbol.description() { + match self.value.variant() { + JsVariant::Null => write!(f, "null"), + JsVariant::Undefined => write!(f, "undefined"), + JsVariant::Boolean(v) => write!(f, "{v}"), + JsVariant::Symbol(symbol) => match symbol.description() { Some(description) => write!(f, "Symbol({description})"), None => write!(f, "Symbol()"), }, - JsValue::String(ref v) => write!(f, "\"{v}\""), - JsValue::Rational(v) => format_rational(*v, f), - JsValue::Object(_) => { + JsVariant::String(v) => write!(f, "\"{}\"", *v), + JsVariant::Float64(v) => format_rational(v, f), + JsVariant::Object(_) => { write!(f, "{}", log_string_from(self.value, self.internals, true)) } - JsValue::Integer(v) => write!(f, "{v}"), - JsValue::BigInt(ref num) => write!(f, "{num}n"), + JsVariant::Integer32(v) => write!(f, "{v}"), + JsVariant::BigInt(num) => write!(f, "{}n", *num), } } } diff --git a/boa_engine/src/value/equality.rs b/boa_engine/src/value/equality.rs index c7d4d65235..c4ce3787f6 100644 --- a/boa_engine/src/value/equality.rs +++ b/boa_engine/src/value/equality.rs @@ -1,4 +1,4 @@ -use super::{JsBigInt, JsObject, JsResult, JsValue, PreferredType}; +use super::{JsBigInt, JsObject, JsResult, JsValue, JsVariant, PreferredType}; use crate::{builtins::Number, Context}; impl JsValue { @@ -12,20 +12,20 @@ impl JsValue { return false; } - match (self, other) { + match (self.variant(), other.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::equal(x, y). - (Self::BigInt(x), Self::BigInt(y)) => JsBigInt::equal(x, y), - (Self::Rational(x), Self::Rational(y)) => Number::equal(*x, *y), - (Self::Rational(x), Self::Integer(y)) => Number::equal(*x, f64::from(*y)), - (Self::Integer(x), Self::Rational(y)) => Number::equal(f64::from(*x), *y), - (Self::Integer(x), Self::Integer(y)) => x == y, + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => JsBigInt::equal(x, y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::equal(x, y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::equal(x, f64::from(y)), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::equal(f64::from(x), y), + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y, //Null has to be handled specially because "typeof null" returns object and if we managed //this without a special case we would compare self and other as if they were actually //objects which unfortunately fails //Specification Link: https://tc39.es/ecma262/#sec-typeof-operator - (Self::Null, Self::Null) => true, + (JsVariant::Null, JsVariant::Null) => true, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(self, other), @@ -44,17 +44,22 @@ impl JsValue { return Ok(self.strict_equals(other)); } - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // 2. If x is null and y is undefined, return true. // 3. If x is undefined and y is null, return true. - (Self::Null, Self::Undefined) | (Self::Undefined, Self::Null) => true, + (JsVariant::Null, JsVariant::Undefined) | (JsVariant::Undefined, JsVariant::Null) => { + true + } // 3. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y). // 4. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y. // // https://github.com/rust-lang/rust/issues/54883 - (Self::Integer(_) | Self::Rational(_), Self::String(_) | Self::Boolean(_)) - | (Self::String(_), Self::Integer(_) | Self::Rational(_)) => { + ( + JsVariant::Integer32(_) | JsVariant::Float64(_), + JsVariant::String(_) | JsVariant::Boolean(_), + ) + | (JsVariant::String(_), JsVariant::Integer32(_) | JsVariant::Float64(_)) => { let x = self.to_number(context)?; let y = other.to_number(context)?; Number::equal(x, y) @@ -64,32 +69,32 @@ impl JsValue { // a. Let n be ! StringToBigInt(y). // b. If n is NaN, return false. // c. Return the result of the comparison x == n. - (Self::BigInt(ref a), Self::String(ref b)) => match JsBigInt::from_string(b) { - Some(ref b) => a == b, + (JsVariant::BigInt(a), JsVariant::String(ref b)) => match JsBigInt::from_string(b) { + Some(b) => *a == b, None => false, }, // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x. - (Self::String(ref a), Self::BigInt(ref b)) => match JsBigInt::from_string(a) { - Some(ref a) => a == b, + (JsVariant::String(ref a), JsVariant::BigInt(b)) => match JsBigInt::from_string(a) { + Some(a) => a == *b, None => false, }, // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. - (Self::Boolean(x), _) => return other.equals(&Self::new(i32::from(*x)), context), + (JsVariant::Boolean(x), _) => return other.equals(&Self::new(i32::from(x)), context), // 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y). - (_, Self::Boolean(y)) => return self.equals(&Self::new(i32::from(*y)), context), + (_, JsVariant::Boolean(y)) => return self.equals(&Self::new(i32::from(y)), context), // 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result // of the comparison x == ? ToPrimitive(y). ( - Self::Object(_), - Self::String(_) - | Self::Rational(_) - | Self::Integer(_) - | Self::BigInt(_) - | Self::Symbol(_), + JsVariant::Object(_), + JsVariant::String(_) + | JsVariant::Float64(_) + | JsVariant::Integer32(_) + | JsVariant::BigInt(_) + | JsVariant::Symbol(_), ) => { let primitive = self.to_primitive(context, PreferredType::Default)?; return Ok(primitive @@ -100,12 +105,12 @@ impl JsValue { // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result // of the comparison ? ToPrimitive(x) == y. ( - Self::String(_) - | Self::Rational(_) - | Self::Integer(_) - | Self::BigInt(_) - | Self::Symbol(_), - Self::Object(_), + JsVariant::String(_) + | JsVariant::Float64(_) + | JsVariant::Integer32(_) + | JsVariant::BigInt(_) + | JsVariant::Symbol(_), + JsVariant::Object(_), ) => { let primitive = other.to_primitive(context, PreferredType::Default)?; return Ok(primitive @@ -116,10 +121,10 @@ impl JsValue { // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then // a. If x or y are any of NaN, +∞, or -∞, return false. // b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false. - (Self::BigInt(ref a), Self::Rational(ref b)) => a == b, - (Self::Rational(ref a), Self::BigInt(ref b)) => a == b, - (Self::BigInt(ref a), Self::Integer(ref b)) => a == b, - (Self::Integer(ref a), Self::BigInt(ref b)) => a == b, + (JsVariant::BigInt(a), JsVariant::Float64(b)) => *a == b, + (JsVariant::Float64(a), JsVariant::BigInt(b)) => a == *b, + (JsVariant::BigInt(a), JsVariant::Integer32(b)) => *a == b, + (JsVariant::Integer32(a), JsVariant::BigInt(b)) => a == *b, // 13. Return false. _ => false, @@ -139,14 +144,14 @@ impl JsValue { return false; } - match (x, y) { + match (x.variant(), y.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::SameValue(x, y). - (Self::BigInt(x), Self::BigInt(y)) => JsBigInt::same_value(x, y), - (Self::Rational(x), Self::Rational(y)) => Number::same_value(*x, *y), - (Self::Rational(x), Self::Integer(y)) => Number::same_value(*x, f64::from(*y)), - (Self::Integer(x), Self::Rational(y)) => Number::same_value(f64::from(*x), *y), - (Self::Integer(x), Self::Integer(y)) => x == y, + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => JsBigInt::same_value(x, y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::same_value(x, y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::same_value(x, f64::from(y)), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::same_value(f64::from(x), y), + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(x, y), @@ -167,19 +172,19 @@ impl JsValue { return false; } - match (x, y) { + match (x.variant(), y.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::SameValueZero(x, y). - (JsValue::BigInt(x), JsValue::BigInt(y)) => JsBigInt::same_value_zero(x, y), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => JsBigInt::same_value_zero(x, y), - (JsValue::Rational(x), JsValue::Rational(y)) => Number::same_value_zero(*x, *y), - (JsValue::Rational(x), JsValue::Integer(y)) => { - Number::same_value_zero(*x, f64::from(*y)) + (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::same_value_zero(x, y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Number::same_value_zero(x, f64::from(y)) } - (JsValue::Integer(x), JsValue::Rational(y)) => { - Number::same_value_zero(f64::from(*x), *y) + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + Number::same_value_zero(f64::from(x), y) } - (JsValue::Integer(x), JsValue::Integer(y)) => x == y, + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(x, y), @@ -188,12 +193,14 @@ impl JsValue { fn same_value_non_numeric(x: &Self, y: &Self) -> bool { debug_assert!(x.get_type() == y.get_type()); - match (x, y) { - (Self::Null, Self::Null) | (Self::Undefined, Self::Undefined) => true, - (Self::String(ref x), Self::String(ref y)) => x == y, - (Self::Boolean(x), Self::Boolean(y)) => x == y, - (Self::Object(ref x), Self::Object(ref y)) => JsObject::equals(x, y), - (Self::Symbol(ref x), Self::Symbol(ref y)) => x == y, + match (x.variant(), y.variant()) { + (JsVariant::Null, JsVariant::Null) | (JsVariant::Undefined, JsVariant::Undefined) => { + true + } + (JsVariant::String(x), JsVariant::String(y)) => x == y, + (JsVariant::Boolean(x), JsVariant::Boolean(y)) => x == y, + (JsVariant::Object(ref x), JsVariant::Object(ref y)) => JsObject::equals(x, y), + (JsVariant::Symbol(x), JsVariant::Symbol(y)) => x == y, _ => false, } } diff --git a/boa_engine/src/value/hash.rs b/boa_engine/src/value/hash.rs index 4498eca035..e6024c75cb 100644 --- a/boa_engine/src/value/hash.rs +++ b/boa_engine/src/value/hash.rs @@ -1,4 +1,4 @@ -use super::JsValue; +use super::{JsValue, JsVariant}; use crate::builtins::Number; use std::hash::{Hash, Hasher}; @@ -37,16 +37,16 @@ impl Hash for RationalHashable { impl Hash for JsValue { fn hash(&self, state: &mut H) { - match self { - Self::Undefined => UndefinedHashable.hash(state), - Self::Null => NullHashable.hash(state), - Self::String(ref string) => string.hash(state), - Self::Boolean(boolean) => boolean.hash(state), - Self::Integer(integer) => RationalHashable(f64::from(*integer)).hash(state), - Self::BigInt(ref bigint) => bigint.hash(state), - Self::Rational(rational) => RationalHashable(*rational).hash(state), - Self::Symbol(ref symbol) => Hash::hash(symbol, state), - Self::Object(ref object) => std::ptr::hash(object.as_ref(), state), + match self.variant() { + JsVariant::Undefined => UndefinedHashable.hash(state), + JsVariant::Null => NullHashable.hash(state), + JsVariant::String(string) => string.hash(state), + JsVariant::Boolean(boolean) => boolean.hash(state), + JsVariant::Integer32(integer) => RationalHashable(f64::from(integer)).hash(state), + JsVariant::BigInt(bigint) => bigint.hash(state), + JsVariant::Float64(rational) => RationalHashable(rational).hash(state), + JsVariant::Symbol(symbol) => Hash::hash(&*symbol, state), + JsVariant::Object(object) => std::ptr::hash(object.as_ref(), state), } } } diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index d7798c4312..fae8a3a1ca 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -1,6 +1,27 @@ -//! This module implements the JavaScript Value. +#![warn(unsafe_op_in_unsafe_fn)] +//! This module implements the JavaScript [`JsValue`] type. //! -//! Javascript values, utility methods and conversion between Javascript values and Rust values. +//! [`JsValue`] implements several utility methods and conversions between Javascript +//! values and Rust values. +//! +//! # Notes +//! +//! We recommend using [`JsValue::from`], [`JsValue::new`] or the [`Into::into`] +//! trait if you need to convert from float types ([`f64`], [`f32`]) to [`JsValue`], +//! since there are some checks implemented that convert float values to signed +//! integer values when there's no loss of precision involved. +//! +//! Alternatively, you can use the [`JsValue::float64`] method if you do need a pure +//! [`f64`] value. The constructor skips all conversions from [`f64`]s to +//! [`i32`]s, even if the value can be represented by an [`i32`] without loss of +//! precision. +//! +//! # Alternative implementations +//! +//! `boa` right now implements two versions of [`JsValue`]: +//! - The default implementation using a simple `enum`. +//! - A NaN-boxed implementation for x86-64 platforms that can be enabled using the `nan_boxing` feature. +//! The feature flag is ignored on incompatible platforms. #[cfg(test)] mod tests; @@ -12,10 +33,9 @@ use crate::{ }, object::{JsObject, ObjectData}, property::{PropertyDescriptor, PropertyKey}, - symbol::{JsSymbol, WellKnownSymbols}, + symbol::WellKnownSymbols, Context, JsBigInt, JsResult, JsString, }; -use boa_gc::{custom_trace, Finalize, Trace}; use boa_profiler::Profiler; use num_bigint::BigInt; use num_integer::Integer; @@ -28,6 +48,11 @@ use std::{ str::FromStr, }; +mod sys; + +#[doc(inline)] +pub use sys::{JsValue, JsVariant, Ref}; + mod conversions; pub(crate) mod display; mod equality; @@ -44,6 +69,7 @@ pub use hash::*; pub use integer::IntegerOrInfinity; pub use operations::*; pub use r#type::Type; +pub(crate) use sys::PointerType; static TWO_E_64: Lazy = Lazy::new(|| { const TWO_E_64: u128 = 2u128.pow(64); @@ -55,37 +81,6 @@ static TWO_E_63: Lazy = Lazy::new(|| { BigInt::from(TWO_E_63) }); -/// A Javascript value -#[derive(Finalize, Debug, Clone)] -pub enum JsValue { - /// `null` - A null value, for when a value doesn't exist. - Null, - /// `undefined` - An undefined value, for when a field or index doesn't exist. - Undefined, - /// `boolean` - A `true` / `false` value, for if a certain criteria is met. - Boolean(bool), - /// `String` - A UTF-8 string, such as `"Hello, world"`. - String(JsString), - /// `Number` - A 64-bit floating point number, such as `3.1415` - Rational(f64), - /// `Number` - A 32-bit integer, such as `42`. - Integer(i32), - /// `BigInt` - holds any arbitrary large signed integer. - BigInt(JsBigInt), - /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values. - Object(JsObject), - /// `Symbol` - A Symbol Primitive type. - Symbol(JsSymbol), -} - -unsafe impl Trace for JsValue { - custom_trace! {this, { - if let Self::Object(o) = this { - mark(o); - } - }} -} - impl JsValue { /// Create a new [`JsValue`]. #[inline] @@ -96,48 +91,28 @@ impl JsValue { value.into() } - /// Creates a new `undefined` value. - #[inline] - pub fn undefined() -> Self { - Self::Undefined - } - - /// Creates a new `null` value. + /// Returns true if the value is null or undefined. #[inline] - pub fn null() -> Self { - Self::Null + pub fn is_null_or_undefined(&self) -> bool { + self.is_null() || self.is_undefined() } /// Creates a new number with `NaN` value. #[inline] pub fn nan() -> Self { - Self::Rational(f64::NAN) + Self::new(f64::NAN) } /// Creates a new number with `Infinity` value. #[inline] pub fn positive_infinity() -> Self { - Self::Rational(f64::INFINITY) + Self::new(f64::INFINITY) } /// Creates a new number with `-Infinity` value. #[inline] pub fn negative_infinity() -> Self { - Self::Rational(f64::NEG_INFINITY) - } - - /// Returns true if the value is an object - #[inline] - pub fn is_object(&self) -> bool { - matches!(self, Self::Object(_)) - } - - #[inline] - pub fn as_object(&self) -> Option<&JsObject> { - match *self { - Self::Object(ref o) => Some(o), - _ => None, - } + Self::new(f64::NEG_INFINITY) } /// It determines if the value is a callable function with a `[[Call]]` internal method. @@ -148,143 +123,68 @@ impl JsValue { /// [spec]: https://tc39.es/ecma262/#sec-iscallable #[inline] pub fn is_callable(&self) -> bool { - matches!(self, Self::Object(obj) if obj.is_callable()) + self.as_object() + .as_deref() + .map_or(false, JsObject::is_callable) } #[inline] - pub fn as_callable(&self) -> Option<&JsObject> { + pub fn as_callable(&self) -> Option> { 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()) + self.as_object() + .as_deref() + .map_or(false, JsObject::is_constructor) } #[inline] - pub fn as_constructor(&self) -> Option<&JsObject> { + pub fn as_constructor(&self) -> Option> { self.as_object().filter(|obj| obj.is_constructor()) } /// Returns true if the value is a promise object. #[inline] pub fn is_promise(&self) -> bool { - matches!(self, Self::Object(obj) if obj.is_promise()) + self.as_object() + .as_deref() + .map_or(false, JsObject::is_promise) } #[inline] - pub fn as_promise(&self) -> Option<&JsObject> { + pub fn as_promise(&self) -> Option> { self.as_object().filter(|obj| obj.is_promise()) } - /// Returns true if the value is a symbol. - #[inline] - pub fn is_symbol(&self) -> bool { - matches!(self, Self::Symbol(_)) - } - - pub fn as_symbol(&self) -> Option { - match self { - Self::Symbol(symbol) => Some(symbol.clone()), - _ => None, - } - } - - /// Returns true if the value is undefined. - #[inline] - pub fn is_undefined(&self) -> bool { - matches!(self, Self::Undefined) - } - - /// Returns true if the value is null. - #[inline] - pub fn is_null(&self) -> bool { - matches!(self, Self::Null) - } - - /// Returns true if the value is null or undefined. - #[inline] - pub fn is_null_or_undefined(&self) -> bool { - matches!(self, Self::Null | Self::Undefined) - } - - /// Returns true if the value is a 64-bit floating-point number. - #[inline] - pub fn is_double(&self) -> bool { - matches!(self, Self::Rational(_)) - } - - /// Returns true if the value is integer. + /// Returns true if the value is an integer, even if it's represented by an [`f64`]. #[inline] #[allow(clippy::float_cmp)] pub fn is_integer(&self) -> bool { - // If it can fit in a i32 and the truncated version is - // equal to the original then it is an integer. - let is_rational_integer = |n: f64| n == f64::from(n as i32); - - match *self { - Self::Integer(_) => true, - Self::Rational(n) if is_rational_integer(n) => true, - _ => false, + if self.is_integer32() { + return true; } + + self.as_float64() + // If it can fit in a i32 and the trucated version is + // equal to the original then it is an integer. + .map(|num| num == f64::from(num as i32)) + .unwrap_or_default() } /// Returns true if the value is a number. #[inline] pub fn is_number(&self) -> bool { - matches!(self, Self::Rational(_) | Self::Integer(_)) + self.is_integer32() || self.is_float64() } #[inline] pub fn as_number(&self) -> Option { - match *self { - Self::Integer(integer) => Some(integer.into()), - Self::Rational(rational) => Some(rational), - _ => None, - } - } - - /// Returns true if the value is a string. - #[inline] - pub fn is_string(&self) -> bool { - matches!(self, Self::String(_)) - } - - /// Returns the string if the values is a string, otherwise `None`. - #[inline] - pub fn as_string(&self) -> Option<&JsString> { - match self { - Self::String(ref string) => Some(string), - _ => None, - } - } - - /// Returns true if the value is a boolean. - #[inline] - pub fn is_boolean(&self) -> bool { - matches!(self, Self::Boolean(_)) - } - - #[inline] - pub fn as_boolean(&self) -> Option { - match self { - Self::Boolean(boolean) => Some(*boolean), - _ => None, - } - } - - /// Returns true if the value is a bigint. - #[inline] - pub fn is_bigint(&self) -> bool { - matches!(self, Self::BigInt(_)) - } - - /// Returns an optional reference to a `BigInt` if the value is a `BigInt` primitive. - #[inline] - pub fn as_bigint(&self) -> Option<&JsBigInt> { - match self { - Self::BigInt(bigint) => Some(bigint), + match self.variant() { + JsVariant::Integer32(integer) => Some(integer.into()), + JsVariant::Float64(rational) => Some(rational), _ => None, } } @@ -296,13 +196,13 @@ impl JsValue { /// /// [spec]: https://tc39.es/ecma262/#sec-toboolean pub fn to_boolean(&self) -> bool { - match *self { - Self::Symbol(_) | Self::Object(_) => true, - Self::String(ref s) if !s.is_empty() => true, - Self::Rational(n) if n != 0.0 && !n.is_nan() => true, - Self::Integer(n) if n != 0 => true, - Self::BigInt(ref n) if !n.is_zero() => true, - Self::Boolean(v) => v, + match self.variant() { + JsVariant::Symbol(_) | JsVariant::Object(_) => true, + JsVariant::String(s) if !s.is_empty() => true, + JsVariant::Float64(n) if n != 0.0 && !n.is_nan() => true, + JsVariant::Integer32(n) if n != 0 => true, + JsVariant::BigInt(n) if !n.is_zero() => true, + JsVariant::Boolean(v) => v, _ => false, } } @@ -316,28 +216,27 @@ impl JsValue { { let key = key.into(); let _timer = Profiler::global().start_event("Value::get_property", "value"); - match self { - Self::Object(ref object) => { - // TODO: had to skip `__get_own_properties__` since we don't have context here - let property = object.borrow().properties().get(&key); - if property.is_some() { - return property; - } - - object - .prototype() - .as_ref() - .map_or(Self::Null, |obj| obj.clone().into()) - .get_property(key) + if let Some(object) = self.as_object() { + // TODO: had to skip `__get_own_properties__` since we don't have context here + let property = object.borrow().properties().get(&key); + if property.is_some() { + return property; } - _ => None, + + object + .prototype() + .as_ref() + .map_or(Self::null(), |obj| obj.clone().into()) + .get_property(key) + } else { + None } } /// Set the kind of an object. #[inline] pub fn set_data(&self, data: ObjectData) { - if let Self::Object(ref obj) = *self { + if let Some(obj) = self.as_object() { obj.borrow_mut().data = data; } } @@ -352,9 +251,9 @@ impl JsValue { ) -> JsResult { // 1. Assert: input is an ECMAScript language value. (always a value not need to check) // 2. If Type(input) is Object, then - if self.is_object() { + if let Some(object) = self.as_object() { // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive). - let exotic_to_prim = self.get_method(WellKnownSymbols::to_primitive(), context)?; + let exotic_to_prim = object.get_method(WellKnownSymbols::to_primitive(), context)?; // b. If exoticToPrim is not undefined, then if let Some(exotic_to_prim) = exotic_to_prim { @@ -388,9 +287,7 @@ impl JsValue { }; // d. Return ? OrdinaryToPrimitive(input, preferredType). - self.as_object() - .expect("self was not an object") - .ordinary_to_primitive(context, preferred_type) + object.ordinary_to_primitive(context, preferred_type) } else { // 3. Return input. Ok(self.clone()) @@ -404,10 +301,13 @@ impl JsValue { /// /// [spec]: https://tc39.es/ecma262/#sec-tobigint pub fn to_bigint(&self, context: &mut Context) -> JsResult { - match self { - Self::Null => context.throw_type_error("cannot convert null to a BigInt"), - Self::Undefined => context.throw_type_error("cannot convert undefined to a BigInt"), - Self::String(ref string) => { + match self.variant() { + JsVariant::Null => context.throw_type_error("cannot convert null to a BigInt"), + JsVariant::Undefined => { + context.throw_type_error("cannot convert undefined to a BigInt") + } + JsVariant::String(string) => { + let string = &*string; if let Some(value) = JsBigInt::from_string(string) { Ok(value) } else { @@ -416,17 +316,17 @@ impl JsValue { )) } } - Self::Boolean(true) => Ok(JsBigInt::one()), - Self::Boolean(false) => Ok(JsBigInt::zero()), - Self::Integer(_) | Self::Rational(_) => { + JsVariant::Boolean(true) => Ok(JsBigInt::one()), + JsVariant::Boolean(false) => Ok(JsBigInt::zero()), + JsVariant::Integer32(_) | JsVariant::Float64(_) => { context.throw_type_error("cannot convert Number to a BigInt") } - Self::BigInt(b) => Ok(b.clone()), - Self::Object(_) => { + JsVariant::BigInt(b) => Ok(b.clone()), + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_bigint(context) } - Self::Symbol(_) => context.throw_type_error("cannot convert Symbol to a BigInt"), + JsVariant::Symbol(_) => context.throw_type_error("cannot convert Symbol to a BigInt"), } } @@ -456,16 +356,16 @@ impl JsValue { /// /// This function is equivalent to `String(value)` in JavaScript. pub fn to_string(&self, context: &mut Context) -> JsResult { - match self { - Self::Null => Ok("null".into()), - Self::Undefined => Ok("undefined".into()), - Self::Boolean(boolean) => Ok(boolean.to_string().into()), - Self::Rational(rational) => Ok(Number::to_native_string(*rational).into()), - Self::Integer(integer) => Ok(integer.to_string().into()), - Self::String(string) => Ok(string.clone()), - Self::Symbol(_) => context.throw_type_error("can't convert symbol to string"), - Self::BigInt(ref bigint) => Ok(bigint.to_string().into()), - Self::Object(_) => { + match self.variant() { + JsVariant::Null => Ok("null".into()), + JsVariant::Undefined => Ok("undefined".into()), + JsVariant::Boolean(boolean) => Ok(boolean.to_string().into()), + JsVariant::Float64(rational) => Ok(Number::to_native_string(rational).into()), + JsVariant::Integer32(integer) => Ok(integer.to_string().into()), + JsVariant::String(string) => Ok(string.clone()), + JsVariant::Symbol(_) => context.throw_type_error("can't convert symbol to string"), + JsVariant::BigInt(bigint) => Ok(bigint.to_string().into()), + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::String)?; primitive.to_string(context) } @@ -478,32 +378,32 @@ impl JsValue { /// /// See: pub fn to_object(&self, context: &mut Context) -> JsResult { - match self { - Self::Undefined | Self::Null => { + match self.variant() { + JsVariant::Undefined | JsVariant::Null => { context.throw_type_error("cannot convert 'null' or 'undefined' to object") } - Self::Boolean(boolean) => { + JsVariant::Boolean(boolean) => { let prototype = context.intrinsics().constructors().boolean().prototype(); Ok(JsObject::from_proto_and_data( prototype, - ObjectData::boolean(*boolean), + ObjectData::boolean(boolean), )) } - Self::Integer(integer) => { + JsVariant::Integer32(integer) => { let prototype = context.intrinsics().constructors().number().prototype(); Ok(JsObject::from_proto_and_data( prototype, - ObjectData::number(f64::from(*integer)), + ObjectData::number(f64::from(integer)), )) } - Self::Rational(rational) => { + JsVariant::Float64(rational) => { let prototype = context.intrinsics().constructors().number().prototype(); Ok(JsObject::from_proto_and_data( prototype, - ObjectData::number(*rational), + ObjectData::number(rational), )) } - Self::String(ref string) => { + JsVariant::String(string) => { let prototype = context.intrinsics().constructors().string().prototype(); let object = @@ -519,14 +419,14 @@ impl JsValue { ); Ok(object) } - Self::Symbol(ref symbol) => { + JsVariant::Symbol(symbol) => { let prototype = context.intrinsics().constructors().symbol().prototype(); Ok(JsObject::from_proto_and_data( prototype, ObjectData::symbol(symbol.clone()), )) } - Self::BigInt(ref bigint) => { + JsVariant::BigInt(bigint) => { let prototype = context .intrinsics() .constructors() @@ -537,7 +437,7 @@ impl JsValue { ObjectData::big_int(bigint.clone()), )) } - Self::Object(jsobject) => Ok(jsobject.clone()), + JsVariant::Object(jsobject) => Ok(jsobject.clone()), } } @@ -545,19 +445,22 @@ impl JsValue { /// /// See pub fn to_property_key(&self, context: &mut Context) -> JsResult { - Ok(match self { + Ok(match self.variant() { // Fast path: - Self::String(string) => string.clone().into(), - Self::Symbol(symbol) => symbol.clone().into(), - Self::Integer(integer) => (*integer).into(), + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), + JsVariant::Integer32(integer) => integer.into(), // Slow path: - Self::Object(_) => match self.to_primitive(context, PreferredType::String)? { - Self::String(ref string) => string.clone().into(), - Self::Symbol(ref symbol) => symbol.clone().into(), - Self::Integer(integer) => integer.into(), - primitive => primitive.to_string(context)?.into(), - }, - primitive => primitive.to_string(context)?.into(), + JsVariant::Object(_) => { + let primitive = self.to_primitive(context, PreferredType::String)?; + match primitive.variant() { + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), + JsVariant::Integer32(integer) => integer.into(), + _ => primitive.to_string(context)?.into(), + } + }, + _ => self.to_string(context)?.into(), }) } @@ -579,7 +482,7 @@ impl JsValue { /// See: pub fn to_u32(&self, context: &mut Context) -> JsResult { // This is the fast path, if the value is Integer we can just return it. - if let JsValue::Integer(number) = *self { + if let Some(number) = self.as_integer32() { return Ok(number as u32); } let number = self.to_number(context)?; @@ -592,7 +495,7 @@ impl JsValue { /// See: pub fn to_i32(&self, context: &mut Context) -> JsResult { // This is the fast path, if the value is Integer we can just return it. - if let JsValue::Integer(number) = *self { + if let Some(number) = self.as_integer32() { return Ok(number); } let number = self.to_number(context)?; @@ -873,16 +776,16 @@ impl JsValue { /// /// See: pub fn to_number(&self, context: &mut Context) -> JsResult { - match *self { - Self::Null => Ok(0.0), - Self::Undefined => Ok(f64::NAN), - Self::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), - Self::String(ref string) => Ok(string.string_to_number()), - Self::Rational(number) => Ok(number), - Self::Integer(integer) => Ok(f64::from(integer)), - Self::Symbol(_) => context.throw_type_error("argument must not be a symbol"), - Self::BigInt(_) => context.throw_type_error("argument must not be a bigint"), - Self::Object(_) => { + match self.variant() { + JsVariant::Null => Ok(0.0), + JsVariant::Undefined => Ok(f64::NAN), + JsVariant::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), + JsVariant::String(string) => Ok(string.string_to_number()), + JsVariant::Float64(number) => Ok(number), + JsVariant::Integer32(integer) => Ok(f64::from(integer)), + JsVariant::Symbol(_) => context.throw_type_error("argument must not be a symbol"), + JsVariant::BigInt(_) => context.throw_type_error("argument must not be a bigint"), + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_number(context) } @@ -942,15 +845,15 @@ impl JsValue { /// /// [spec]: https://tc39.es/ecma262/#sec-typeof-operator pub fn type_of(&self) -> JsString { - match *self { - Self::Rational(_) | Self::Integer(_) => "number", - Self::String(_) => "string", - Self::Boolean(_) => "boolean", - Self::Symbol(_) => "symbol", - Self::Null => "object", - Self::Undefined => "undefined", - Self::BigInt(_) => "bigint", - Self::Object(ref object) => { + match self.variant() { + JsVariant::Float64(_) | JsVariant::Integer32(_) => "number", + JsVariant::String(_) => "string", + JsVariant::Boolean(_) => "boolean", + JsVariant::Symbol(_) => "symbol", + JsVariant::Null => "object", + JsVariant::Undefined => "undefined", + JsVariant::BigInt(_) => "bigint", + JsVariant::Object(object) => { if object.is_callable() { "function" } else { @@ -986,7 +889,7 @@ impl JsValue { impl Default for JsValue { fn default() -> Self { - Self::Undefined + Self::undefined() } } diff --git a/boa_engine/src/value/operations.rs b/boa_engine/src/value/operations.rs index 4e45f6790e..238e25397c 100644 --- a/boa_engine/src/value/operations.rs +++ b/boa_engine/src/value/operations.rs @@ -1,5 +1,5 @@ use super::{ - Context, FromStr, JsBigInt, JsResult, JsString, JsValue, Numeric, PreferredType, + Context, FromStr, JsBigInt, JsResult, JsString, JsValue, JsVariant, Numeric, PreferredType, WellKnownSymbols, }; use crate::builtins::number::{f64_to_int32, f64_to_uint32, Number}; @@ -7,60 +7,63 @@ use crate::builtins::number::{f64_to_int32, f64_to_uint32, Number}; impl JsValue { #[inline] pub fn add(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: // Numeric add - (Self::Integer(x), Self::Integer(y)) => x - .checked_add(*y) - .map_or_else(|| Self::new(f64::from(*x) + f64::from(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x + y), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) + y), - (Self::Rational(x), Self::Integer(y)) => Self::new(x + f64::from(*y)), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::add(x, y)), + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x + .checked_add(y) + .map_or_else(|| Self::new(f64::from(x) + f64::from(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x + y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) + y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x + f64::from(y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::add(&*x, &*y)), // String concat - (Self::String(ref x), Self::String(ref y)) => Self::from(JsString::concat(x, y)), - (Self::String(ref x), y) => Self::from(JsString::concat(x, y.to_string(context)?)), - (x, Self::String(ref y)) => Self::from(JsString::concat(x.to_string(context)?, y)), + (JsVariant::String(x), JsVariant::String(y)) => Self::from(JsString::concat(&*x, &*y)), + (JsVariant::String(x), _) => { + Self::new(JsString::concat(&*x, other.to_string(context)?)) + } + (_, JsVariant::String(y)) => Self::new(JsString::concat(self.to_string(context)?, &*y)), // Slow path: - (_, _) => match ( - self.to_primitive(context, PreferredType::Default)?, - other.to_primitive(context, PreferredType::Default)?, - ) { - (Self::String(ref x), ref y) => { - Self::from(JsString::concat(x, y.to_string(context)?)) - } - (ref x, Self::String(ref y)) => { - Self::from(JsString::concat(x.to_string(context)?, y)) - } - (x, y) => match (x.to_numeric(context)?, y.to_numeric(context)?) { - (Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y), - (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => { - Self::new(JsBigInt::add(x, y)) + (_, _) => { + let x = self.to_primitive(context, PreferredType::Default)?; + let y = other.to_primitive(context, PreferredType::Default)?; + match (x.variant(), y.variant()) { + (JsVariant::String(x), _) => { + Self::from(JsString::concat(&*x, y.to_string(context)?)) } - (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ) + (_, JsVariant::String(y)) => { + Self::from(JsString::concat(x.to_string(context)?, &*y)) } - }, - }, + (_, _) => match (x.to_numeric(context)?, y.to_numeric(context)?) { + (Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y), + (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => { + Self::new(JsBigInt::add(x, y)) + } + (_, _) => { + return context.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ) + } + }, + } + } }) } #[inline] pub fn sub(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => x - .checked_sub(*y) - .map_or_else(|| Self::new(f64::from(*x) - f64::from(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x - y), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) - y), - (Self::Rational(x), Self::Integer(y)) => Self::new(x - f64::from(*y)), + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x + .checked_sub(y) + .map_or_else(|| Self::new(f64::from(x) - f64::from(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x - y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) - y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x - f64::from(y)), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -77,16 +80,16 @@ impl JsValue { #[inline] pub fn mul(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => x - .checked_mul(*y) - .map_or_else(|| Self::new(f64::from(*x) * f64::from(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x * y), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) * y), - (Self::Rational(x), Self::Integer(y)) => Self::new(x * f64::from(*y)), + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x + .checked_mul(y) + .map_or_else(|| Self::new(f64::from(x) * f64::from(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x * y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) * y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x * f64::from(y)), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -103,17 +106,17 @@ impl JsValue { #[inline] pub fn div(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => x - .checked_div(*y) - .filter(|div| *y * div == *x) - .map_or_else(|| Self::new(f64::from(*x) / f64::from(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x / y), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) / y), - (Self::Rational(x), Self::Integer(y)) => Self::new(x / f64::from(*y)), - - (Self::BigInt(ref x), Self::BigInt(ref y)) => { + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x + .checked_div(y) + .filter(|div| y * div == x) + .map_or_else(|| Self::new(f64::from(x) / f64::from(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x / y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) / y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x / f64::from(y)), + + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => { if y.is_zero() { return context.throw_range_error("BigInt division by zero"); } @@ -140,33 +143,32 @@ impl JsValue { #[inline] pub fn rem(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => { - if *y == 0 { - Self::nan() + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => { + if y == 0 { + Self::from(f64::NAN) } else { - match x % *y { - rem if rem == 0 && *x < 0 => Self::new(-0.0), + match x % y { + rem if rem == 0 && x < 0 => Self::new(-0.0), rem => Self::new(rem), } } } - (Self::Rational(x), Self::Rational(y)) => Self::new((x % y).copysign(*x)), - (Self::Integer(x), Self::Rational(y)) => { - let x = f64::from(*x); + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new((x % y).copysign(x)), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + let x = f64::from(x); Self::new((x % y).copysign(x)) } - - (Self::Rational(x), Self::Integer(y)) => Self::new((x % f64::from(*y)).copysign(*x)), - - (Self::BigInt(ref x), Self::BigInt(ref y)) => { + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Self::new((x % f64::from(y)).copysign(x)) + } + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => { if y.is_zero() { return context.throw_range_error("BigInt division by zero"); } Self::new(JsBigInt::rem(x, y)) } - // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { (Numeric::Number(a), Numeric::Number(b)) => Self::new(a % b), @@ -187,17 +189,19 @@ impl JsValue { #[inline] pub fn pow(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => u32::try_from(*y) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => u32::try_from(y) .ok() .and_then(|y| x.checked_pow(y)) - .map_or_else(|| Self::new(f64::from(*x).powi(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x.powf(*y)), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x).powf(*y)), - (Self::Rational(x), Self::Integer(y)) => Self::new(x.powi(*y)), + .map_or_else(|| Self::new(f64::from(x).powi(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x.powf(y)), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x).powf(y)), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x.powi(y)), - (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::pow(a, b, context)?), + (JsVariant::BigInt(ref a), JsVariant::BigInt(ref b)) => { + Self::new(JsBigInt::pow(a, b, context)?) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -216,16 +220,18 @@ impl JsValue { #[inline] pub fn bitand(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x & y), - (Self::Rational(x), Self::Rational(y)) => { - Self::new(f64_to_int32(*x) & f64_to_int32(*y)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x & y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x) & f64_to_int32(y)) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x & f64_to_int32(*y)), - (Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) & y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x & f64_to_int32(y)), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) & y), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitand(x, y)), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => { + Self::new(JsBigInt::bitand(x, y)) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -246,16 +252,18 @@ impl JsValue { #[inline] pub fn bitor(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x | y), - (Self::Rational(x), Self::Rational(y)) => { - Self::new(f64_to_int32(*x) | f64_to_int32(*y)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x | y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x) | f64_to_int32(y)) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x | f64_to_int32(*y)), - (Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) | y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x | f64_to_int32(y)), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) | y), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitor(x, y)), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => { + Self::new(JsBigInt::bitor(x, y)) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -276,16 +284,18 @@ impl JsValue { #[inline] pub fn bitxor(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x ^ y), - (Self::Rational(x), Self::Rational(y)) => { - Self::new(f64_to_int32(*x) ^ f64_to_int32(*y)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x ^ y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x) ^ f64_to_int32(y)) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x ^ f64_to_int32(*y)), - (Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) ^ y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x ^ f64_to_int32(y)), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) ^ y), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitxor(x, y)), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => { + Self::new(JsBigInt::bitxor(x, y)) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -306,18 +316,22 @@ impl JsValue { #[inline] pub fn shl(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shl(*y as u32)), - (Self::Rational(x), Self::Rational(y)) => { - Self::new(f64_to_int32(*x).wrapping_shl(f64_to_uint32(*y))) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => { + Self::new(x.wrapping_shl(y as u32)) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shl(f64_to_uint32(*y))), - (Self::Rational(x), Self::Integer(y)) => { - Self::new(f64_to_int32(*x).wrapping_shl(*y as u32)) + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x).wrapping_shl(f64_to_uint32(y))) + } + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + Self::new(x.wrapping_shl(f64_to_uint32(y))) + } + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Self::new(f64_to_int32(x).wrapping_shl(y as u32)) } - (Self::BigInt(ref a), Self::BigInt(ref b)) => { + (JsVariant::BigInt(ref a), JsVariant::BigInt(ref b)) => { Self::new(JsBigInt::shift_left(a, b, context)?) } @@ -340,18 +354,22 @@ impl JsValue { #[inline] pub fn shr(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shr(*y as u32)), - (Self::Rational(x), Self::Rational(y)) => { - Self::new(f64_to_int32(*x).wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => { + Self::new(x.wrapping_shr(y as u32)) + } + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x).wrapping_shr(f64_to_uint32(y))) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shr(f64_to_uint32(*y))), - (Self::Rational(x), Self::Integer(y)) => { - Self::new(f64_to_int32(*x).wrapping_shr(*y as u32)) + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + Self::new(x.wrapping_shr(f64_to_uint32(y))) + } + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Self::new(f64_to_int32(x).wrapping_shr(y as u32)) } - (Self::BigInt(ref a), Self::BigInt(ref b)) => { + (JsVariant::BigInt(ref a), JsVariant::BigInt(ref b)) => { Self::new(JsBigInt::shift_right(a, b, context)?) } @@ -374,17 +392,19 @@ impl JsValue { #[inline] pub fn ushr(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new((*x as u32).wrapping_shr(*y as u32)), - (Self::Rational(x), Self::Rational(y)) => { - Self::new(f64_to_uint32(*x).wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => { + Self::new((x as u32).wrapping_shr(y as u32)) + } + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y))) } - (Self::Integer(x), Self::Rational(y)) => { - Self::new((*x as u32).wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + Self::new((x as u32).wrapping_shr(f64_to_uint32(y))) } - (Self::Rational(x), Self::Integer(y)) => { - Self::new(f64_to_uint32(*x).wrapping_shr(*y as u32)) + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Self::new(f64_to_uint32(x).wrapping_shr(y as u32)) } // Slow path: @@ -443,22 +463,22 @@ impl JsValue { #[inline] pub fn neg(&self, context: &mut Context) -> JsResult { - Ok(match *self { - Self::Symbol(_) | Self::Undefined => Self::new(f64::NAN), - Self::Object(_) => Self::new(match self.to_numeric_number(context) { + Ok(match self.variant() { + JsVariant::Symbol(_) | JsVariant::Undefined => Self::new(f64::NAN), + JsVariant::Object(_) => Self::new(match self.to_numeric_number(context) { Ok(num) => -num, Err(_) => f64::NAN, }), - Self::String(ref str) => Self::new(match f64::from_str(str) { + JsVariant::String(ref str) => Self::new(match f64::from_str(str) { Ok(num) => -num, Err(_) => f64::NAN, }), - Self::Rational(num) => Self::new(-num), - Self::Integer(num) if num == 0 => Self::new(-f64::from(0)), - Self::Integer(num) => Self::new(-num), - Self::Boolean(true) => Self::new(1), - Self::Boolean(false) | Self::Null => Self::new(0), - Self::BigInt(ref x) => Self::new(JsBigInt::neg(x)), + JsVariant::Float64(num) => Self::new(-num), + JsVariant::Integer32(num) if num == 0 => Self::new(-f64::from(0)), + JsVariant::Integer32(num) => Self::new(-num), + JsVariant::Boolean(true) => Self::new(1), + JsVariant::Boolean(false) | JsVariant::Null => Self::new(0), + JsVariant::BigInt(ref x) => Self::new(JsBigInt::neg(x)), }) } @@ -490,13 +510,13 @@ impl JsValue { left_first: bool, context: &mut Context, ) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path (for some common operations): - (Self::Integer(x), Self::Integer(y)) => (x < y).into(), - (Self::Integer(x), Self::Rational(y)) => Number::less_than(f64::from(*x), *y), - (Self::Rational(x), Self::Integer(y)) => Number::less_than(*x, f64::from(*y)), - (Self::Rational(x), Self::Rational(y)) => Number::less_than(*x, *y), - (Self::BigInt(ref x), Self::BigInt(ref y)) => (x < y).into(), + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => (x < y).into(), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::less_than(f64::from(x), y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::less_than(x, f64::from(y)), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::less_than(x, y), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => (x < y).into(), // Slow path: (_, _) => { @@ -511,8 +531,8 @@ impl JsValue { (px, py) }; - match (px, py) { - (Self::String(ref x), Self::String(ref y)) => { + match (px.variant(), py.variant()) { + (JsVariant::String(x), JsVariant::String(y)) => { if x.starts_with(y.as_str()) { return Ok(AbstractRelation::False); } @@ -526,21 +546,21 @@ impl JsValue { } unreachable!() } - (Self::BigInt(ref x), Self::String(ref y)) => { + (JsVariant::BigInt(x), JsVariant::String(ref y)) => { if let Some(y) = JsBigInt::from_string(y) { (*x < y).into() } else { AbstractRelation::Undefined } } - (Self::String(ref x), Self::BigInt(ref y)) => { + (JsVariant::String(ref x), JsVariant::BigInt(y)) => { if let Some(x) = JsBigInt::from_string(x) { (x < *y).into() } else { AbstractRelation::Undefined } } - (px, py) => match (px.to_numeric(context)?, py.to_numeric(context)?) { + (_, _) => match (px.to_numeric(context)?, py.to_numeric(context)?) { (Numeric::Number(x), Numeric::Number(y)) => Number::less_than(x, y), (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => (x < y).into(), (Numeric::BigInt(ref x), Numeric::Number(y)) => { diff --git a/boa_engine/src/value/serde_json.rs b/boa_engine/src/value/serde_json.rs index 645e7ae361..79f303453c 100644 --- a/boa_engine/src/value/serde_json.rs +++ b/boa_engine/src/value/serde_json.rs @@ -1,6 +1,6 @@ //! This module implements the conversions from and into [`serde_json::Value`]. -use super::JsValue; +use super::{JsValue, JsVariant}; use crate::{ builtins::Array, property::{PropertyDescriptor, PropertyKey}, @@ -41,13 +41,13 @@ impl JsValue { const MIN_INT: i64 = i32::MIN as i64; match json { - Value::Null => Ok(Self::Null), - Value::Bool(b) => Ok(Self::Boolean(*b)), + Value::Null => Ok(Self::null()), + Value::Bool(b) => Ok(Self::from(*b)), Value::Number(num) => num .as_i64() .filter(|n| (MIN_INT..=MAX_INT).contains(n)) - .map(|i| Self::Integer(i as i32)) - .or_else(|| num.as_f64().map(Self::Rational)) + .map(|i| Self::from(i as i32)) + .or_else(|| num.as_f64().map(Self::from)) .ok_or_else(|| { context.construct_type_error(format!( "could not convert JSON number {num} to JsValue" @@ -104,15 +104,15 @@ impl JsValue { /// # assert_eq!(json, back_to_json); /// ``` pub fn to_json(&self, context: &mut Context) -> JsResult { - match self { - Self::Null => Ok(Value::Null), - Self::Undefined => todo!("undefined to JSON"), - &Self::Boolean(b) => Ok(b.into()), - Self::String(string) => Ok(string.as_str().into()), - &Self::Rational(rat) => Ok(rat.into()), - &Self::Integer(int) => Ok(int.into()), - Self::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"), - Self::Object(obj) => { + match self.variant() { + JsVariant::Null => Ok(Value::Null), + JsVariant::Undefined => todo!("undefined to JSON"), + JsVariant::Boolean(b) => Ok(b.into()), + JsVariant::String(string) => Ok(string.as_str().into()), + JsVariant::Float64(rat) => Ok(rat.into()), + JsVariant::Integer32(int) => Ok(int.into()), + JsVariant::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"), + JsVariant::Object(obj) => { if obj.is_array() { let len = obj.length_of_array_like(context)?; let mut arr = Vec::with_capacity(len as usize); @@ -120,9 +120,12 @@ impl JsValue { let obj = obj.borrow(); for k in 0..len as u32 { - let val = obj.properties().get(&k.into()).map_or(Self::Null, |desc| { - desc.value().cloned().unwrap_or(Self::Null) - }); + let val = obj + .properties() + .get(&k.into()) + .map_or(Self::null(), |desc| { + desc.value().cloned().unwrap_or(Self::null()) + }); arr.push(val.to_json(context)?); } @@ -149,7 +152,7 @@ impl JsValue { Ok(Value::Object(map)) } } - Self::Symbol(_sym) => context.throw_type_error("cannot convert Symbol to JSON"), + JsVariant::Symbol(_sym) => context.throw_type_error("cannot convert Symbol to JSON"), } } } diff --git a/boa_engine/src/value/sys/default.rs b/boa_engine/src/value/sys/default.rs new file mode 100644 index 0000000000..3dd72466b8 --- /dev/null +++ b/boa_engine/src/value/sys/default.rs @@ -0,0 +1,290 @@ +use boa_gc::{custom_trace, Finalize, Trace}; + +use super::JsVariant; + +use crate::{object::JsObject, JsBigInt, JsString, JsSymbol}; + +#[derive(Finalize, Debug, Clone)] +enum Value { + Null, + Undefined, + Boolean(bool), + Integer32(i32), + Float64(f64), + String(JsString), + BigInt(JsBigInt), + Symbol(JsSymbol), + Object(JsObject), +} + +#[allow(unsafe_op_in_unsafe_fn)] +unsafe impl Trace for Value { + custom_trace! {this, { + if let Self::Object(o) = this { + mark(o); + } + }} +} + +/// A Javascript value +/// +/// Check the [`value`][`super::super`] module for more information. +#[derive(Trace, Finalize, Debug, Clone)] +pub struct JsValue(Value); + +impl JsValue { + /// `null` - A null value, for when a value doesn't exist. + #[inline] + pub const fn null() -> Self { + Self(Value::Null) + } + + /// `undefined` - An undefined value, for when a field or index doesn't exist + #[inline] + pub const fn undefined() -> Self { + Self(Value::Undefined) + } + + /// `boolean` - A `true` / `false` value. + #[inline] + pub fn boolean(boolean: bool) -> Self { + Self(Value::Boolean(boolean)) + } + + /// `integer32` - A 32-bit integer value, such as `42`. + #[inline] + pub fn integer32(integer: i32) -> Self { + Self(Value::Integer32(integer)) + } + + /// `float64` - A 64-bit floating point number value, such as `3.1415` + #[inline] + pub fn float64(float64: f64) -> Self { + Self(Value::Float64(float64)) + } + + /// `String` - A [`JsString`] value, such as `"Hello, world"`. + #[inline] + pub fn string(string: JsString) -> Self { + Self(Value::String(string)) + } + + /// `BigInt` - A [`JsBigInt`] value, an arbitrarily large signed integer. + #[inline] + pub fn bigint(bigint: JsBigInt) -> Self { + Self(Value::BigInt(bigint)) + } + + /// `Symbol` - A [`JsSymbol`] value. + #[inline] + pub fn symbol(symbol: JsSymbol) -> Self { + Self(Value::Symbol(symbol)) + } + + /// `Object` - A [`JsObject`], such as `Math`, represented by a binary tree of string keys to Javascript values. + #[inline] + pub fn object(object: JsObject) -> Self { + Self(Value::Object(object)) + } + + /// Returns the internal [`bool`] if the value is a boolean, or + /// [`None`] otherwise. + #[inline] + pub fn as_boolean(&self) -> Option { + match self.0 { + Value::Boolean(boolean) => Some(boolean), + _ => None, + } + } + + /// Returns the internal [`i32`] if the value is a 32-bit signed integer number, or + /// [`None`] otherwise. + #[inline] + pub fn as_integer32(&self) -> Option { + match self.0 { + Value::Integer32(integer) => Some(integer), + _ => None, + } + } + + /// Returns the internal [`f64`] if the value is a 64-bit floating-point number, or + /// [`None`] otherwise. + #[inline] + pub fn as_float64(&self) -> Option { + match self.0 { + Value::Float64(rational) => Some(rational), + _ => None, + } + } + + /// Returns a reference to the internal [`JsString`] if the value is a string, or + /// [`None`] otherwise. + #[inline] + pub fn as_string(&self) -> Option> { + match self.0 { + Value::String(ref inner) => Some(Ref { inner }), + _ => None, + } + } + + /// Returns a reference to the internal [`JsBigInt`] if the value is a big int, or + /// [`None`] otherwise. + #[inline] + pub fn as_bigint(&self) -> Option> { + match self.0 { + Value::BigInt(ref inner) => Some(Ref { inner }), + _ => None, + } + } + + /// Returns a reference to the internal [`JsSymbol`] if the value is a symbol, or + /// [`None`] otherwise. + #[inline] + pub fn as_symbol(&self) -> Option> { + match self.0 { + Value::Symbol(ref inner) => Some(Ref { inner }), + _ => None, + } + } + + /// Returns a reference to the internal [`JsObject`] if the value is an object, or + /// [`None`] otherwise. + #[inline] + pub fn as_object(&self) -> Option> { + match self.0 { + Value::Object(ref inner) => Some(Ref { inner }), + _ => None, + } + } + + /// Returns true if the value is null. + #[inline] + pub fn is_null(&self) -> bool { + matches!(self.0, Value::Null) + } + + /// Returns true if the value is undefined. + #[inline] + pub fn is_undefined(&self) -> bool { + matches!(self.0, Value::Undefined) + } + + /// Returns true if the value is a boolean. + #[inline] + pub fn is_boolean(&self) -> bool { + matches!(self.0, Value::Boolean(_)) + } + + /// Returns true if the value is a 32-bit signed integer number. + #[inline] + pub fn is_integer32(&self) -> bool { + matches!(self.0, Value::Integer32(_)) + } + + /// Returns true if the value is a 64-bit floating-point number. + #[inline] + pub fn is_float64(&self) -> bool { + matches!(self.0, Value::Float64(_)) + } + + /// Returns true if the value is a 64-bit floating-point `NaN` number. + #[inline] + pub fn is_nan(&self) -> bool { + matches!(self.0, Value::Float64(r) if r.is_nan()) + } + + /// Returns true if the value is a string. + #[inline] + pub fn is_string(&self) -> bool { + matches!(self.0, Value::String(_)) + } + + /// Returns true if the value is a bigint. + #[inline] + pub fn is_bigint(&self) -> bool { + matches!(self.0, Value::BigInt(_)) + } + + /// Returns true if the value is a symbol. + #[inline] + pub fn is_symbol(&self) -> bool { + matches!(self.0, Value::Symbol(_)) + } + + /// Returns true if the value is an object + #[inline] + pub fn is_object(&self) -> bool { + matches!(self.0, Value::Object(_)) + } + + /// Returns a [`JsVariant`] enum representing the current variant of the value. + /// + /// # Note + /// + /// More exotic implementations of [`JsValue`] cannot use direct references to + /// heap based types, so [`JsVariant`] instead returns [`Ref`]s on those cases. + pub fn variant(&self) -> JsVariant<'_> { + match self.0 { + Value::Null => JsVariant::Null, + Value::Undefined => JsVariant::Undefined, + Value::Integer32(i) => JsVariant::Integer32(i), + Value::Float64(d) => JsVariant::Float64(d), + Value::Boolean(b) => JsVariant::Boolean(b), + Value::Object(ref inner) => JsVariant::Object(Ref { inner }), + Value::String(ref inner) => JsVariant::String(Ref { inner }), + Value::Symbol(ref inner) => JsVariant::Symbol(Ref { inner }), + Value::BigInt(ref inner) => JsVariant::BigInt(Ref { inner }), + } + } +} + +/// Represents a reference to a boxed pointer type inside a [`JsValue`] +/// +/// This is exclusively used to return references to [`JsString`], [`JsObject`], +/// [`JsSymbol`] and [`JsBigInt`], since some [`JsValue`] implementations makes +/// returning proper references difficult. +/// It is mainly returned by the [`JsValue::variant`] method and the +/// `as_` methods for checked casts to pointer types. +/// +/// [`Ref`] implements [`Deref`][`std::ops::Deref`], which facilitates conversion +/// to a proper [`reference`] by using the `ref` keyword or the +/// [`Option::as_deref`][`std::option::Option::as_deref`] method. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Ref<'a, T> { + inner: &'a T, +} + +// Lift `Ref` over `AsRef`, since implementing `AsRef` would override the +// `as_ref` implementations of `T`. +impl AsRef for Ref<'_, T> +where + T: AsRef, +{ + #[inline] + fn as_ref(&self) -> &U { + >::as_ref(self.inner) + } +} + +impl std::ops::Deref for Ref<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + self.inner + } +} + +impl PartialEq for Ref<'_, T> { + #[inline] + fn eq(&self, other: &T) -> bool { + self.inner == other + } +} + +impl std::borrow::Borrow for Ref<'_, T> { + #[inline] + fn borrow(&self) -> &T { + self.inner + } +} diff --git a/boa_engine/src/value/sys/mod.rs b/boa_engine/src/value/sys/mod.rs new file mode 100644 index 0000000000..a657724012 --- /dev/null +++ b/boa_engine/src/value/sys/mod.rs @@ -0,0 +1,99 @@ +//! This module contains various implementations of the [`JsValue`] type, and +//! type redefinitions to select the requested [`JsValue`] implementation at +//! compile time, using features. + +use crate::{object::JsObject, JsBigInt, JsString, JsSymbol}; +use std::mem::ManuallyDrop; + +// Minimum required definition for a correct `JsValue` implementation: + +// pub const fn null() -> Self; +// pub const fn undefined() -> Self; +// pub fn boolean(bool) -> Self; +// pub fn integer(i32) -> Self; +// pub fn rational(f64) -> Self; +// pub const fn nan() -> Self; +// pub fn string(JsString) -> Self; +// pub fn bigint(JsBigInt) -> Self; +// pub fn symbol(JsSymbol) -> Self; +// pub fn object(JsObject) -> Self; + +// pub fn as_boolean(&self) -> Option; +// pub fn as_integer(&self) -> Option; +// pub fn as_rational(&self) -> Option; +// pub fn as_string(&self) -> Option>; +// pub fn as_bigint(&self) -> Option>; +// pub fn as_symbol(&self) -> Option>; +// pub fn as_object(&self) -> Option>; + +// pub fn is_null(&self) -> bool; +// pub fn is_undefined(&self) -> bool; +// pub fn is_boolean(&self) -> bool; +// pub fn is_integer(&self) -> bool; +// pub fn is_rational(&self) -> bool; +// pub fn is_nan(&self) -> bool; +// pub fn is_string(&self) -> bool; +// pub fn is_bigint(&self) -> bool; +// pub fn is_symbol(&self) -> bool; +// pub fn is_object(&self) -> bool; + +// pub fn variant(&self) -> JsVariant<'_>; + +// Ref<'a, T> type + +cfg_if::cfg_if! { + if #[cfg(all(feature = "nan_boxing", not(doc)))] { + cfg_if::cfg_if! { + if #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] { + + #[path = "nan_boxed.rs"] + mod r#impl; + + } else { + compile_error!("This platform doesn't support NaN-boxing."); + } + } + } else { + #[path = "default.rs"] + mod r#impl; + } +} + +pub use r#impl::*; + +/// Return value of the [`JsValue::variant`] method. +/// +/// Represents either a primitive value ([`bool`], [`f64`], [`i32`]) or a reference +/// to a heap allocated value ([`JsString`], [`JsSymbol`]). +/// +/// References to heap allocated values are represented by [`Ref`], since +/// more exotic implementations of [`JsValue`] such as nan-boxed ones cannot +/// effectively return references. +#[derive(Debug)] +pub enum JsVariant<'a> { + Null, + Undefined, + Boolean(bool), + Integer32(i32), + Float64(f64), + String(Ref<'a, JsString>), + BigInt(Ref<'a, JsBigInt>), + Symbol(Ref<'a, JsSymbol>), + Object(Ref<'a, JsObject>), +} + +/// This abstracts over every pointer type and the required conversions +/// for some of the [`JsValue`] implementations. +/// +/// # Safety +/// +/// Non-exhaustive list of situations that could cause undefined behaviour: +/// - Returning an invalid `*mut ()`. +/// - Returning a `ManuallyDrop` that doesn't correspond with the provided +/// `ptr`. +/// - Dropping `ty` before returning its pointer. +pub(crate) unsafe trait PointerType { + unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop; + + unsafe fn into_void_ptr(ty: ManuallyDrop) -> *mut (); +} diff --git a/boa_engine/src/value/sys/nan_boxed.rs b/boa_engine/src/value/sys/nan_boxed.rs new file mode 100644 index 0000000000..45221a1bf1 --- /dev/null +++ b/boa_engine/src/value/sys/nan_boxed.rs @@ -0,0 +1,760 @@ +#![allow(unstable_name_collisions)] + +use num_enum::TryFromPrimitive; +use std::cell::Cell; +use std::marker::PhantomData; +use std::mem::ManuallyDrop; + +use boa_gc::{Finalize, Trace}; + +// TODO: Remove if/when https://github.com/rust-lang/rust/issues/95228 gets stabilized +use sptr::Strict; + +use super::JsVariant; + +use crate::{object::JsObject, value::PointerType, JsBigInt, JsString, JsSymbol}; + +// Our `cfg` options must ensure `usize == u64`. +// Using `usize`s only makes it more convenient to use in this module + +const SIGN_BIT: usize = 0x8000_0000_0000_0000; +const EXPONENT: usize = 0x7FF0_0000_0000_0000; +// const MANTISA: usize = 0x000F_FFFF_FFFF_FFFF; +const SIGNAL_BIT: usize = 0x0008_0000_0000_0000; +const QNAN: usize = EXPONENT | SIGNAL_BIT; // 0x7FF8000000000000 + +const CANONICALIZED_NAN: usize = QNAN; +// const PAYLOAD: usize = 0x0000_7FFF_FFFF_FFFF; +// const TYPE: usize = !PAYLOAD; + +const TAG_MASK: usize = 0xFFFF_0000_0000_0000; + +const DOUBLE_TYPE: usize = QNAN; +const INTEGER_TYPE: usize = QNAN | (0b001 << 48); +const BOOLEAN_TYPE: usize = QNAN | (0b010 << 48); +const UNDEFINED_TYPE: usize = QNAN | (0b011 << 48); +const NULL_TYPE: usize = QNAN | (0b100 << 48); + +#[allow(unused)] +const RESERVED1_TYPE: usize = QNAN | (0b101 << 48); +#[allow(unused)] +const RESERVED2_TYPE: usize = QNAN | (0b110 << 48); +#[allow(unused)] +const RESERVED3_TYPE: usize = QNAN | (0b111 << 48); + +const POINTER_TYPE: usize = SIGN_BIT | QNAN; +const OBJECT_TYPE: usize = POINTER_TYPE | (0b001 << 48); +const STRING_TYPE: usize = POINTER_TYPE | (0b010 << 48); +const SYMBOL_TYPE: usize = POINTER_TYPE | (0b011 << 48); +const BIGINT_TYPE: usize = POINTER_TYPE | (0b100 << 48); + +#[allow(unused)] +const RESERVED4_TYPE: usize = POINTER_TYPE | (0b101 << 48); +#[allow(unused)] +const RESERVED5_TYPE: usize = POINTER_TYPE | (0b110 << 48); +#[allow(unused)] +const RESERVED6_TYPE: usize = POINTER_TYPE | (0b111 << 48); + +const MASK_INT_PAYLOAD: usize = 0x00000000FFFFFFFF; +const MASK_POINTER_PAYLOAD: usize = 0x0000FFFFFFFFFFFF; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] +#[repr(u16)] +enum ValueTag { + Float64 = (DOUBLE_TYPE >> 48) as _, + Integer32 = (INTEGER_TYPE >> 48) as _, + Boolean = (BOOLEAN_TYPE >> 48) as _, + Undefined = (UNDEFINED_TYPE >> 48) as _, + Null = (NULL_TYPE >> 48) as _, + Object = (OBJECT_TYPE >> 48) as _, + String = (STRING_TYPE >> 48) as _, + Symbol = (SYMBOL_TYPE >> 48) as _, + BigInt = (BIGINT_TYPE >> 48) as _, + // Reserved1 = RESERVED1_TYPE, + // Reserved2 = RESERVED2_TYPE, + // Reserved3 = RESERVED3_TYPE, + // Reserved4 = RESERVED4_TYPE, + // Reserved5 = RESERVED5_TYPE, + // Reserved6 = RESERVED6_TYPE, +} + +/// A Javascript value +/// +/// Check the [`value`][`super::super`] module for more information. +#[derive(Debug)] +#[repr(transparent)] +pub struct JsValue { + value: Cell<*mut ()>, +} + +impl JsValue { + /// `null` - A null value, for when a value doesn't exist. + #[inline] + pub const fn null() -> Self { + Self { + value: Cell::new(sptr::invalid_mut(NULL_TYPE)), + } + } + + /// `undefined` - An undefined value, for when a field or index doesn't exist + #[inline] + pub const fn undefined() -> Self { + Self { + value: Cell::new(sptr::invalid_mut(UNDEFINED_TYPE)), + } + } + + /// `boolean` - A `true` / `false` value. + #[inline] + pub fn boolean(boolean: bool) -> Self { + let value = Self { + value: Cell::new(sptr::invalid_mut(usize::from(boolean) | BOOLEAN_TYPE)), + }; + debug_assert!(value.is_boolean()); + debug_assert_eq!(value.tag(), ValueTag::Boolean); + value + } + + /// `integer32` - A 32-bit integer value, such as `42`. + #[inline] + pub fn integer32(integer32: i32) -> Self { + let value = Self { + value: Cell::new(sptr::invalid_mut(integer32 as u32 as usize | INTEGER_TYPE)), + }; + debug_assert!(value.is_integer32()); + debug_assert_eq!(value.tag(), ValueTag::Integer32); + value + } + + /// `float64` - A 64-bit floating point number value, such as `3.1415` + #[inline] + pub fn float64(float64: f64) -> Self { + if float64.is_nan() { + return Self { + value: Cell::new(sptr::invalid_mut(CANONICALIZED_NAN)), + }; + } + + let value = Self { + value: Cell::new(sptr::invalid_mut(float64.to_bits() as usize)), + }; + debug_assert!(value.is_float64()); + debug_assert_eq!(value.tag(), ValueTag::Float64); + value + } + + /// `String` - A [`JsString`] value, such as `"Hello, world"`. + #[inline] + pub fn string(string: JsString) -> Self { + let string = ManuallyDrop::new(string); + let pointer = unsafe { JsString::into_void_ptr(string) }; + debug_assert_eq!(pointer.addr() & MASK_POINTER_PAYLOAD, pointer.addr()); + let value = Self { + value: Cell::new(pointer.map_addr(|addr| addr | STRING_TYPE)), + }; + debug_assert!(value.is_string()); + debug_assert_eq!(value.tag(), ValueTag::String); + value + } + + /// `BigInt` - A [`JsBigInt`] value, an arbitrarily large signed integer. + #[inline] + pub fn bigint(bigint: JsBigInt) -> Self { + let bigint = ManuallyDrop::new(bigint); + let pointer = unsafe { JsBigInt::into_void_ptr(bigint) }; + debug_assert_eq!(pointer.addr() & MASK_POINTER_PAYLOAD, pointer.addr()); + let value = Self { + value: Cell::new(pointer.map_addr(|addr| addr | BIGINT_TYPE)), + }; + debug_assert!(value.is_bigint()); + debug_assert_eq!(value.tag(), ValueTag::BigInt); + value + } + + /// `Symbol` - A [`JsSymbol`] value. + #[inline] + pub fn symbol(symbol: JsSymbol) -> Self { + let symbol = ManuallyDrop::new(symbol); + let pointer = unsafe { JsSymbol::into_void_ptr(symbol) }; + debug_assert_eq!(pointer.addr() & MASK_POINTER_PAYLOAD, pointer.addr()); + let value = Self { + value: Cell::new(pointer.map_addr(|addr| addr | SYMBOL_TYPE)), + }; + debug_assert!(value.is_symbol()); + debug_assert_eq!(value.tag(), ValueTag::Symbol); + value + } + + /// `Object` - A [`JsObject`], such as `Math`, represented by a binary tree of string keys to Javascript values. + #[inline] + pub fn object(object: JsObject) -> Self { + let object = ManuallyDrop::new(object); + let pointer = unsafe { JsObject::into_void_ptr(object) }; + debug_assert_eq!(pointer.addr() & MASK_POINTER_PAYLOAD, pointer.addr()); + debug_assert_eq!(OBJECT_TYPE & MASK_POINTER_PAYLOAD, 0); + let value = Self { + value: Cell::new(pointer.map_addr(|addr| addr | OBJECT_TYPE)), + }; + debug_assert!(value.is_object()); + debug_assert_eq!(value.tag(), ValueTag::Object); + value + } + + /// Returns the internal [`bool`] if the value is a boolean, or + /// [`None`] otherwise. + #[inline] + pub fn as_boolean(&self) -> Option { + if self.is_boolean() { + return Some(self.as_boolean_uncheched()); + } + + None + } + + /// Returns the internal [`i32`] if the value is a 32-bit signed integer number, or + /// [`None`] otherwise. + pub fn as_integer32(&self) -> Option { + if self.is_integer32() { + return Some(self.as_integer32_uncheched()); + } + + None + } + + /// Returns the internal [`f64`] if the value is a 64-bit floating-point number, or + /// [`None`] otherwise. + pub fn as_float64(&self) -> Option { + if self.is_float64() { + return Some(self.as_float64_unchecked()); + } + + None + } + + /// Returns a reference to the internal [`JsString`] if the value is a string, or + /// [`None`] otherwise. + #[inline] + pub fn as_string(&self) -> Option> { + if self.is_string() { + return unsafe { Some(self.as_string_unchecked()) }; + } + + None + } + + /// Returns a reference to the internal [`JsBigInt`] if the value is a big int, or + /// [`None`] otherwise. + pub fn as_bigint(&self) -> Option> { + if self.is_bigint() { + return unsafe { Some(self.as_bigint_unchecked()) }; + } + + None + } + + /// Returns a reference to the internal [`JsSymbol`] if the value is a symbol, or + /// [`None`] otherwise. + pub fn as_symbol(&self) -> Option> { + if self.is_symbol() { + return unsafe { Some(self.as_symbol_unchecked()) }; + } + + None + } + + /// Returns a reference to the internal [`JsObject`] if the value is an object, or + /// [`None`] otherwise. + pub fn as_object(&self) -> Option> { + if self.is_object() { + return unsafe { Some(self.as_object_unchecked()) }; + } + + None + } + + /// Returns true if the value is null. + #[inline] + pub fn is_null(&self) -> bool { + self.value.get().addr() == NULL_TYPE + } + + /// Returns true if the value is undefined. + #[inline] + pub fn is_undefined(&self) -> bool { + self.value.get().addr() == UNDEFINED_TYPE + } + + /// Returns true if the value is a boolean. + #[inline] + pub fn is_boolean(&self) -> bool { + self.value.get().addr() & TAG_MASK == BOOLEAN_TYPE + } + + /// Returns true if the value is a 32-bit signed integer number. + pub fn is_integer32(&self) -> bool { + self.value.get().addr() & TAG_MASK == INTEGER_TYPE + } + + /// Returns true if the value is a 64-bit floating-point number. + pub fn is_float64(&self) -> bool { + (self.value.get().addr() & !SIGN_BIT) <= QNAN + } + + /// Returns true if the value is a 64-bit floating-point `NaN` number. + pub fn is_nan(&self) -> bool { + self.value.get().addr() == CANONICALIZED_NAN + } + + /// Returns true if the value is a string. + #[inline] + pub fn is_string(&self) -> bool { + self.value.get().addr() & TAG_MASK == STRING_TYPE + } + + /// Returns true if the value is a bigint. + #[inline] + pub fn is_bigint(&self) -> bool { + self.value.get().addr() & TAG_MASK == BIGINT_TYPE + } + + /// Returns true if the value is a symbol. + pub fn is_symbol(&self) -> bool { + self.value.get().addr() & TAG_MASK == SYMBOL_TYPE + } + + /// Returns true if the value is an object + #[inline] + pub fn is_object(&self) -> bool { + self.value.get().addr() & TAG_MASK == OBJECT_TYPE + } + + /// Returns a [`JsVariant`] enum representing the current variant of the value. + /// + /// # Note + /// + /// More exotic implementations of [`JsValue`] cannot use direct references to + /// heap based types, so [`JsVariant`] instead returns [`Ref`]s on those cases. + pub fn variant(&self) -> JsVariant<'_> { + unsafe { + match self.tag() { + ValueTag::Null => JsVariant::Null, + ValueTag::Undefined => JsVariant::Undefined, + ValueTag::Integer32 => JsVariant::Integer32(self.as_integer32_uncheched()), + ValueTag::Float64 => JsVariant::Float64(self.as_float64_unchecked()), + ValueTag::Boolean => JsVariant::Boolean(self.as_boolean_uncheched()), + ValueTag::Object => JsVariant::Object(self.as_object_unchecked()), + ValueTag::String => JsVariant::String(self.as_string_unchecked()), + ValueTag::Symbol => JsVariant::Symbol(self.as_symbol_unchecked()), + ValueTag::BigInt => JsVariant::BigInt(self.as_bigint_unchecked()), + } + } + } + + fn as_pointer(&self) -> *mut () { + self.value + .get() + .map_addr(|addr| addr & MASK_POINTER_PAYLOAD) + } + + fn tag(&self) -> ValueTag { + if self.is_float64() { + return ValueTag::Float64; + } + let tag = ((self.value.get().addr() & TAG_MASK) >> 48) as u16; + ValueTag::try_from(tag).expect("Implementation must never construct an invalid tag") + } + + fn as_boolean_uncheched(&self) -> bool { + (self.value.get().addr() & 0xFF) != 0 + } + + fn as_integer32_uncheched(&self) -> i32 { + (self.value.get().addr() & MASK_INT_PAYLOAD) as u32 as i32 + } + + fn as_float64_unchecked(&self) -> f64 { + f64::from_bits(self.value.get().addr() as u64) + } + + /// Returns a reference to the boxed [`JsString`] without checking + /// if the tag of `self` is valid. + /// + /// # Safety + /// + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsString`] is undefined behaviour. + unsafe fn as_string_unchecked(&self) -> Ref<'_, JsString> { + unsafe { Ref::new(JsString::from_void_ptr(self.as_pointer())) } + } + + /// Returns a reference to the boxed [`JsBigInt`] without checking + /// if the tag of `self` is valid. + /// + /// # Safety + /// + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsBigInt`] is undefined behaviour. + #[inline] + unsafe fn as_bigint_unchecked(&self) -> Ref<'_, JsBigInt> { + // SAFETY: The safety contract must be upheld by the caller + unsafe { Ref::new(JsBigInt::from_void_ptr(self.as_pointer())) } + } + + /// Returns a reference to the boxed [`JsSymbol`] without checking + /// if the tag of `self` is valid. + /// + /// # Safety + /// + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsSymbol`] is undefined behaviour. + unsafe fn as_symbol_unchecked(&self) -> Ref<'_, JsSymbol> { + unsafe { Ref::new(JsSymbol::from_void_ptr(self.as_pointer())) } + } + + /// Returns a reference to the boxed [`JsObject`] without checking + /// if the tag of `self` is valid. + /// + /// # Safety + /// + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsObject`] is undefined behaviour. + unsafe fn as_object_unchecked(&self) -> Ref<'_, JsObject> { + unsafe { Ref::new(JsObject::from_void_ptr(self.as_pointer())) } + } +} + +impl Drop for JsValue { + #[inline] + fn drop(&mut self) { + unsafe { + match self.tag() { + ValueTag::Object => { + ManuallyDrop::into_inner(JsObject::from_void_ptr(self.as_pointer())); + } + ValueTag::String => { + ManuallyDrop::into_inner(JsString::from_void_ptr(self.as_pointer())); + } + ValueTag::Symbol => { + ManuallyDrop::into_inner(JsSymbol::from_void_ptr(self.as_pointer())); + } + ValueTag::BigInt => { + ManuallyDrop::into_inner(JsBigInt::from_void_ptr(self.as_pointer())); + } + _ => {} + } + } + } +} + +impl Clone for JsValue { + #[inline] + fn clone(&self) -> Self { + unsafe { + match self.tag() { + ValueTag::Object => Self::new(self.as_object_unchecked().clone()), + ValueTag::String => Self::new(self.as_string_unchecked().clone()), + ValueTag::Symbol => Self::new(self.as_symbol_unchecked().clone()), + ValueTag::BigInt => Self::new(self.as_bigint_unchecked().clone()), + _ => Self { + value: Cell::new(self.value.get()), + }, + } + } + } +} + +impl Finalize for JsValue {} + +unsafe impl Trace for JsValue { + unsafe fn trace(&self) { + if let Some(o) = self.as_object() { + // SAFETY: `self.as_object()` must always return a valid `JsObject + unsafe { + o.trace(); + } + } + } + + unsafe fn root(&self) { + if self.tag() == ValueTag::Object { + // SAFETY: Implementors of `PointerType` must guarantee the + // safety of both `from_void_ptr` and `into_void_ptr` + unsafe { + let o = JsObject::from_void_ptr(self.as_pointer()); + o.root(); + self.value + .set(JsObject::into_void_ptr(o).map_addr(|addr| addr | OBJECT_TYPE)); + } + } + } + + unsafe fn unroot(&self) { + if self.tag() == ValueTag::Object { + // SAFETY: Implementors of `PointerType` must guarantee the + // safety of both `from_void_ptr` and `into_void_ptr` + unsafe { + let o = JsObject::from_void_ptr(self.as_pointer()); + o.unroot(); + self.value + .set(JsObject::into_void_ptr(o).map_addr(|addr| addr | OBJECT_TYPE)); + } + } + } + + #[inline] + fn finalize_glue(&self) { + if let Some(o) = self.as_object() { + o.finalize_glue(); + } + } +} + +/// Represents a reference to a boxed pointer type inside a [`JsValue`] +/// +/// This is exclusively used to return references to [`JsString`], [`JsObject`], +/// [`JsSymbol`] and [`JsBigInt`], since some [`JsValue`] implementations makes +/// returning proper references difficult. +/// It is mainly returned by the [`JsValue::variant`] method and the +/// `as_` methods for checked casts to pointer types. +/// +/// [`Ref`] implements [`Deref`][`std::ops::Deref`], which facilitates conversion +/// to a proper [`reference`] by using the `ref` keyword or the +/// [`Option::as_deref`][`std::option::Option::as_deref`] method. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Ref<'a, T> { + inner: ManuallyDrop, + _marker: PhantomData<&'a T>, +} + +impl Ref<'_, T> { + #[inline] + fn new(inner: ManuallyDrop) -> Self { + Self { + inner, + _marker: PhantomData, + } + } +} + +// Lift `Ref` over `AsRef`, since implementing `AsRef` would override the +// `as_ref` implementations of `T`. +impl AsRef for Ref<'_, T> +where + T: AsRef, +{ + #[inline] + fn as_ref(&self) -> &U { + >::as_ref(&*self) + } +} + +impl std::ops::Deref for Ref<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &*self.inner + } +} + +impl PartialEq for Ref<'_, T> { + #[inline] + fn eq(&self, other: &T) -> bool { + &**self == other + } +} + +impl std::borrow::Borrow for Ref<'_, T> { + #[inline] + fn borrow(&self) -> &T { + &**self + } +} + +#[cfg(test)] +mod tests_nan_box { + use crate::object::ObjectData; + + use super::*; + + #[test] + fn bigint() { + let value = JsValue::new(JsBigInt::new(12345)); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_integer32()); + assert!(!value.is_float64()); + assert!(!value.is_boolean()); + assert!(!value.is_object()); + assert!(!value.is_string()); + assert!(!value.is_symbol()); + + assert!(value.is_bigint()); + + let bigint = value.as_bigint().unwrap(); + + assert_eq!(&bigint, &JsBigInt::new(12345)); + } + + #[test] + fn symbol() { + let value = JsValue::new(JsSymbol::new(Some("description...".into()))); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_integer32()); + assert!(!value.is_float64()); + assert!(!value.is_boolean()); + assert!(!value.is_object()); + assert!(!value.is_string()); + + assert!(value.is_symbol()); + + let symbol = value.as_symbol().unwrap(); + + assert_eq!(symbol.description(), Some("description...".into())); + } + + #[test] + fn string() { + let value = JsValue::new("I am a string :)"); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_integer32()); + assert!(!value.is_float64()); + assert!(!value.is_boolean()); + assert!(!value.is_object()); + + assert!(value.is_string()); + + let string = value.as_string().unwrap(); + + assert_eq!(JsString::refcount(&string), Some(1)); + + assert_eq!(*string, "I am a string :)"); + } + + #[test] + fn object() { + //let context = Context::default(); + + let o1 = JsObject::from_proto_and_data(None, ObjectData::ordinary()); + + // let value = JsValue::new(context.construct_object()); + let value = JsValue::new(o1.clone()); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_integer32()); + assert!(!value.is_float64()); + assert!(!value.is_boolean()); + + assert!(value.is_object()); + + let o2 = value.as_object().unwrap(); + assert!(JsObject::equals(&o1, &o2)); + } + + #[test] + fn boolean() { + let value = JsValue::new(true); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_integer32()); + assert!(!value.is_float64()); + + assert!(value.is_boolean()); + assert_eq!(value.as_boolean(), Some(true)); + + let value = JsValue::new(false); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_integer32()); + assert!(!value.is_float64()); + + assert!(value.is_boolean()); + assert_eq!(value.as_boolean(), Some(false)); + } + + #[test] + fn float64() { + let value = JsValue::new(1.3); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_integer32()); + + assert!(value.is_float64()); + assert_eq!(value.as_float64(), Some(1.3)); + + let value = JsValue::new(f64::MAX); + assert!(value.is_float64()); + assert_eq!(value.as_float64(), Some(f64::MAX)); + + let value = JsValue::new(f64::MIN); + assert!(value.is_float64()); + assert_eq!(value.as_float64(), Some(f64::MIN)); + + let value = JsValue::nan(); + assert!(value.is_float64()); + assert!(value.as_float64().unwrap().is_nan()); + + let value = JsValue::new(12345); + assert!(!value.is_float64()); + assert_eq!(value.as_float64(), None); + + let value = JsValue::undefined(); + assert!(!value.is_float64()); + assert_eq!(value.as_float64(), None); + + let value = JsValue::null(); + assert!(!value.is_float64()); + assert_eq!(value.as_float64(), None); + } + + #[test] + fn undefined() { + let value = JsValue::undefined(); + + println!("{:?}", value); + println!("{:?}", UNDEFINED_TYPE); + + assert!(value.is_undefined()); + } + + #[test] + fn null() { + let value = JsValue::null(); + + assert!(value.is_null()); + assert!(value.is_null_or_undefined()); + assert!(!value.is_undefined()); + } + + #[test] + fn integer32() { + let value = JsValue::new(-0xcafe); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + + assert!(value.is_integer32()); + + assert_eq!(value.as_integer32(), Some(-0xcafe)); + + let value = JsValue::null(); + assert_eq!(value.as_integer32(), None); + } +} diff --git a/boa_engine/src/value/tests.rs b/boa_engine/src/value/tests.rs index dce5bb5a0b..e80622b259 100644 --- a/boa_engine/src/value/tests.rs +++ b/boa_engine/src/value/tests.rs @@ -694,7 +694,7 @@ mod cyclic_conversions { let value = forward_val(&mut context, src).unwrap(); let result = value.as_string().unwrap(); - assert_eq!(result, "[[],[]]",); + assert_eq!(*result, "[[],[]]",); } // These tests don't throw errors. Instead we mirror Chrome / Firefox behavior for these @@ -711,7 +711,7 @@ mod cyclic_conversions { let value = forward_val(&mut context, src).unwrap(); let result = value.as_string().unwrap(); - assert_eq!(result, ""); + assert_eq!(*result, ""); } #[test] diff --git a/boa_engine/src/value/type.rs b/boa_engine/src/value/type.rs index 61e100bae6..fbf6472db3 100644 --- a/boa_engine/src/value/type.rs +++ b/boa_engine/src/value/type.rs @@ -1,4 +1,4 @@ -use super::JsValue; +use super::{JsValue, JsVariant}; /// Possible types of values as defined at . #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -21,15 +21,15 @@ impl JsValue { /// /// Check [`JsValue::type_of`] if you need to call the `typeof` operator. pub fn get_type(&self) -> Type { - match *self { - Self::Rational(_) | Self::Integer(_) => Type::Number, - Self::String(_) => Type::String, - Self::Boolean(_) => Type::Boolean, - Self::Symbol(_) => Type::Symbol, - Self::Null => Type::Null, - Self::Undefined => Type::Undefined, - Self::BigInt(_) => Type::BigInt, - Self::Object(_) => Type::Object, + match self.variant() { + JsVariant::Float64(_) | JsVariant::Integer32(_) => Type::Number, + JsVariant::String(_) => Type::String, + JsVariant::Boolean(_) => Type::Boolean, + JsVariant::Symbol(_) => Type::Symbol, + JsVariant::Null => Type::Null, + JsVariant::Undefined => Type::Undefined, + JsVariant::BigInt(_) => Type::BigInt, + JsVariant::Object(_) => Type::Object, } } } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index efe70b5fba..76bfbafc0c 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -20,7 +20,7 @@ use crate::{ syntax::ast::node::FormalParameterList, vm::call_frame::GeneratorResumeKind, vm::{call_frame::FinallyReturn, CallFrame, Opcode}, - Context, JsResult, JsValue, + Context, JsResult, JsValue, value::JsVariant, }; use boa_gc::{Cell, Finalize, Gc, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; @@ -749,7 +749,7 @@ impl JsObject { let args = if code.params.parameters.len() > args.len() { let mut v = args.to_vec(); v.extend(vec![ - JsValue::Undefined; + JsValue::undefined(); code.params.parameters.len() - args.len() ]); v @@ -872,7 +872,7 @@ impl JsObject { let args = if code.params.parameters.len() > args.len() { let mut v = args.to_vec(); v.extend(vec![ - JsValue::Undefined; + JsValue::undefined(); code.params.parameters.len() - args.len() ]); v @@ -989,7 +989,7 @@ impl JsObject { let mut args = if code.params.parameters.len() > args.len() { let mut v = args.to_vec(); v.extend(vec![ - JsValue::Undefined; + JsValue::undefined(); code.params.parameters.len() - args.len() ]); v @@ -1124,7 +1124,7 @@ impl JsObject { let mut args = if code.params.parameters.len() > args.len() { let mut v = args.to_vec(); v.extend(vec![ - JsValue::Undefined; + JsValue::undefined(); code.params.parameters.len() - args.len() ]); v @@ -1238,10 +1238,10 @@ impl JsObject { let constructor = *constructor; drop(object); - match function(this_target, args, context)? { - JsValue::Object(ref o) => Ok(o.clone()), + match function(this_target, args, context)?.variant() { + JsVariant::Object(o) => Ok(o.clone()), val => { - if constructor.expect("hmm").is_base() || val.is_undefined() { + if constructor.expect("hmm").is_base() || matches!(val, JsVariant::Undefined) { create_this(context) } else { context.throw_type_error( @@ -1262,10 +1262,10 @@ impl JsObject { let constructor = *constructor; drop(object); - match (function)(this_target, args, captures, context)? { - JsValue::Object(ref o) => Ok(o.clone()), + match (function)(this_target, args, captures, context)?.variant() { + JsVariant::Object(o) => Ok(o.clone()), val => { - if constructor.expect("hmma").is_base() || val.is_undefined() { + if constructor.expect("hmma").is_base() || matches!(val, JsVariant::Undefined) { create_this(context) } else { context.throw_type_error( @@ -1369,7 +1369,7 @@ impl JsObject { let args = if code.params.parameters.len() > args.len() { let mut v = args.to_vec(); v.extend(vec![ - JsValue::Undefined; + JsValue::undefined(); code.params.parameters.len() - args.len() ]); v diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index f17a008928..2a5ab4da0d 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -12,7 +12,7 @@ use crate::{ environments::EnvironmentSlots, object::{FunctionBuilder, JsFunction, JsObject, ObjectData, PrivateElement}, property::{DescriptorKind, PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey}, - value::Numeric, + value::{JsVariant, Numeric}, vm::{ call_frame::CatchAddresses, code_block::{initialize_instance_elements, Readable}, @@ -229,17 +229,17 @@ impl Context { self.vm.push(class); self.vm.push(proto); } else if superclass.is_null() { - self.vm.push(JsValue::Null); + self.vm.push(JsValue::null()); } else { return self.throw_type_error("superclass must be a constructor"); } } Opcode::SetClassPrototype => { let prototype_value = self.vm.pop(); - let prototype = match &prototype_value { - JsValue::Object(proto) => Some(proto.clone()), - JsValue::Null => None, - JsValue::Undefined => { + let prototype = match prototype_value.variant() { + JsVariant::Object(proto) => Some(proto.clone()), + JsVariant::Null => None, + JsVariant::Undefined => { Some(self.intrinsics().constructors().object().prototype.clone()) } _ => unreachable!(), @@ -484,7 +484,7 @@ impl Context { .into(); self.global_bindings_mut().entry(key).or_insert( PropertyDescriptor::builder() - .value(JsValue::Undefined) + .value(JsValue::undefined()) .writable(true) .enumerable(true) .configurable(true) @@ -494,7 +494,7 @@ impl Context { self.realm.environments.put_value_if_uninitialized( binding_locator.environment_index(), binding_locator.binding_index(), - JsValue::Undefined, + JsValue::undefined(), ); } } @@ -526,7 +526,7 @@ impl Context { self.realm.environments.put_value( binding_locator.environment_index(), binding_locator.binding_index(), - JsValue::Undefined, + JsValue::undefined(), ); } Opcode::DefInitLet | Opcode::DefInitConst | Opcode::DefInitArg => { @@ -1530,10 +1530,10 @@ impl Context { if let Some(proto) = home.__get_prototype_of__(self)? { self.vm.push(JsValue::from(proto)); } else { - self.vm.push(JsValue::Null); + self.vm.push(JsValue::null()); } } else { - self.vm.push(JsValue::Null); + self.vm.push(JsValue::null()); }; } Opcode::SuperCall => { @@ -1739,8 +1739,8 @@ impl Context { let func = self.vm.pop(); let this = self.vm.pop(); - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), + let object = match func.variant() { + JsVariant::Object(object) if object.is_callable() => object.clone(), _ => return self.throw_type_error("not a callable function"), }; @@ -1755,7 +1755,7 @@ impl Context { crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?; self.vm.push(result); } else { - self.vm.push(JsValue::Undefined); + self.vm.push(JsValue::undefined()); } } else { let result = object.__call__(&this, &arguments, self)?; @@ -1782,8 +1782,8 @@ impl Context { let func = self.vm.pop(); let this = self.vm.pop(); - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), + let object = match func.variant() { + JsVariant::Object(object) if object.is_callable() => object.clone(), _ => return self.throw_type_error("not a callable function"), }; @@ -1798,7 +1798,7 @@ impl Context { crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?; self.vm.push(result); } else { - self.vm.push(JsValue::Undefined); + self.vm.push(JsValue::undefined()); } } else { let result = object.__call__(&this, &arguments, self)?; @@ -1819,8 +1819,8 @@ impl Context { let func = self.vm.pop(); let this = self.vm.pop(); - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), + let object = match func.variant() { + JsVariant::Object(object) if object.is_callable() => object, _ => return self.throw_type_error("not a callable function"), }; @@ -1848,8 +1848,8 @@ impl Context { let func = self.vm.pop(); let this = self.vm.pop(); - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), + let object = match func.variant() { + JsVariant::Object(object) if object.is_callable() => object.clone(), _ => return self.throw_type_error("not a callable function"), }; @@ -1872,7 +1872,7 @@ impl Context { let result = func .as_constructor() .ok_or_else(|| self.construct_type_error("not a constructor")) - .and_then(|cons| cons.__construct__(&arguments, cons, self))?; + .and_then(|cons| cons.__construct__(&arguments, &cons, self))?; self.vm.push(result); } @@ -1897,7 +1897,7 @@ impl Context { let result = func .as_constructor() .ok_or_else(|| self.construct_type_error("not a constructor")) - .and_then(|cons| cons.__construct__(&arguments, cons, self))?; + .and_then(|cons| cons.__construct__(&arguments, &cons, self))?; self.vm.push(result); } @@ -2054,7 +2054,7 @@ impl Context { let iterator = iterator.as_object().expect("iterator was not an object"); if !done { let iterator_record = IteratorRecord::new(iterator.clone(), next_method, done); - iterator_record.close(Ok(JsValue::Null), self)?; + iterator_record.close(Ok(JsValue::null()), self)?; } } Opcode::IteratorToArray => { @@ -2309,7 +2309,7 @@ impl Context { self.vm.frame_mut().pc = done_address as usize; let iterator_record = IteratorRecord::new(iterator.clone(), next_method, done); - iterator_record.close(Ok(JsValue::Undefined), self)?; + iterator_record.close(Ok(JsValue::undefined()), self)?; let error = self.construct_type_error("iterator does not have a throw method"); return Err(error); @@ -2582,7 +2582,7 @@ impl Context { } Ok(ShouldExit::False) => {} Ok(ShouldExit::Yield) => { - let result = self.vm.stack.pop().unwrap_or(JsValue::Undefined); + let result = self.vm.stack.pop().unwrap_or(JsValue::undefined()); return Ok((result, ReturnType::Yield)); } Err(e) => { diff --git a/boa_engine/src/vm/tests.rs b/boa_engine/src/vm/tests.rs index 8a5fda7be0..cbca50d489 100644 --- a/boa_engine/src/vm/tests.rs +++ b/boa_engine/src/vm/tests.rs @@ -62,7 +62,7 @@ fn multiple_catches() { assert_eq!( Context::default().eval(source.as_bytes()), - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) ); } diff --git a/boa_examples/src/bin/modulehandler.rs b/boa_examples/src/bin/modulehandler.rs index e482447e33..dc932028a9 100644 --- a/boa_examples/src/bin/modulehandler.rs +++ b/boa_examples/src/bin/modulehandler.rs @@ -46,7 +46,7 @@ fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult