diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index 87bfb70c804..4a1065c745c 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -101,7 +101,7 @@ struct Opt { )] dump_ast: Option>, - /// Dump the AST to stdout with the given format. + /// Dump the compiled bytecode and trace the execution stack #[structopt(long = "trace", short = "t")] trace: bool, diff --git a/boa_engine/src/bigint.rs b/boa_engine/src/bigint.rs index e276f5bc6c8..88d19c0e351 100644 --- a/boa_engine/src/bigint.rs +++ b/boa_engine/src/bigint.rs @@ -1,11 +1,12 @@ //! This module implements the JavaScript bigint primitive rust type. -use crate::{builtins::Number, Context, JsValue}; +use crate::{builtins::Number, value::PointerType, Context, JsValue}; use boa_gc::{unsafe_empty_trace, Finalize, Trace}; 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, }; @@ -23,6 +24,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) + } +} + // Safety: BigInt does not contain any objects which needs to be traced, // so this is safe. unsafe impl Trace for JsBigInt { diff --git a/boa_engine/src/builtins/array/array_iterator.rs b/boa_engine/src/builtins/array/array_iterator.rs index 2820337b1e9..552531527b5 100644 --- a/boa_engine/src/builtins/array/array_iterator.rs +++ b/boa_engine/src/builtins/array/array_iterator.rs @@ -67,7 +67,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 58e2301b87e..e7c97fb6cc5 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -28,7 +28,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}; @@ -413,6 +413,7 @@ impl Array { Some(constructor) => constructor .construct(&[len.into()], this, context)? .as_object() + .as_deref() .cloned() .ok_or_else(|| { context.construct_type_error("object constructor didn't return an object") @@ -1534,7 +1535,7 @@ impl Array { source_len as u64, 0, 1, - Some(mapper_function), + Some(&mapper_function), args.get_or_undefined(1), context, )?; @@ -1624,7 +1625,7 @@ impl Array { // 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth) target_index = Self::flatten_into_array( target, - element, + &element, element_len as u64, target_index, new_depth, @@ -2161,9 +2162,9 @@ impl Array { context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - let comparefn = match args.get_or_undefined(0) { - JsValue::Object(ref obj) if obj.is_callable() => Some(obj), - JsValue::Undefined => None, + let comparefn = match args.get_or_undefined(0).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", @@ -2190,11 +2191,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. diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index d05e7b82400..6c7533a9b86 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -234,7 +234,7 @@ impl ArrayBuffer { let new = ctor.construct(&[new_len.into()], &ctor.clone().into(), context)?; // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). - let new_obj = new.as_object().cloned().ok_or_else(|| { + let new_obj = new.as_object().as_deref().cloned().ok_or_else(|| { context.construct_type_error("ArrayBuffer constructor returned non-object value") })?; @@ -322,7 +322,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/bigint/mod.rs b/boa_engine/src/builtins/bigint/mod.rs index b5bd14a4e50..93e68db12e3 100644 --- a/boa_engine/src/builtins/bigint/mod.rs +++ b/boa_engine/src/builtins/bigint/mod.rs @@ -124,6 +124,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 3d6ae4a3dec..cc0e18ba0e3 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/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index 0bb9b2497c7..478b9d647db 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -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()) @@ -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()) diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index 9460229aa0f..0b73fe167b6 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_gc::{unsafe_empty_trace, Finalize, Trace}; @@ -393,24 +393,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()); @@ -515,7 +516,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, diff --git a/boa_engine/src/builtins/date/tests.rs b/boa_engine/src/builtins/date/tests.rs index 7a2eebdacd6..4fda2f4ab65 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/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index cc28c657a3c..f61f34b10e9 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 cc4af912b79..d23f4950592 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -399,7 +399,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); @@ -435,7 +437,8 @@ impl BuiltInFunctionObject { #[allow(clippy::wrong_self_convention)] fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let object = this.as_object().map(JsObject::borrow); + let object = this.as_object(); + let object = object.as_deref().map(JsObject::borrow); let function = object .as_deref() .and_then(Object::as_function) diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index f1833439739..eaa9d6cc5e8 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 358890dac77..9fdd045cc9c 100644 --- a/boa_engine/src/builtins/generator/mod.rs +++ b/boa_engine/src/builtins/generator/mod.rs @@ -148,7 +148,7 @@ impl Generator { ) -> JsResult { // 1. Return ? GeneratorResume(this value, value, empty). match this.as_object() { - Some(obj) if obj.is_generator() => { + 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"), diff --git a/boa_engine/src/builtins/intl/mod.rs b/boa_engine/src/builtins/intl/mod.rs index 0410c755249..0864655db50 100644 --- a/boa_engine/src/builtins/intl/mod.rs +++ b/boa_engine/src/builtins/intl/mod.rs @@ -128,7 +128,7 @@ impl Intl { // 1. Let ll be ? CanonicalizeLocaleList(locales). let ll = Self::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(Into::into), context, ))) diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index a5c773c6c70..ba5d3abf1d1 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -144,7 +144,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)); @@ -154,7 +154,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 e51e10110cf..172a1a99f33 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)? { @@ -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 7ed7c236c34..05176e932e2 100644 --- a/boa_engine/src/builtins/map/map_iterator.rs +++ b/boa_engine/src/builtins/map/map_iterator.rs @@ -71,7 +71,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 673d7f9b386..cf06e73209c 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -23,6 +23,7 @@ use crate::{ }, property::{Attribute, PropertyNameKind}, symbol::WellKnownSymbols, + value::JsVariant, Context, JsResult, JsValue, }; use boa_profiler::Profiler; @@ -221,11 +222,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::Rational(r) => { // 5. If key is -0𝔽, set key to +0𝔽. if r.is_zero() { - JsValue::Rational(0f64) + JsValue::new(0f64) } else { key.clone() } @@ -326,29 +327,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::Rational(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()); } } @@ -398,29 +391,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::Rational(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()); } } diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index ddf02edf2d4..8065662eb2b 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -202,7 +202,7 @@ pub trait JsArgs { impl JsArgs for [JsValue] { fn get_or_undefined(&self, index: usize) -> &JsValue { - const UNDEFINED: &JsValue = &JsValue::Undefined; - self.get(index).unwrap_or(UNDEFINED) + static UNDEFINED: JsValue = JsValue::undefined(); + self.get(index).unwrap_or(&UNDEFINED) } } diff --git a/boa_engine/src/builtins/number/mod.rs b/boa_engine/src/builtins/number/mod.rs index dcbf38cf722..f48122492a7 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::Rational(number) => number.is_finite(), + JsVariant::Integer(_) => 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::Integer(_)) => true, + Some(JsVariant::Rational(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::Integer(_) => true, + JsVariant::Rational(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 612daa0b499..4b1d366a615 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 6bdb4b6a158..a9944b8df0b 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -23,7 +23,7 @@ use crate::{ }, property::{PropertyDescriptor, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, - value::JsValue, + value::{JsValue, JsVariant}, Context, JsResult, JsString, }; use boa_profiler::Profiler; @@ -129,9 +129,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(), ), _ => { @@ -220,7 +220,7 @@ impl Object { } // 5. Return descriptors. - Ok(descriptors.into()) + Ok(JsValue::new(descriptors)) } /// The abstract operation `FromPropertyDescriptor`. @@ -314,7 +314,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. @@ -322,9 +322,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() )); @@ -335,16 +339,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() + )) } }; @@ -356,11 +363,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. @@ -406,14 +413,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)?; @@ -440,7 +447,7 @@ 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)?; Ok(arg.clone()) @@ -518,7 +525,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()) diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index 5f580f74bfd..e709300af62 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -158,7 +158,7 @@ impl Proxy { .data = None; // d. Set F.[[RevocableProxy]] to null. - *revocable_proxy = JsValue::Null; + *revocable_proxy = JsValue::null(); // h. Return undefined. Ok(JsValue::undefined()) diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index 0adad88ba67..fee7bd354e3 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -16,6 +16,7 @@ use crate::{ object::{JsObject, ObjectInitializer}, property::Attribute, symbol::WellKnownSymbols, + value::JsVariant, Context, JsResult, JsValue, }; use boa_profiler::Profiler; @@ -113,7 +114,12 @@ impl Reflect { } let new_target = if let Some(new_target) = args.get(2) { - if new_target.as_object().map(JsObject::is_constructor) != Some(true) { + if new_target + .as_object() + .as_deref() + .map(JsObject::is_constructor) + != Some(true) + { return context.throw_type_error("newTarget must be constructor"); } new_target.clone() @@ -145,7 +151,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(); @@ -248,7 +254,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. @@ -381,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 b39d66a8a9b..234a96e64c3 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -181,13 +181,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()); } } } @@ -330,7 +330,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'g' => regexp.flags.contains(RegExpFlags::GLOBAL), @@ -648,7 +648,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() { @@ -688,7 +688,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()) @@ -723,7 +723,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]]). @@ -1000,7 +1000,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()) @@ -1025,7 +1025,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, @@ -1204,6 +1204,7 @@ impl RegExp { let mut replace_value = args.get_or_undefined(1).clone(); let functional_replace = replace_value .as_object() + .as_deref() .map(JsObject::is_callable) .unwrap_or_default(); @@ -1233,7 +1234,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, @@ -1451,7 +1452,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)?; @@ -1557,7 +1558,7 @@ impl RegExp { // 16. If size is 0, then if size == 0 { // a. Let z be ? RegExpExec(splitter, S). - let result = Self::abstract_exec(splitter, arg_str.clone(), context)?; + let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?; // b. If z is not null, return A. if result.is_some() { @@ -1583,7 +1584,7 @@ impl RegExp { splitter.set("lastIndex", JsValue::new(q), true, context)?; // b. Let z be ? RegExpExec(splitter, S). - let result = Self::abstract_exec(splitter, arg_str.clone(), context)?; + let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?; // c. If z is null, set q to AdvanceStringIndex(S, q, unicodeMatching). // d. Else, diff --git a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs index 620d26640b8..49c065b8b6e 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 1ff23fb706d..53c5501805a 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -200,7 +200,7 @@ 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() }); @@ -350,7 +350,7 @@ impl Set { index += 1; } - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) } /// `Map.prototype.has( key )` diff --git a/boa_engine/src/builtins/set/set_iterator.rs b/boa_engine/src/builtins/set/set_iterator.rs index 07b2f5e2c72..21d476abb0a 100644 --- a/boa_engine/src/builtins/set/set_iterator.rs +++ b/boa_engine/src/builtins/set/set_iterator.rs @@ -66,7 +66,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() @@ -85,7 +86,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 60bfdd8f9f5..e227e0f637e 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 ( )` @@ -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. @@ -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(); @@ -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"). @@ -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(); @@ -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 { @@ -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 31fd9c00f99..3ddd6cb6a9f 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 df98d652c1a..a1b4b2ee72d 100644 --- a/boa_engine/src/builtins/symbol/mod.rs +++ b/boa_engine/src/builtins/symbol/mod.rs @@ -187,6 +187,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")) } @@ -231,7 +233,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` @@ -302,14 +304,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 d7ea9c48957..1c4f65cda76 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_gc::{unsafe_empty_trace, Finalize, Trace}; @@ -162,7 +162,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 @@ -408,7 +408,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. @@ -434,7 +436,7 @@ impl TypedArray { // b. Let len be the number of elements in values. // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let target_obj = Self::create(constructor, &[values.len().into()], context)?; + let target_obj = Self::create(&constructor, &[values.len().into()], context)?; // d. Let k be 0. // e. Repeat, while k < len, @@ -470,7 +472,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, @@ -516,7 +518,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, @@ -1076,7 +1078,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 @@ -1635,7 +1637,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, @@ -1903,16 +1905,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)?; } } @@ -2305,7 +2307,7 @@ impl TypedArray { let count = std::cmp::max(r#final - k, 0) as usize; // 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »). - let a = Self::species_create(obj, o.typed_array_name(), &[count.into()], context)?; + let a = Self::species_create(&obj, o.typed_array_name(), &[count.into()], context)?; let a_borrow = a.borrow(); let a_array = a_borrow .as_typed_array() @@ -2480,9 +2482,9 @@ impl TypedArray { /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort fn sort(this: &JsValue, args: &[JsValue], 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") @@ -2566,7 +2568,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); @@ -2651,7 +2653,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 }) @@ -2751,7 +2753,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(), @@ -2814,7 +2816,7 @@ impl TypedArray { .as_typed_array() .map(|o| o.typed_array_name().name().into()) }) - .unwrap_or(JsValue::Undefined)) + .unwrap_or_else(JsValue::undefined)) } /// `23.2.4.1 TypedArraySpeciesCreate ( exemplar, argumentList )` diff --git a/boa_engine/src/class.rs b/boa_engine/src/class.rs index e61123d615c..fa1a58bc594 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/environments/compile.rs b/boa_engine/src/environments/compile.rs index ec0bee639ff..23a12cc4aa8 100644 --- a/boa_engine/src/environments/compile.rs +++ b/boa_engine/src/environments/compile.rs @@ -186,7 +186,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/lib.rs b/boa_engine/src/lib.rs index 9db8165bf50..c095e0a70cc 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -64,6 +64,7 @@ clippy::missing_errors_doc, clippy::as_conversions, clippy::let_unit_value, + clippy::transmute_ptr_to_ptr, rustdoc::missing_doc_code_examples )] diff --git a/boa_engine/src/object/internal_methods/bound_function.rs b/boa_engine/src/object/internal_methods/bound_function.rs index 3949cd93d3b..8bfd9c6cb03 100644 --- a/boa_engine/src/object/internal_methods/bound_function.rs +++ b/boa_engine/src/object/internal_methods/bound_function.rs @@ -1,4 +1,4 @@ -use crate::{object::JsObject, Context, JsResult, JsValue}; +use crate::{object::JsObject, value::JsVariant, Context, JsResult, JsValue}; use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; @@ -90,8 +90,10 @@ fn bound_function_exotic_construct( args.extend_from_slice(arguments_list); // 5. If SameValue(F, newTarget) is true, set newTarget to target. - let new_target = match new_target { - JsValue::Object(new_target) if JsObject::equals(obj, new_target) => target.clone().into(), + let new_target = match new_target.variant() { + JsVariant::Object(ref new_target) if JsObject::equals(obj, new_target) => { + target.clone().into() + } _ => new_target.clone(), }; diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs index 5b5be57dbf6..4db7a601d23 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; @@ -97,9 +97,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"), }; @@ -159,7 +159,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, )? @@ -700,7 +700,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"); } @@ -821,8 +825,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", @@ -830,7 +834,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", diff --git a/boa_engine/src/object/jsarray.rs b/boa_engine/src/object/jsarray.rs index 1f69efdc738..44d6741c790 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/jsobject.rs b/boa_engine/src/object/jsobject.rs index 3d6ddf6cfd9..e8ee5d59747 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}; @@ -15,6 +15,7 @@ use std::{ collections::HashMap, error::Error, fmt::{self, Debug, Display}, + mem::{self, ManuallyDrop}, result::Result as StdResult, }; @@ -30,6 +31,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/operations.rs b/boa_engine/src/object/operations.rs index 91b22c31662..1b2fd013940 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, }; @@ -587,11 +587,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") @@ -826,7 +829,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 0a624184b49..ecb7672a139 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::FxHashSet; use std::{ @@ -7,6 +7,7 @@ use std::{ cell::Cell, hash::{Hash, Hasher}, marker::PhantomData, + mem::ManuallyDrop, ops::Deref, ptr::{copy_nonoverlapping, NonNull}, rc::Rc, @@ -318,6 +319,21 @@ pub struct JsString { _marker: PhantomData>, } +unsafe impl PointerType for JsString { + unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop { + let string = Self { + inner: NonNull::new_unchecked(ptr.cast()), + _marker: PhantomData, + }; + + ManuallyDrop::new(string) + } + + unsafe fn into_void_ptr(string: ManuallyDrop) -> *mut () { + string.inner.as_ptr().cast() + } +} + impl Default for JsString { #[inline] fn default() -> Self { diff --git a/boa_engine/src/symbol.rs b/boa_engine/src/symbol.rs index f869c6a0a6b..375ab1e095c 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,6 +254,16 @@ 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) + } +} + impl JsSymbol { /// Create a new symbol. #[inline] diff --git a/boa_engine/src/tests.rs b/boa_engine/src/tests.rs index a8ee0730b3e..83a1b77c884 100644 --- a/boa_engine/src/tests.rs +++ b/boa_engine/src/tests.rs @@ -845,6 +845,7 @@ mod in_operator { .get("prototype", &mut context) .unwrap() .as_object() + .as_deref() .cloned()) ); } diff --git a/boa_engine/src/value/conversions.rs b/boa_engine/src/value/conversions.rs index 75fe285db83..2f095c5a1f6 100644 --- a/boa_engine/src/value/conversions.rs +++ b/boa_engine/src/value/conversions.rs @@ -1,4 +1,4 @@ -use super::{Display, JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler}; +use super::{Display, JsValue}; impl From<&Self> for JsValue { #[inline] @@ -7,18 +7,6 @@ impl From<&Self> for JsValue { } } -impl From for JsValue -where - T: Into, -{ - #[inline] - fn from(value: T) -> Self { - let _timer = Profiler::global().start_event("From", "value"); - - Self::String(value.into()) - } -} - impl From for JsValue { #[inline] fn from(value: char) -> Self { @@ -26,13 +14,6 @@ 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; @@ -42,95 +23,13 @@ impl Display for TryFromCharError { } } -impl From for JsValue { - #[allow(clippy::float_cmp)] - #[inline] - fn from(value: f64) -> Self { - // if value as i32 as f64 == value { - // Self::Integer(value as i32) - // } else { - Self::Rational(value) - // } - } -} - -impl From for JsValue { - #[inline] - fn from(value: u32) -> Self { - if let Ok(integer) = i32::try_from(value) { - Self::Integer(integer) - } else { - Self::Rational(value.into()) - } - } -} - -impl From for JsValue { - #[inline] - fn from(value: i32) -> Self { - Self::Integer(value) - } -} - -impl From for JsValue { - #[inline] - fn from(value: JsBigInt) -> Self { - Self::BigInt(value) - } -} - -impl From for JsValue { - #[inline] - fn from(value: usize) -> Self { - if let Ok(value) = i32::try_from(value) { - Self::Integer(value) - } else { - Self::Rational(value as f64) - } - } -} - -impl From for JsValue { - #[inline] - fn from(value: u64) -> Self { - if let Ok(value) = i32::try_from(value) { - Self::Integer(value) - } else { - Self::Rational(value as f64) - } - } -} - -impl From for JsValue { - #[inline] - fn from(value: i64) -> Self { - if let Ok(value) = i32::try_from(value) { - Self::Integer(value) - } else { - Self::Rational(value as f64) - } - } -} - -impl From for JsValue { - #[inline] - fn from(value: bool) -> Self { - Self::Boolean(value) - } -} - -impl From for JsValue { - #[inline] - fn from(object: JsObject) -> Self { - let _timer = Profiler::global().start_event("From", "value"); - Self::Object(object) - } -} +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct TryFromObjectError; -impl From<()> for JsValue { +impl Display for TryFromObjectError { #[inline] - fn from(_: ()) -> Self { - Self::null() + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Could not convert value to an Object type") } } diff --git a/boa_engine/src/value/display.rs b/boa_engine/src/value/display.rs index 2e6d8855b74..97a7e2b443e 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() { @@ -197,7 +197,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(), } } @@ -217,7 +217,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()); @@ -255,20 +255,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}"); @@ -280,21 +280,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::Rational(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::Integer(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 07870915d31..4889d3342f4 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::Rational(x), JsVariant::Rational(y)) => Number::equal(x, y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Number::equal(x, f64::from(y)), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Number::equal(f64::from(x), y), + (JsVariant::Integer(x), JsVariant::Integer(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::Integer(_) | JsVariant::Rational(_), + JsVariant::String(_) | JsVariant::Boolean(_), + ) + | (JsVariant::String(_), JsVariant::Integer(_) | JsVariant::Rational(_)) => { 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::Rational(_) + | JsVariant::Integer(_) + | 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::Rational(_) + | JsVariant::Integer(_) + | 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::Rational(b)) => *a == b, + (JsVariant::Rational(a), JsVariant::BigInt(b)) => a == *b, + (JsVariant::BigInt(a), JsVariant::Integer(b)) => *a == b, + (JsVariant::Integer(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::Rational(x), JsVariant::Rational(y)) => Number::same_value(x, y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Number::same_value(x, f64::from(y)), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Number::same_value(f64::from(x), y), + (JsVariant::Integer(x), JsVariant::Integer(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::Rational(x), JsVariant::Rational(y)) => Number::same_value_zero(x, y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => { + Number::same_value_zero(x, f64::from(y)) } - (JsValue::Integer(x), JsValue::Rational(y)) => { - Number::same_value_zero(f64::from(*x), *y) + (JsVariant::Integer(x), JsVariant::Rational(y)) => { + Number::same_value_zero(f64::from(x), y) } - (JsValue::Integer(x), JsValue::Integer(y)) => x == y, + (JsVariant::Integer(x), JsVariant::Integer(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) { - (JsValue::Null, JsValue::Null) | (JsValue::Undefined, JsValue::Undefined) => true, - (JsValue::String(ref x), JsValue::String(ref y)) => x == y, - (JsValue::Boolean(x), JsValue::Boolean(y)) => x == y, - (JsValue::Object(ref x), JsValue::Object(ref y)) => JsObject::equals(x, y), - (JsValue::Symbol(ref x), JsValue::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 4498eca035a..0f27cbdb57a 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::Integer(integer) => RationalHashable(f64::from(integer)).hash(state), + JsVariant::BigInt(bigint) => bigint.hash(state), + JsVariant::Rational(rational) => RationalHashable(rational).hash(state), + JsVariant::Symbol(symbol) => Hash::hash(symbol.as_ref(), 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 2fd12402887..2aa9e17f1a2 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -1,3 +1,4 @@ +#![warn(unsafe_op_in_unsafe_fn)] //! This module implements the JavaScript Value. //! //! Javascript values, utility methods and conversion between Javascript values and Rust values. @@ -22,8 +23,11 @@ use num_integer::Integer; use num_traits::Zero; use once_cell::sync::Lazy; use std::{ + cell::Cell, collections::HashSet, fmt::{self, Display}, + marker::PhantomData, + mem::ManuallyDrop, ops::Sub, str::FromStr, }; @@ -55,29 +59,70 @@ static TWO_E_63: Lazy = Lazy::new(|| { BigInt::from(TWO_E_63) }); -/// A Javascript value -#[derive(Trace, 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), +const SIGN_BIT: u64 = 0x8000000000000000; +const EXPONENT: u64 = 0x7FF0000000000000; +// const MANTISA: u64 = 0x0008000000000000; +const SIGNAL_BIT: u64 = 0x0008000000000000; +const QNAN: u64 = EXPONENT | SIGNAL_BIT; // 0x7FF8000000000000 + +pub const CANONICALIZED_NAN: u64 = QNAN; +// const PAYLOAD: u64 = 0x00007FFFFFFFFFFF; +// const TYPE: u64 = !PAYLOAD; + +pub const TAG_MASK: u64 = 0xFFFF000000000000; + +pub const DOUBLE_TYPE: u64 = QNAN; +pub const INTEGER_TYPE: u64 = QNAN | (0b001 << 48); +pub const BOOLEAN_TYPE: u64 = QNAN | (0b010 << 48); +pub const UNDEFINED_TYPE: u64 = QNAN | (0b011 << 48); +pub const NULL_TYPE: u64 = QNAN | (0b100 << 48); + +pub const RESERVED1_TYPE: u64 = QNAN | (0b101 << 48); +pub const RESERVED2_TYPE: u64 = QNAN | (0b110 << 48); +pub const RESERVED3_TYPE: u64 = QNAN | (0b111 << 48); + +pub const POINTER_TYPE: u64 = SIGN_BIT | QNAN; +pub const OBJECT_TYPE: u64 = POINTER_TYPE | (0b001 << 48); +pub const STRING_TYPE: u64 = POINTER_TYPE | (0b010 << 48); +pub const SYMBOL_TYPE: u64 = POINTER_TYPE | (0b011 << 48); +pub const BIGINT_TYPE: u64 = POINTER_TYPE | (0b100 << 48); + +pub const RESERVED4_TYPE: u64 = POINTER_TYPE | (0b101 << 48); +pub const RESERVED5_TYPE: u64 = POINTER_TYPE | (0b110 << 48); +pub const RESERVED6_TYPE: u64 = POINTER_TYPE | (0b111 << 48); + +pub const MASK_INT_PAYLOAD: u64 = 0x00000000FFFFFFFF; +pub const MASK_POINTER_PAYLOAD: u64 = 0x0000FFFFFFFFFFFF; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum ValueTag { + Double = (DOUBLE_TYPE >> 48) as _, + Integer = (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, } +#[derive(Debug)] +#[repr(transparent)] +pub struct JsValue { + value: Cell, +} + +// TODO: Remove this !!!! +unsafe impl Sync for JsValue {} + impl JsValue { /// Create a new [`JsValue`]. #[inline] @@ -88,184 +133,851 @@ impl JsValue { value.into() } + fn tag(&self) -> ValueTag { + if self.is_rational() { + return ValueTag::Double; + } + unsafe { std::mem::transmute(((self.value.get() & TAG_MASK) >> 48) as u16) } + } + + /// Creates a new number with `NaN` value. + #[inline] + pub const fn nan() -> Self { + Self { + value: Cell::new(CANONICALIZED_NAN), + } + } + + pub fn is_nan(&self) -> bool { + self.value.get() == CANONICALIZED_NAN + } + /// Creates a new `undefined` value. #[inline] - pub fn undefined() -> Self { - Self::Undefined + pub const fn undefined() -> Self { + Self { + value: Cell::new(UNDEFINED_TYPE), + } + } + + /// Returns true if the value is undefined. + #[inline] + pub fn is_undefined(&self) -> bool { + self.value.get() == UNDEFINED_TYPE } /// Creates a new `null` value. #[inline] - pub fn null() -> Self { - Self::Null + pub const fn null() -> Self { + Self { + value: Cell::new(NULL_TYPE), + } } - /// Creates a new number with `NaN` value. + /// Returns true if the value is null. #[inline] - pub fn nan() -> Self { - Self::Rational(f64::NAN) + pub fn is_null(&self) -> bool { + self.value.get() == NULL_TYPE } - /// Creates a new number with `Infinity` value. + /// Returns true if the value is null or undefined. #[inline] - pub fn positive_infinity() -> Self { - Self::Rational(f64::INFINITY) + pub fn is_null_or_undefined(&self) -> bool { + self.is_null() || self.is_undefined() } - /// Creates a new number with `-Infinity` value. + pub fn is_rational(&self) -> bool { + (self.value.get() & !SIGN_BIT) <= QNAN + } + + fn as_rational_unchecked(&self) -> f64 { + f64::from_bits(self.value.get()) + } + + pub fn as_rational(&self) -> Option { + if self.is_rational() { + return Some(self.as_rational_unchecked()); + } + + None + } + + pub fn is_i32(&self) -> bool { + self.value.get() & TAG_MASK == INTEGER_TYPE + } + + pub fn as_i32_uncheched(&self) -> i32 { + (self.value.get() & MASK_INT_PAYLOAD) as u32 as i32 + } + + pub fn as_i32(&self) -> Option { + if self.is_i32() { + return Some(self.as_i32_uncheched()); + } + + None + } + + /// Returns true if the value is a boolean. #[inline] - pub fn negative_infinity() -> Self { - Self::Rational(f64::NEG_INFINITY) + pub fn is_boolean(&self) -> bool { + self.value.get() & TAG_MASK == BOOLEAN_TYPE + } + + pub fn as_boolean_uncheched(&self) -> bool { + (self.value.get() & 0xFF) != 0 + } + + #[inline] + pub fn as_boolean(&self) -> Option { + if self.is_boolean() { + return Some(self.as_boolean_uncheched()); + } + + None + } + + pub fn as_pointer(&self) -> *mut () { + (self.value.get() & MASK_POINTER_PAYLOAD) as *mut () } /// Returns true if the value is an object #[inline] pub fn is_object(&self) -> bool { - matches!(self, Self::Object(_)) + self.value.get() & TAG_MASK == OBJECT_TYPE + } + + /// 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. + pub unsafe fn as_object_unchecked(&self) -> Ref<'_, JsObject> { + unsafe { Ref::new(JsObject::from_void_ptr(self.as_pointer())) } + } + + pub fn as_object(&self) -> Option> { + if self.is_object() { + return unsafe { Some(self.as_object_unchecked()) }; + } + + None } + /// Returns true if the value is a string. #[inline] - pub fn as_object(&self) -> Option<&JsObject> { - match *self { - Self::Object(ref o) => Some(o), - _ => None, + pub fn is_string(&self) -> bool { + self.value.get() & TAG_MASK == STRING_TYPE + } + + /// 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. + pub unsafe fn as_string_unchecked(&self) -> Ref<'_, JsString> { + unsafe { Ref::new(JsString::from_void_ptr(self.as_pointer())) } + } + + /// Returns the string if the values is a string, otherwise `None`. + #[inline] + pub fn as_string(&self) -> Option> { + if self.is_string() { + return unsafe { Some(self.as_string_unchecked()) }; } + + None } - /// It determines if the value is a callable function with a `[[Call]]` internal method. + pub fn is_symbol(&self) -> bool { + self.value.get() & TAG_MASK == SYMBOL_TYPE + } + + /// Returns a reference to the boxed [`JsSymbol`] without checking + /// if the tag of `self` is valid. /// - /// More information: - /// - [ECMAScript reference][spec] + /// # Safety /// - /// [spec]: https://tc39.es/ecma262/#sec-iscallable + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsSymbol`] is undefined behaviour. + pub unsafe fn as_symbol_unchecked(&self) -> Ref<'_, JsSymbol> { + unsafe { Ref::new(JsSymbol::from_void_ptr(self.as_pointer())) } + } + + pub fn as_symbol(&self) -> Option> { + if self.is_symbol() { + return unsafe { Some(self.as_symbol_unchecked()) }; + } + + None + } + + /// Returns true if the value is a bigint. #[inline] - pub fn is_callable(&self) -> bool { - matches!(self, Self::Object(obj) if obj.is_callable()) + pub fn is_bigint(&self) -> bool { + self.value.get() & TAG_MASK == BIGINT_TYPE } + /// 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] - pub fn as_callable(&self) -> Option<&JsObject> { - self.as_object().filter(|obj| obj.is_callable()) + pub 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 true if the value is a constructor object + pub fn as_bigint(&self) -> Option> { + if self.is_bigint() { + return unsafe { Some(self.as_bigint_unchecked()) }; + } + + None + } + + pub fn variant(&self) -> JsVariant<'_> { + unsafe { + match self.tag() { + ValueTag::Null => JsVariant::Null, + ValueTag::Undefined => JsVariant::Undefined, + ValueTag::Integer => JsVariant::Integer(self.as_i32_uncheched()), + ValueTag::Double => JsVariant::Rational(self.as_rational_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()), + } + } + } +} + +#[derive(Debug)] +pub enum JsVariant<'a> { + Null, + Undefined, + Rational(f64), + Integer(i32), + Boolean(bool), + String(Ref<'a, JsString>), + Symbol(Ref<'a, JsSymbol>), + BigInt(Ref<'a, JsBigInt>), + Object(Ref<'a, JsObject>), +} + +impl From for JsValue { #[inline] - pub fn is_constructor(&self) -> bool { - matches!(self, Self::Object(obj) if obj.is_constructor()) + fn from(value: bool) -> Self { + let value = Self { + value: Cell::new(BOOLEAN_TYPE | u64::from(value)), + }; + debug_assert!(value.is_boolean()); + debug_assert_eq!(value.tag(), ValueTag::Boolean); + value } +} +impl From for JsValue { #[inline] - pub fn as_constructor(&self) -> Option<&JsObject> { - self.as_object().filter(|obj| obj.is_constructor()) + fn from(value: i32) -> Self { + let value = Self { + value: Cell::new(INTEGER_TYPE | u64::from(value as u32)), + }; + debug_assert!(value.is_integer()); + debug_assert_eq!(value.tag(), ValueTag::Integer); + value } +} - /// Returns true if the value is a symbol. +impl From for JsValue { #[inline] - pub fn is_symbol(&self) -> bool { - matches!(self, Self::Symbol(_)) + fn from(value: u32) -> Self { + if let Ok(integer) = i32::try_from(value) { + Self::new(integer) + } else { + Self::new(f64::from(value)) + } } +} - pub fn as_symbol(&self) -> Option { - match self { - Self::Symbol(symbol) => Some(symbol.clone()), - _ => None, +impl From for JsValue { + #[inline] + fn from(value: usize) -> Self { + if let Ok(value) = i32::try_from(value) { + Self::new(value) + } else { + Self::new(value as f64) } } +} - /// Returns true if the value is undefined. +impl From for JsValue { #[inline] - pub fn is_undefined(&self) -> bool { - matches!(self, Self::Undefined) + fn from(value: u64) -> Self { + if let Ok(value) = i32::try_from(value) { + Self::new(value) + } else { + Self::new(value as f64) + } + } +} + +impl From for JsValue { + #[inline] + fn from(value: i64) -> Self { + if let Ok(value) = i32::try_from(value) { + Self::new(value) + } else { + Self::new(value as f64) + } } +} - /// Returns true if the value is null. +impl From for JsValue { #[inline] - pub fn is_null(&self) -> bool { - matches!(self, Self::Null) + fn from(value: f64) -> Self { + if value.is_nan() { + return Self { + value: Cell::new(CANONICALIZED_NAN), + }; + } + + let value = Self { + value: Cell::new(value.to_bits()), + }; + debug_assert!(value.is_rational()); + debug_assert_eq!(value.tag(), ValueTag::Double); + value } +} - /// Returns true if the value is null or undefined. +impl From<()> for JsValue { #[inline] - pub fn is_null_or_undefined(&self) -> bool { - matches!(self, Self::Null | Self::Undefined) + fn from(_: ()) -> Self { + let value = Self::null(); + debug_assert!(value.is_null()); + debug_assert_eq!(value.tag(), ValueTag::Null); + value } +} - /// Returns true if the value is a 64-bit floating-point number. +impl From<&str> for JsValue { #[inline] - pub fn is_double(&self) -> bool { - matches!(self, Self::Rational(_)) + fn from(string: &str) -> Self { + From::::from(JsString::new(string)) } +} - /// Returns true if the value is integer. +impl From<&String> for JsValue { #[inline] - #[allow(clippy::float_cmp)] - pub fn is_integer(&self) -> bool { - // If it can fit in a i32 and the trucated version is - // equal to the original then it is an integer. - let is_racional_intiger = |n: f64| n == f64::from(n as i32); + fn from(string: &String) -> Self { + From::::from(JsString::new(string.as_str())) + } +} - match *self { - Self::Integer(_) => true, - Self::Rational(n) if is_racional_intiger(n) => true, - _ => false, +impl From for JsValue { + #[inline] + fn from(string: String) -> Self { + From::::from(JsString::new(string)) + } +} + +impl From> for JsValue { + #[inline] + fn from(string: Box) -> Self { + From::::from(JsString::new(string)) + } +} + +impl From for JsValue { + #[inline] + fn from(string: JsString) -> Self { + let string = ManuallyDrop::new(string); + let pointer = unsafe { JsString::into_void_ptr(string) } as u64; + debug_assert_eq!(pointer & MASK_POINTER_PAYLOAD, pointer); + let value = Self { + value: Cell::new(STRING_TYPE | pointer), + }; + debug_assert!(value.is_string()); + debug_assert_eq!(value.tag(), ValueTag::String); + value + } +} + +impl From for JsValue { + #[inline] + fn from(object: JsObject) -> Self { + let object = ManuallyDrop::new(object); + let pointer = unsafe { JsObject::into_void_ptr(object) } as u64; + debug_assert_eq!(pointer & MASK_POINTER_PAYLOAD, pointer); + debug_assert_eq!(OBJECT_TYPE & MASK_POINTER_PAYLOAD, 0); + let value = Self { + value: Cell::new(OBJECT_TYPE | pointer), + }; + debug_assert!(value.is_object()); + debug_assert_eq!(value.tag(), ValueTag::Object); + value + } +} + +impl From for JsValue { + #[inline] + fn from(symbol: JsSymbol) -> Self { + let symbol = ManuallyDrop::new(symbol); + let pointer = unsafe { JsSymbol::into_void_ptr(symbol) as u64 }; + debug_assert_eq!(pointer & MASK_POINTER_PAYLOAD, pointer); + let value = Self { + value: Cell::new(SYMBOL_TYPE | pointer), + }; + debug_assert!(value.is_symbol()); + debug_assert_eq!(value.tag(), ValueTag::Symbol); + value + } +} + +impl From for JsValue { + #[inline] + fn from(bigint: JsBigInt) -> Self { + let bigint = ManuallyDrop::new(bigint); + let pointer = unsafe { JsBigInt::into_void_ptr(bigint) as u64 }; + debug_assert_eq!(pointer & MASK_POINTER_PAYLOAD, pointer); + let value = Self { + value: Cell::new(BIGINT_TYPE | pointer), + }; + debug_assert!(value.is_bigint()); + debug_assert_eq!(value.tag(), ValueTag::BigInt); + value + } +} + +/// This abstracts over every pointer type boxed inside `NaN` values. +/// +/// # 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 (); +} + +/// 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 `NaN` boxing 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, } } +} - /// Returns true if the value is a number. +impl std::borrow::Borrow for Ref<'_, T> { #[inline] - pub fn is_number(&self) -> bool { - matches!(self, Self::Rational(_) | Self::Integer(_)) + fn borrow(&self) -> &T { + &**self } +} +impl AsRef for Ref<'_, T> { #[inline] - pub fn as_number(&self) -> Option { - match *self { - Self::Integer(integer) => Some(integer.into()), - Self::Rational(rational) => Some(rational), - _ => None, + fn as_ref(&self) -> &T { + &**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 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())); + } + _ => {} + } } } +} - /// Returns true if the value is a string. +impl Clone for JsValue { #[inline] - pub fn is_string(&self) -> bool { - matches!(self, Self::String(_)) + 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(OBJECT_TYPE | (JsObject::into_void_ptr(o) as u64)); + } + } + } + + 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(OBJECT_TYPE | (JsObject::into_void_ptr(o) as u64)); + } + } } - /// 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, + fn finalize_glue(&self) { + if let Some(o) = self.as_object() { + o.finalize_glue(); } } +} +#[cfg(test)] +mod tests_nan_box { + use super::*; - /// Returns true if the value is a boolean. + #[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_i32()); + assert!(!value.is_rational()); + 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_i32()); + assert!(!value.is_rational()); + 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_i32()); + assert!(!value.is_rational()); + assert!(!value.is_boolean()); + assert!(!value.is_object()); + + assert!(value.is_string()); + + let string = value.as_string().unwrap(); + + assert_eq!(JsString::refcount(&string), 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_i32()); + assert!(!value.is_rational()); + 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_i32()); + assert!(!value.is_rational()); + + 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_i32()); + assert!(!value.is_rational()); + + assert!(value.is_boolean()); + assert_eq!(value.as_boolean(), Some(false)); + } + + #[test] + fn rational() { + let value = JsValue::new(1.3); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + + assert!(value.is_rational()); + assert_eq!(value.as_rational(), Some(1.3)); + + let value = JsValue::new(f64::MAX); + assert!(value.is_rational()); + assert_eq!(value.as_rational(), Some(f64::MAX)); + + let value = JsValue::new(f64::MIN); + assert!(value.is_rational()); + assert_eq!(value.as_rational(), Some(f64::MIN)); + + let value = JsValue::nan(); + assert!(value.is_rational()); + assert!(value.as_rational().unwrap().is_nan()); + + let value = JsValue::new(12345); + assert!(!value.is_rational()); + assert_eq!(value.as_rational(), None); + + let value = JsValue::undefined(); + assert!(!value.is_rational()); + assert_eq!(value.as_rational(), None); + + let value = JsValue::null(); + assert!(!value.is_rational()); + assert_eq!(value.as_rational(), 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 integer() { + let value = JsValue::new(-0xcafe); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + + assert!(value.is_i32()); + + assert_eq!(value.as_i32(), Some(-0xcafe)); + + let value = JsValue::null(); + assert_eq!(value.as_i32(), None); + } +} + +impl JsValue { + /// Creates a new number with `Infinity` value. #[inline] - pub fn is_boolean(&self) -> bool { - matches!(self, Self::Boolean(_)) + pub fn positive_infinity() -> Self { + Self::new(f64::INFINITY) } + /// Creates a new number with `-Infinity` value. #[inline] - pub fn as_boolean(&self) -> Option { - match self { - Self::Boolean(boolean) => Some(*boolean), - _ => None, + pub fn negative_infinity() -> Self { + Self::new(f64::NEG_INFINITY) + } + + /// It determines if the value is a callable function with a `[[Call]]` internal method. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-iscallable + #[inline] + pub fn is_callable(&self) -> bool { + self.as_object() + .as_deref() + .map_or(false, JsObject::is_callable) + } + + #[inline] + 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 { + self.as_object() + .as_deref() + .map_or(false, JsObject::is_constructor) + } + + #[inline] + pub fn as_constructor(&self) -> Option> { + self.as_object().filter(|obj| obj.is_constructor()) + } + + /// Returns true if the value is a 64-bit floating-point number. + #[inline] + pub fn is_double(&self) -> bool { + self.is_rational() + } + + /// Returns true if the value is integer. + #[inline] + #[allow(clippy::float_cmp)] + pub fn is_integer(&self) -> bool { + // If it can fit in a i32 and the trucated version is + // equal to the original then it is an integer. + let is_racional_intiger = |n: f64| n == f64::from(n as i32); + + if self.is_i32() { + true + } else if self.is_rational() { + is_racional_intiger(self.as_rational_unchecked()) + } else { + false } } - /// Returns true if the value is a bigint. + /// Returns true if the value is a number. #[inline] - pub fn is_bigint(&self) -> bool { - matches!(self, Self::BigInt(_)) + pub fn is_number(&self) -> bool { + self.is_i32() || self.is_rational() } - /// 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), + pub fn as_number(&self) -> Option { + match self.variant() { + JsVariant::Integer(integer) => Some(integer.into()), + JsVariant::Rational(rational) => Some(rational), _ => None, } } @@ -277,14 +989,14 @@ impl JsValue { /// /// [spec]: https://tc39.es/ecma262/#sec-toboolean pub fn to_boolean(&self) -> bool { - match *self { - Self::Undefined | Self::Null => false, - 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::Undefined | JsVariant::Null => false, + JsVariant::Symbol(_) | JsVariant::Object(_) => true, + JsVariant::String(s) if !s.is_empty() => true, + JsVariant::Rational(n) if n != 0.0 && !n.is_nan() => true, + JsVariant::Integer(n) if n != 0 => true, + JsVariant::BigInt(n) if !n.is_zero() => true, + JsVariant::Boolean(v) => v, _ => false, } } @@ -298,28 +1010,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).cloned(); - 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).cloned(); + 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; } } @@ -334,9 +1045,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 { @@ -370,9 +1081,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()) @@ -386,10 +1095,13 @@ impl JsValue { /// /// [spec]: https://tc39.es/ecma262/#sec-tobigint pub fn to_bigint(&self, context: &mut Context) -> JsResult { - match self { - JsValue::Null => context.throw_type_error("cannot convert null to a BigInt"), - JsValue::Undefined => context.throw_type_error("cannot convert undefined to a BigInt"), - JsValue::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 { @@ -398,17 +1110,17 @@ impl JsValue { )) } } - JsValue::Boolean(true) => Ok(JsBigInt::one()), - JsValue::Boolean(false) => Ok(JsBigInt::zero()), - JsValue::Integer(_) | JsValue::Rational(_) => { + JsVariant::Boolean(true) => Ok(JsBigInt::one()), + JsVariant::Boolean(false) => Ok(JsBigInt::zero()), + JsVariant::Integer(_) | JsVariant::Rational(_) => { context.throw_type_error("cannot convert Number to a BigInt") } - JsValue::BigInt(b) => Ok(b.clone()), - JsValue::Object(_) => { + JsVariant::BigInt(b) => Ok(b.clone()), + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_bigint(context) } - JsValue::Symbol(_) => context.throw_type_error("cannot convert Symbol to a BigInt"), + JsVariant::Symbol(_) => context.throw_type_error("cannot convert Symbol to a BigInt"), } } @@ -438,16 +1150,16 @@ impl JsValue { /// /// This function is equivalent to `String(value)` in JavaScript. pub fn to_string(&self, context: &mut Context) -> JsResult { - match self { - JsValue::Null => Ok("null".into()), - JsValue::Undefined => Ok("undefined".into()), - JsValue::Boolean(boolean) => Ok(boolean.to_string().into()), - JsValue::Rational(rational) => Ok(Number::to_native_string(*rational).into()), - JsValue::Integer(integer) => Ok(integer.to_string().into()), - JsValue::String(string) => Ok(string.clone()), - JsValue::Symbol(_) => context.throw_type_error("can't convert symbol to string"), - JsValue::BigInt(ref bigint) => Ok(bigint.to_string().into()), - JsValue::Object(_) => { + match self.variant() { + JsVariant::Null => Ok("null".into()), + JsVariant::Undefined => Ok("undefined".into()), + JsVariant::Boolean(boolean) => Ok(boolean.to_string().into()), + JsVariant::Rational(rational) => Ok(Number::to_native_string(rational).into()), + JsVariant::Integer(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) } @@ -460,32 +1172,32 @@ impl JsValue { /// /// See: pub fn to_object(&self, context: &mut Context) -> JsResult { - match self { - JsValue::Undefined | JsValue::Null => { + match self.variant() { + JsVariant::Undefined | JsVariant::Null => { context.throw_type_error("cannot convert 'null' or 'undefined' to object") } - JsValue::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), )) } - JsValue::Integer(integer) => { + JsVariant::Integer(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)), )) } - JsValue::Rational(rational) => { + JsVariant::Rational(rational) => { let prototype = context.intrinsics().constructors().number().prototype(); Ok(JsObject::from_proto_and_data( prototype, - ObjectData::number(*rational), + ObjectData::number(rational), )) } - JsValue::String(ref string) => { + JsVariant::String(string) => { let prototype = context.intrinsics().constructors().string().prototype(); let object = @@ -501,14 +1213,14 @@ impl JsValue { ); Ok(object) } - JsValue::Symbol(ref symbol) => { + JsVariant::Symbol(symbol) => { let prototype = context.intrinsics().constructors().symbol().prototype(); Ok(JsObject::from_proto_and_data( prototype, ObjectData::symbol(symbol.clone()), )) } - JsValue::BigInt(ref bigint) => { + JsVariant::BigInt(bigint) => { let prototype = context .intrinsics() .constructors() @@ -519,7 +1231,7 @@ impl JsValue { ObjectData::big_int(bigint.clone()), )) } - JsValue::Object(jsobject) => Ok(jsobject.clone()), + JsVariant::Object(jsobject) => Ok(jsobject.clone()), } } @@ -527,16 +1239,19 @@ impl JsValue { /// /// See pub fn to_property_key(&self, context: &mut Context) -> JsResult { - Ok(match self { + Ok(match self.variant() { // Fast path: - JsValue::String(string) => string.clone().into(), - JsValue::Symbol(symbol) => symbol.clone().into(), + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), // Slow path: - _ => match self.to_primitive(context, PreferredType::String)? { - JsValue::String(ref string) => string.clone().into(), - JsValue::Symbol(ref symbol) => symbol.clone().into(), - primitive => primitive.to_string(context)?.into(), - }, + _ => { + let primitive = self.to_primitive(context, PreferredType::String)?; + match primitive.variant() { + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), + _ => primitive.to_string(context)?.into(), + } + } }) } @@ -558,7 +1273,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_i32() { return Ok(number as u32); } let number = self.to_number(context)?; @@ -571,7 +1286,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_i32() { return Ok(number); } let number = self.to_number(context)?; @@ -852,16 +1567,16 @@ impl JsValue { /// /// See: pub fn to_number(&self, context: &mut Context) -> JsResult { - match *self { - JsValue::Null => Ok(0.0), - JsValue::Undefined => Ok(f64::NAN), - JsValue::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), - JsValue::String(ref string) => Ok(string.string_to_number()), - JsValue::Rational(number) => Ok(number), - JsValue::Integer(integer) => Ok(f64::from(integer)), - JsValue::Symbol(_) => context.throw_type_error("argument must not be a symbol"), - JsValue::BigInt(_) => context.throw_type_error("argument must not be a bigint"), - JsValue::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::Rational(number) => Ok(number), + JsVariant::Integer(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) } @@ -921,15 +1636,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::Rational(_) | JsVariant::Integer(_) => "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 { @@ -965,7 +1680,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 15c9d6bc8b9..7ad7bd38b2b 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,54 +7,61 @@ 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: - (Self::Integer(x), Self::Integer(y)) => Self::new(f64::from(*x) + f64::from(*y)), - (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::Integer(x), JsVariant::Integer(y)) => { + Self::new(f64::from(x) + f64::from(y)) + } + (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x + y), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) + y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x + f64::from(y)), - (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)), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::add(x, 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)), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => Self::new(JsBigInt::add(x, 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)) => Self::new(f64::from(*x) - f64::from(*y)), - (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::Integer(x), JsVariant::Integer(y)) => { + Self::new(f64::from(x) - f64::from(y)) + } + (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x - y), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) - y), + (JsVariant::Rational(x), JsVariant::Integer(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)?) { @@ -71,14 +78,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)) => Self::new(f64::from(*x) * f64::from(*y)), - (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::Integer(x), JsVariant::Integer(y)) => { + Self::new(f64::from(x) * f64::from(y)) + } + (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x * y), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) * y), + (JsVariant::Rational(x), JsVariant::Integer(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)?) { @@ -95,14 +104,16 @@ 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)) => Self::new(f64::from(*x) / f64::from(*y)), - (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::Integer(x), JsVariant::Integer(y)) => { + Self::new(f64::from(x) / f64::from(y)) + } + (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x / y), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) / y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x / f64::from(y)), - (Self::BigInt(ref x), Self::BigInt(ref y)) => { + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => { if y.is_zero() { return context.throw_range_error("BigInt division by zero"); } @@ -129,33 +140,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 { + (JsVariant::Integer(x), JsVariant::Integer(y)) => { + if y == 0 { Self::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::Rational(x), JsVariant::Rational(y)) => Self::new((x % y).copysign(x)), + (JsVariant::Integer(x), JsVariant::Rational(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::Rational(x), JsVariant::Integer(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), @@ -176,14 +186,16 @@ 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)) => Self::new(f64::from(*x).powi(*y)), - (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)), + (JsVariant::Integer(x), JsVariant::Integer(y)) => Self::new(f64::from(x).powi(y)), + (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x.powf(y)), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x).powf(y)), + (JsVariant::Rational(x), JsVariant::Integer(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)?) { @@ -202,16 +214,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::Integer(x), JsVariant::Integer(y)) => Self::new(x & y), + (JsVariant::Rational(x), JsVariant::Rational(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::Integer(x), JsVariant::Rational(y)) => Self::new(x & f64_to_int32(y)), + (JsVariant::Rational(x), JsVariant::Integer(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)?) { @@ -232,16 +246,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::Integer(x), JsVariant::Integer(y)) => Self::new(x | y), + (JsVariant::Rational(x), JsVariant::Rational(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::Integer(x), JsVariant::Rational(y)) => Self::new(x | f64_to_int32(y)), + (JsVariant::Rational(x), JsVariant::Integer(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)?) { @@ -262,16 +278,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::Integer(x), JsVariant::Integer(y)) => Self::new(x ^ y), + (JsVariant::Rational(x), JsVariant::Rational(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::Integer(x), JsVariant::Rational(y)) => Self::new(x ^ f64_to_int32(y)), + (JsVariant::Rational(x), JsVariant::Integer(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)?) { @@ -292,18 +310,20 @@ 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::Integer(x), JsVariant::Integer(y)) => Self::new(x.wrapping_shl(y as u32)), + (JsVariant::Rational(x), JsVariant::Rational(y)) => { + Self::new(f64_to_int32(x).wrapping_shl(f64_to_uint32(y))) } - (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::Integer(x), JsVariant::Rational(y)) => { + Self::new(x.wrapping_shl(f64_to_uint32(y))) + } + (JsVariant::Rational(x), JsVariant::Integer(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)?) } @@ -326,18 +346,20 @@ 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::Integer(x), JsVariant::Integer(y)) => Self::new(x.wrapping_shr(y as u32)), + (JsVariant::Rational(x), JsVariant::Rational(y)) => { + Self::new(f64_to_int32(x).wrapping_shr(f64_to_uint32(y))) + } + (JsVariant::Integer(x), JsVariant::Rational(y)) => { + Self::new(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::Rational(x), JsVariant::Integer(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)?) } @@ -360,17 +382,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::Integer(x), JsVariant::Integer(y)) => { + Self::new((x as u32).wrapping_shr(y as u32)) + } + (JsVariant::Rational(x), JsVariant::Rational(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::Integer(x), JsVariant::Rational(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::Rational(x), JsVariant::Integer(y)) => { + Self::new(f64_to_uint32(x).wrapping_shr(y as u32)) } // Slow path: @@ -429,22 +453,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::Rational(num) => Self::new(-num), + JsVariant::Integer(num) if num == 0 => Self::new(-f64::from(0)), + JsVariant::Integer(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)), }) } @@ -476,13 +500,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::Integer(x), JsVariant::Integer(y)) => (x < y).into(), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Number::less_than(f64::from(x), y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Number::less_than(x, f64::from(y)), + (JsVariant::Rational(x), JsVariant::Rational(y)) => Number::less_than(x, y), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => (x < y).into(), // Slow path: (_, _) => { @@ -497,8 +521,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); } @@ -512,21 +536,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 9f8d6a75410..5fe322f0e3e 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::Rational(rat) => Ok(rat.into()), + JsVariant::Integer(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); @@ -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/tests.rs b/boa_engine/src/value/tests.rs index 1b880c40cff..68e86dfd65c 100644 --- a/boa_engine/src/value/tests.rs +++ b/boa_engine/src/value/tests.rs @@ -643,7 +643,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 @@ -660,7 +660,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 61e100bae6a..3504f2ca100 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::Rational(_) | JsVariant::Integer(_) => 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 8673e2de97a..9f78cccfbc7 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -645,7 +645,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 @@ -744,7 +744,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 @@ -920,7 +920,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 90639f1ccfc..7e7f9d7862e 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -5,7 +5,7 @@ use crate::{ builtins::{iterable::IteratorRecord, Array, ForInIterator, Number}, property::{DescriptorKind, PropertyDescriptor, PropertyKey}, - value::Numeric, + value::{JsVariant, Numeric}, vm::{ call_frame::CatchAddresses, code_block::{create_function_object, create_generator_function_object, Readable}, @@ -345,7 +345,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) @@ -355,7 +355,7 @@ impl Context { self.realm.environments.put_value_if_uninitialized( binding_locator.environment_index(), binding_locator.binding_index(), - JsValue::Undefined, + JsValue::undefined(), ); } } @@ -387,7 +387,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 => { @@ -946,8 +946,8 @@ impl Context { let func = self.vm.pop(); let mut 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"), }; @@ -980,8 +980,8 @@ impl Context { } arguments.append(&mut rest_arguments); - 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"), }; @@ -1193,7 +1193,7 @@ impl Context { let iterator = self.vm.pop(); if !done.as_boolean().expect("not a boolean") { let iterator_record = IteratorRecord::new(iterator, next_function); - iterator_record.close(Ok(JsValue::Null), self)?; + iterator_record.close(Ok(JsValue::null()), self)?; } } Opcode::IteratorToArray => { @@ -1374,7 +1374,7 @@ impl Context { self.vm.frame_mut().pc = done_address as usize; let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone()); - 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); @@ -1497,7 +1497,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 8a5fda7be06..cbca50d489a 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()) ); }