diff --git a/core/engine/src/builtins/array/tests.rs b/core/engine/src/builtins/array/tests.rs index 738bd93512f..5903f93d93f 100644 --- a/core/engine/src/builtins/array/tests.rs +++ b/core/engine/src/builtins/array/tests.rs @@ -953,3 +953,12 @@ fn array_sort() { "#}), ]); } + +#[test] +fn array_of_neg_zero() { + run_test_actions([ + TestAction::run("let arr = [-0, -0, -0, -0];"), + // Assert the parity of all items of the list. + TestAction::assert("arr.every(x => (1/x) === -Infinity)"), + ]); +} diff --git a/core/engine/src/value/conversions/mod.rs b/core/engine/src/value/conversions/mod.rs index daee6456064..982e2adf9a2 100644 --- a/core/engine/src/value/conversions/mod.rs +++ b/core/engine/src/value/conversions/mod.rs @@ -1,9 +1,9 @@ //! Conversions from JavaScript values into Rust values, and the other way around. +use super::{JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler}; +use crate::value::inner::InnerValue; use crate::{js_string, string::JsStr}; -use super::{InnerValue, JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler}; - mod either; mod serde_json; pub(super) mod try_from_js; @@ -15,7 +15,7 @@ impl From> for JsValue { fn from(value: JsStr<'_>) -> Self { let _timer = Profiler::global().start_event("From>", "value"); - Self::from_inner(InnerValue::String(value.into())) + Self::from_inner(InnerValue::string(value.into())) } } @@ -23,7 +23,7 @@ impl From for JsValue { fn from(value: JsString) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::String(value)) + Self::from_inner(InnerValue::string(value)) } } @@ -45,7 +45,7 @@ impl From for JsValue { fn from(value: JsSymbol) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::Symbol(value)) + Self::from_inner(InnerValue::symbol(value)) } } @@ -54,7 +54,7 @@ impl From for JsValue { fn from(value: f32) -> Self { let _timer = Profiler::global().start_event("From", "value"); - JsValue::from(f64::from(value)) + Self::rational(f64::from(value)) } } @@ -63,7 +63,7 @@ impl From for JsValue { fn from(value: f64) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::Float64(value)) + Self::rational(value) } } @@ -78,8 +78,8 @@ macro_rules! impl_from_integer { i32::try_from(value) .map_or_else( - |_| Self::from(value as f64), - |value| Self::from_inner(InnerValue::Integer32(value)), + |_| Self::rational(value as f64), + |value| Self::from_inner(InnerValue::integer32(value)), ) } } @@ -94,7 +94,7 @@ impl From for JsValue { fn from(value: JsBigInt) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::BigInt(value)) + Self::from_inner(InnerValue::bigint(value)) } } @@ -103,7 +103,7 @@ impl From for JsValue { fn from(value: bool) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::Boolean(value)) + Self::from_inner(InnerValue::boolean(value)) } } @@ -112,7 +112,7 @@ impl From for JsValue { fn from(object: JsObject) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::Object(object)) + Self::from_inner(InnerValue::object(object)) } } diff --git a/core/engine/src/value/conversions/serde_json.rs b/core/engine/src/value/conversions/serde_json.rs index cad9e9933cf..ddc6cf91746 100644 --- a/core/engine/src/value/conversions/serde_json.rs +++ b/core/engine/src/value/conversions/serde_json.rs @@ -1,13 +1,13 @@ //! This module implements the conversions from and into [`serde_json::Value`]. -use super::{InnerValue, JsValue}; +use super::JsValue; use crate::{ builtins::Array, error::JsNativeError, js_string, object::JsObject, property::{PropertyDescriptor, PropertyKey}, - Context, JsResult, + Context, JsResult, JsVariant, }; use serde_json::{Map, Value}; @@ -113,17 +113,17 @@ impl JsValue { /// /// Panics if the `JsValue` is `Undefined`. pub fn to_json(&self, context: &mut Context) -> JsResult { - match &self.inner { - InnerValue::Null => Ok(Value::Null), - InnerValue::Undefined => todo!("undefined to JSON"), - InnerValue::Boolean(b) => Ok(Value::from(*b)), - InnerValue::String(string) => Ok(string.to_std_string_escaped().into()), - InnerValue::Float64(rat) => Ok(Value::from(*rat)), - InnerValue::Integer32(int) => Ok(Value::from(*int)), - InnerValue::BigInt(_bigint) => Err(JsNativeError::typ() + match self.variant() { + JsVariant::Null => Ok(Value::Null), + JsVariant::Undefined => todo!("undefined to JSON"), + JsVariant::Boolean(b) => Ok(Value::from(b)), + JsVariant::String(string) => Ok(string.to_std_string_escaped().into()), + JsVariant::Float64(rat) => Ok(Value::from(rat)), + JsVariant::Integer32(int) => Ok(Value::from(int)), + JsVariant::BigInt(_bigint) => Err(JsNativeError::typ() .with_message("cannot convert bigint to JSON") .into()), - InnerValue::Object(obj) => { + JsVariant::Object(obj) => { let value_by_prop_key = |property_key, context: &mut Context| { obj.borrow() .properties() @@ -168,7 +168,7 @@ impl JsValue { Ok(Value::Object(map)) } } - InnerValue::Symbol(_sym) => Err(JsNativeError::typ() + JsVariant::Symbol(_sym) => Err(JsNativeError::typ() .with_message("cannot convert Symbol to JSON") .into()), } diff --git a/core/engine/src/value/conversions/try_from_js.rs b/core/engine/src/value/conversions/try_from_js.rs index 63844269612..77533f6a9f8 100644 --- a/core/engine/src/value/conversions/try_from_js.rs +++ b/core/engine/src/value/conversions/try_from_js.rs @@ -3,7 +3,6 @@ use num_bigint::BigInt; use num_traits::AsPrimitive; -use crate::value::InnerValue; use crate::{js_string, Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsValue}; mod collections; @@ -62,11 +61,12 @@ impl TryFromJs for String { impl TryFromJs for JsString { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match &value.inner { - InnerValue::String(s) => Ok(s.clone()), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a String") - .into()), + if let Some(s) = value.as_string() { + Ok(s.clone()) + } else { + Err(JsNativeError::typ() + .with_message("cannot convert value to a JsString") + .into()) } } } @@ -90,7 +90,7 @@ where T: TryFromJs, { fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { - let InnerValue::Object(object) = &value.inner else { + let Some(object) = &value.as_object() else { return Err(JsNativeError::typ() .with_message("cannot convert value to a Vec") .into()); @@ -119,33 +119,36 @@ where impl TryFromJs for JsObject { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match &value.inner { - InnerValue::Object(o) => Ok(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Ok(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("cannot convert value to a Object") - .into()), + .into()) } } } impl TryFromJs for JsBigInt { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match &value.inner { - InnerValue::BigInt(b) => Ok(b.clone()), - _ => Err(JsNativeError::typ() + if let Some(b) = value.as_bigint() { + Ok(b.clone()) + } else { + Err(JsNativeError::typ() .with_message("cannot convert value to a BigInt") - .into()), + .into()) } } } impl TryFromJs for BigInt { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match &value.inner { - InnerValue::BigInt(b) => Ok(b.as_inner().clone()), - _ => Err(JsNativeError::typ() + if let Some(b) = value.as_bigint() { + Ok(b.as_inner().clone()) + } else { + Err(JsNativeError::typ() .with_message("cannot convert value to a BigInt") - .into()), + .into()) } } } @@ -158,12 +161,14 @@ impl TryFromJs for JsValue { impl TryFromJs for f64 { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match &value.inner { - InnerValue::Integer32(i) => Ok((*i).into()), - InnerValue::Float64(r) => Ok(*r), - _ => Err(JsNativeError::typ() + if let Some(i) = value.0.as_integer32() { + Ok(f64::from(i)) + } else if let Some(f) = value.0.as_float64() { + Ok(f) + } else { + Err(JsNativeError::typ() .with_message("cannot convert value to a f64") - .into()), + .into()) } } } @@ -184,23 +189,25 @@ macro_rules! impl_try_from_js_integer { $( impl TryFromJs for $type { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match &value.inner { - InnerValue::Integer32(i) => (*i).try_into().map_err(|e| { + if let Some(i) = value.as_i32() { + i.try_into().map_err(|e| { JsNativeError::typ() .with_message(format!( concat!("cannot convert value to a ", stringify!($type), ": {}"), e) ) .into() - }), - InnerValue::Float64(f) => from_f64(*f).ok_or_else(|| { + }) + } else if let Some(f) = value.as_number() { + from_f64(f).ok_or_else(|| { JsNativeError::typ() .with_message(concat!("cannot convert value to a ", stringify!($type))) .into() - }), - _ => Err(JsNativeError::typ() + }) + } else { + Err(JsNativeError::typ() .with_message(concat!("cannot convert value to a ", stringify!($type))) - .into()), + .into()) } } } diff --git a/core/engine/src/value/equality.rs b/core/engine/src/value/equality.rs index 58f664ac8bc..c4c517ae3a4 100644 --- a/core/engine/src/value/equality.rs +++ b/core/engine/src/value/equality.rs @@ -1,5 +1,5 @@ -use super::{InnerValue, JsBigInt, JsObject, JsResult, JsValue, PreferredType}; -use crate::{builtins::Number, Context}; +use super::{JsBigInt, JsObject, JsResult, JsValue, PreferredType}; +use crate::{builtins::Number, Context, JsVariant}; impl JsValue { /// Strict equality comparison. @@ -13,20 +13,20 @@ impl JsValue { return false; } - match (&self.inner, &other.inner) { + match (self.variant(), other.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::equal(x, y). - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => JsBigInt::equal(x, y), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Number::equal(*x, *y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Number::equal(*x, f64::from(*y)), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Number::equal(f64::from(*x), *y), - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x == y, + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::equal(x, y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::equal(x, y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::equal(x, f64::from(y)), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::equal(f64::from(x), y), + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y, //Null has to be handled specially because "typeof null" returns object and if we managed //this without a special case we would compare self and other as if they were actually //objects which unfortunately fails //Specification Link: https://tc39.es/ecma262/#sec-typeof-operator - (InnerValue::Null, InnerValue::Null) => true, + (JsVariant::Null, JsVariant::Null) => true, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(self, other), @@ -45,21 +45,22 @@ impl JsValue { return Ok(self.strict_equals(other)); } - Ok(match (&self.inner, &other.inner) { + 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. - (InnerValue::Null, InnerValue::Undefined) - | (InnerValue::Undefined, InnerValue::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 ( - InnerValue::Integer32(_) | InnerValue::Float64(_), - InnerValue::String(_) | InnerValue::Boolean(_), + JsVariant::Integer32(_) | JsVariant::Float64(_), + JsVariant::String(_) | JsVariant::Boolean(_), ) - | (InnerValue::String(_), InnerValue::Integer32(_) | InnerValue::Float64(_)) => { + | (JsVariant::String(_), JsVariant::Integer32(_) | JsVariant::Float64(_)) => { let x = self.to_number(context)?; let y = other.to_number(context)?; Number::equal(x, y) @@ -69,34 +70,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. - (InnerValue::BigInt(ref a), InnerValue::String(ref b)) => JsBigInt::from_js_string(b) + (JsVariant::BigInt(a), JsVariant::String(b)) => JsBigInt::from_js_string(b) .as_ref() .map_or(false, |b| a == b), // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x. - (InnerValue::String(ref a), InnerValue::BigInt(ref b)) => JsBigInt::from_js_string(a) + (JsVariant::String(a), JsVariant::BigInt(b)) => JsBigInt::from_js_string(a) .as_ref() .map_or(false, |a| a == b), // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. - (InnerValue::Boolean(x), _) => { - return other.equals(&JsValue::new(i32::from(*x)), context) + (JsVariant::Boolean(x), _) => { + return other.equals(&JsValue::new(i32::from(x)), context) } // 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y). - (_, InnerValue::Boolean(y)) => { - return self.equals(&JsValue::new(i32::from(*y)), context) - } + (_, JsVariant::Boolean(y)) => return self.equals(&JsValue::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). ( - InnerValue::Object(_), - InnerValue::String(_) - | InnerValue::Float64(_) - | InnerValue::Integer32(_) - | InnerValue::BigInt(_) - | InnerValue::Symbol(_), + JsVariant::Object(_), + JsVariant::String(_) + | JsVariant::Float64(_) + | JsVariant::Integer32(_) + | JsVariant::BigInt(_) + | JsVariant::Symbol(_), ) => { let primitive = self.to_primitive(context, PreferredType::Default)?; return Ok(primitive @@ -107,12 +106,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. ( - InnerValue::String(_) - | InnerValue::Float64(_) - | InnerValue::Integer32(_) - | InnerValue::BigInt(_) - | InnerValue::Symbol(_), - InnerValue::Object(_), + JsVariant::String(_) + | JsVariant::Float64(_) + | JsVariant::Integer32(_) + | JsVariant::BigInt(_) + | JsVariant::Symbol(_), + JsVariant::Object(_), ) => { let primitive = other.to_primitive(context, PreferredType::Default)?; return Ok(primitive @@ -123,10 +122,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. - (InnerValue::BigInt(ref a), InnerValue::Float64(ref b)) => a == b, - (InnerValue::Float64(ref a), InnerValue::BigInt(ref b)) => a == b, - (InnerValue::BigInt(ref a), InnerValue::Integer32(ref b)) => a == b, - (InnerValue::Integer32(ref a), InnerValue::BigInt(ref b)) => a == b, + (JsVariant::BigInt(a), JsVariant::Float64(ref b)) => a == b, + (JsVariant::Float64(ref a), JsVariant::BigInt(b)) => a == b, + (JsVariant::BigInt(a), JsVariant::Integer32(ref b)) => a == b, + (JsVariant::Integer32(ref a), JsVariant::BigInt(b)) => a == b, // 13. Return false. _ => false, @@ -147,18 +146,14 @@ impl JsValue { return false; } - match (&x.inner, &y.inner) { + match (x.variant(), y.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::SameValue(x, y). - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => JsBigInt::same_value(x, y), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Number::same_value(*x, *y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Number::same_value(*x, f64::from(*y)) - } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - Number::same_value(f64::from(*x), *y) - } - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x == y, + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::same_value(x, y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::same_value(x, y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::same_value(x, f64::from(y)), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::same_value(f64::from(x), y), + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(x, y), @@ -180,19 +175,19 @@ impl JsValue { return false; } - match (&x.inner, &y.inner) { + match (x.variant(), y.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::SameValueZero(x, y). - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => JsBigInt::same_value_zero(x, y), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::same_value_zero(x, y), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Number::same_value_zero(*x, *y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Number::same_value_zero(*x, f64::from(*y)) + (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::same_value_zero(x, y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Number::same_value_zero(x, f64::from(y)) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - Number::same_value_zero(f64::from(*x), *y) + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + Number::same_value_zero(f64::from(x), y) } - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x == y, + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(x, y), @@ -201,13 +196,14 @@ impl JsValue { fn same_value_non_numeric(x: &Self, y: &Self) -> bool { debug_assert!(x.get_type() == y.get_type()); - match (&x.inner, &y.inner) { - (InnerValue::Null, InnerValue::Null) - | (InnerValue::Undefined, InnerValue::Undefined) => true, - (InnerValue::String(x), InnerValue::String(y)) => x == y, - (InnerValue::Boolean(x), InnerValue::Boolean(y)) => x == y, - (InnerValue::Object(x), InnerValue::Object(y)) => JsObject::equals(x, y), - (InnerValue::Symbol(x), InnerValue::Symbol(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(x), JsVariant::Object(y)) => JsObject::equals(x, y), + (JsVariant::Symbol(x), JsVariant::Symbol(y)) => x == y, _ => false, } } diff --git a/core/engine/src/value/hash.rs b/core/engine/src/value/hash.rs index 3becb2fcdab..0efac634fdc 100644 --- a/core/engine/src/value/hash.rs +++ b/core/engine/src/value/hash.rs @@ -1,5 +1,6 @@ -use super::{InnerValue, JsValue}; +use super::JsValue; use crate::builtins::Number; +use crate::JsVariant; use std::hash::{Hash, Hasher}; impl PartialEq for JsValue { @@ -36,16 +37,16 @@ impl Hash for RationalHashable { impl Hash for JsValue { fn hash(&self, state: &mut H) { - match self.inner { - InnerValue::Undefined => UndefinedHashable.hash(state), - InnerValue::Null => NullHashable.hash(state), - InnerValue::String(ref string) => string.hash(state), - InnerValue::Boolean(boolean) => boolean.hash(state), - InnerValue::Integer32(integer) => RationalHashable(f64::from(integer)).hash(state), - InnerValue::BigInt(ref bigint) => bigint.hash(state), - InnerValue::Float64(rational) => RationalHashable(rational).hash(state), - InnerValue::Symbol(ref symbol) => Hash::hash(symbol, state), - InnerValue::Object(ref object) => object.hash(state), + match self.variant() { + JsVariant::Undefined => UndefinedHashable.hash(state), + JsVariant::Null => NullHashable.hash(state), + JsVariant::String(string) => string.hash(state), + JsVariant::Boolean(boolean) => boolean.hash(state), + JsVariant::Integer32(integer) => RationalHashable(f64::from(integer)).hash(state), + JsVariant::BigInt(bigint) => bigint.hash(state), + JsVariant::Float64(rational) => RationalHashable(rational).hash(state), + JsVariant::Symbol(symbol) => Hash::hash(symbol, state), + JsVariant::Object(object) => object.hash(state), } } } diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs new file mode 100644 index 00000000000..bf6c00a31d3 --- /dev/null +++ b/core/engine/src/value/inner.rs @@ -0,0 +1,3 @@ +//! Module implementing the operations for the inner value of a `[super::JsValue]`. +mod nan_boxed; +pub(crate) use nan_boxed::NanBoxedValue as InnerValue; diff --git a/core/engine/src/value/inner/nan_boxed.rs b/core/engine/src/value/inner/nan_boxed.rs new file mode 100644 index 00000000000..ab55d7f144f --- /dev/null +++ b/core/engine/src/value/inner/nan_boxed.rs @@ -0,0 +1,906 @@ +//! This `[JsValue]` inner type is a NaN-boxed value, which is a 64-bits value +//! that can represent any JavaScript value. If the integer is a non-NaN value, +//! it will be stored as a 64-bits float. If it is a `f64::NAN` value, it will +//! be stored as a quiet `NaN` value. Subnormal numbers are regular float. +//! +//! For any other type of values, the value will be stored as a 51-bits non-zero +//! integer. +//! +//! In short, the memory layout of a NaN-boxed value is as follows: +//! +//! | Type of | Bit Layout | Comment | +//! |-------------------|------------|---------| +//! | `+Infinity` | `7FF0:0000:0000:0000` | | +//! | `-Infinity` | `FFF0:0000:0000:0000` | | +//! | `NAN` (quiet) | `7FF8:0000:0000:0000` | | +//! | `NAN` (signaling) | `FFF8:0000:0000:0000` | | +//! | `Undefined` | `7FF4:0000:0000:0000` | | +//! | `Null` | `7FF5:0000:0000:0000` | | +//! | `False` | `7FF6:0000:0000:0000` | | +//! | `True` | `7FF6:0000:0000:0001` | | +//! | `Integer32` | `7FF7:0000:IIII:IIII` | 32-bits integer. | +//! | `BigInt` | `7FF8:PPPP:PPPP:PPPP` | 48-bits pointer. Assumes non-null pointer. | +//! | `Object` | `7FFA:PPPP:PPPP:PPPP` | 48-bits pointer. | +//! | `Symbol` | `7FFC:PPPP:PPPP:PPPP` | 48-bits pointer. | +//! | `String` | `7FFE:PPPP:PPPP:PPPP` | 48-bits pointer. | +//! | `Float64` | Any other values. | | +//! +//! Another way to vizualize this is by looking at the bit layout of a NaN-boxed +//! value: +//! ```text +//! ....--<| The type of inner value is represented by this. +//! |..| | 1??0 - Pointer, where ?? is the subtype of pointer: +//! |..| | b00 - BigInt, b01 - Object, +//! |..| | b10 - Symbol, b11 - String. +//! |..| | If the pointer is null, then it is a NaN value. +//! |..| | 0??? - Non-pointer, where ??? is the subtype: +//! |..| | b100 - Undefined, b101 - Null, +//! |..| | b011 - Boolean, b110 - Integer32. +//! vvvv +//! bit index: 63 59 55 51 47 43 39 35 31 .. 3 0 +//! 0000 0000 0000 0000 0000 0000 0000 0000 0000 .. 0000 +//! +Inf 0111 1111 1111 0000 0000 0000 0000 0000 0000 .. 0000 +//! -Inf 1111 1111 1111 0000 0000 0000 0000 0000 0000 .. 0000 +//! NaN (q) 0111 1111 1111 1000 0000 0000 0000 0000 0000 .. 0000 +//! NaN (s) 1111 1111 1111 1000 0000 0000 0000 0000 0000 .. 0000 +//! Undefined 0111 1111 1111 0100 0000 0000 0000 0000 0000 .. 0000 +//! Null 0111 1111 1111 0101 0000 0000 0000 0000 0000 .. 0000 +//! False 0111 1111 1111 0110 0000 0000 0000 0000 0000 .. 0000 +//! True 0111 1111 1111 0110 0000 0000 0000 0000 0000 .. 0001 +//! Integer32 0111 1111 1111 0111 0000 0000 0000 0000 IIII .. IIII +//! BigInt 0111 1111 1111 1000 PPPP PPPP PPPP PPPP PPPP .. PPPP +//! Object 0111 1111 1111 1010 PPPP PPPP PPPP PPPP PPPP .. PPPP +//! Symbol 0111 1111 1111 1100 PPPP PPPP PPPP PPPP PPPP .. PPPP +//! String 0111 1111 1111 1110 PPPP PPPP PPPP PPPP PPPP .. PPPP +//! Float64 Any other value. +//! ``` +//! +//! The pointers are assumed to never be NULL, and as such no clash +//! with regular NAN should happen. +#![allow(clippy::inline_always)] + +use crate::{JsBigInt, JsObject, JsSymbol, JsVariant}; +use boa_gc::{custom_trace, Finalize, Trace}; +use boa_string::JsString; +use core::fmt; +use static_assertions::const_assert; + +/// Transform an `u64` into `f64`, by its bytes. This is necessary for +/// keeping the MSRV at 1.82, as `f64::from_bits` is not const until +/// 1.83. +#[inline(always)] +const fn f64_from_bits(bits: u64) -> f64 { + unsafe { std::mem::transmute(bits) } +} + +/// Transform a `f64` into `u64`, by its bytes. This is necessary for +/// keeping the MSRV at 1.82, as `f64::to_bits` is not const until +/// 1.83. +#[inline(always)] +const fn f64_to_bits(bits: f64) -> u64 { + unsafe { std::mem::transmute(bits) } +} + +/// Check that a float is a `NaN`. This is necessary for keeping the MSRV +/// at 1.82, as `f64::is_nan` is not const until 1.53. +#[inline(always)] +#[allow(clippy::eq_op, clippy::float_cmp)] +const fn f64_is_nan(f: f64) -> bool { + f != f +} + +// We cannot NaN-box pointers larger than 64 bits. +const_assert!(size_of::() <= size_of::()); + +// We cannot NaN-box pointers that are not 4-bytes aligned. +const_assert!(align_of::<*mut ()>() >= 4); + +/// Internal module for bit masks and constants. +/// +/// All bit magic is done here. +mod bits { + use super::{f64_from_bits, f64_is_nan, f64_to_bits}; + use boa_engine::{JsBigInt, JsObject, JsSymbol}; + use boa_string::JsString; + use std::ptr::NonNull; + + /// Undefined value in `u64`. + pub(super) const UNDEFINED: u64 = 0x7FF4_0000_0000_0000; + + /// Null value in `u64`. + pub(super) const NULL: u64 = 0x7FF5_0000_0000_0000; + + /// False value in `u64`. + pub(super) const FALSE: u64 = 0x7FF6_0000_0000_0000; + + /// True value in `u64`. + pub(super) const TRUE: u64 = 0x7FF6_0000_0000_0001; + + /// Integer32 start (zero) value in `u64`. + pub(super) const INTEGER32_ZERO: u64 = 0x7FF7_0000_0000_0000; + + /// Integer32 end (MAX) value in `u64`. + pub(super) const INTEGER32_MAX: u64 = 0x7FF7_0000_FFFF_FFFF; + + /// Pointer starting point in `u64`. + pub(super) const POINTER_START: u64 = 0x7FF8_0000_0000_0000; + + /// Pointer types mask in `u64`. + pub(super) const POINTER_MASK: u64 = 0x0000_FFFF_FFFF_FFFF; + + /// Pointer start point for `BigInt` in `u64`. + pub(super) const POINTER_BIGINT_START: u64 = POINTER_START | POINTER_TYPE_BIGINT; + /// Pointer end point for `BigInt` in `u64`. + pub(super) const POINTER_BIGINT_END: u64 = POINTER_START | POINTER_MASK | POINTER_TYPE_BIGINT; + /// Pointer start point for `JsObject` in `u64`. + pub(super) const POINTER_OBJECT_START: u64 = POINTER_START | POINTER_TYPE_OBJECT; + /// Pointer end point for `JsObject` in `u64`. + pub(super) const POINTER_OBJECT_END: u64 = POINTER_START | POINTER_MASK | POINTER_TYPE_OBJECT; + /// Pointer start point for `JsSymbol` in `u64`. + pub(super) const POINTER_SYMBOL_START: u64 = POINTER_START | POINTER_TYPE_SYMBOL; + /// Pointer end point for `JsSymbol` in `u64`. + pub(super) const POINTER_SYMBOL_END: u64 = POINTER_START | POINTER_MASK | POINTER_TYPE_SYMBOL; + /// Pointer start point for `JsString` in `u64`. + pub(super) const POINTER_STRING_START: u64 = POINTER_START | POINTER_TYPE_STRING; + /// Pointer end point for `JsString` in `u64`. + pub(super) const POINTER_STRING_END: u64 = POINTER_START | POINTER_MASK | POINTER_TYPE_STRING; + + /// Pointer mask for the type of the pointer. + pub(super) const POINTER_TYPE_MASK: u64 = 0x0007_0000_0000_0000; + + /// Pointer type value for `BigInt`. + pub(super) const POINTER_TYPE_BIGINT: u64 = 0x0000_0000_0000_0000; + + /// Pointer type value for `JsObject`. + pub(super) const POINTER_TYPE_OBJECT: u64 = 0x0004_0000_0000_0000; + + /// Pointer type value for `JsSymbol`. + pub(super) const POINTER_TYPE_SYMBOL: u64 = 0x0005_0000_0000_0000; + + /// Pointer type value for `JsString`. + pub(super) const POINTER_TYPE_STRING: u64 = 0x0006_0000_0000_0000; + + /// NAN value in `u64`. + pub(super) const NAN: u64 = 0x7FF8_0000_0000_0000; + + /// Checks that a value is a valid boolean (either true or false). + #[inline(always)] + pub(super) const fn is_bool(value: u64) -> bool { + value == TRUE || value == FALSE + } + + /// Checks that a value is a valid float, not a tagged nan boxed value. + #[inline(always)] + pub(super) const fn is_float(value: u64) -> bool { + let as_float = f64_from_bits(value); + !f64_is_nan(as_float) || value == NAN + } + + /// Checks that a value is a valid undefined. + #[inline(always)] + pub(super) const fn is_undefined(value: u64) -> bool { + value == UNDEFINED + } + + /// Checks that a value is a valid null. + #[inline(always)] + pub(super) const fn is_null(value: u64) -> bool { + value == NULL + } + + /// Checks that a value is a valid integer32. + #[inline(always)] + pub(super) const fn is_integer32(value: u64) -> bool { + value & INTEGER32_ZERO == INTEGER32_ZERO + } + + /// Untag a value as a pointer. + #[inline(always)] + pub(super) const fn is_pointer(value: u64) -> bool { + value & POINTER_START == POINTER_START + } + + /// Checks that a value is a valid `BigInt`. + #[inline(always)] + #[allow(clippy::verbose_bit_mask)] + pub(super) const fn is_bigint(value: u64) -> bool { + // If `(value & POINTER_MASK)` is zero, then it is NaN. + is_pointer(value) + && (value & POINTER_TYPE_MASK == POINTER_TYPE_BIGINT) + && (value & POINTER_MASK) != 0 + } + + /// Checks that a value is a valid Object. + #[inline(always)] + pub(super) const fn is_object(value: u64) -> bool { + is_pointer(value) && (value & POINTER_TYPE_MASK == POINTER_TYPE_OBJECT) + } + + /// Checks that a value is a valid Symbol. + #[inline(always)] + pub(super) const fn is_symbol(value: u64) -> bool { + is_pointer(value) && (value & POINTER_TYPE_MASK == POINTER_TYPE_SYMBOL) + } + + /// Checks that a value is a valid String. + #[inline(always)] + pub(super) const fn is_string(value: u64) -> bool { + is_pointer(value) && (value & POINTER_TYPE_MASK == POINTER_TYPE_STRING) + } + + /// Returns a tagged u64 of a 64-bits float. + #[inline(always)] + pub(super) const fn tag_f64(value: f64) -> u64 { + if f64_is_nan(value) { + // Reduce any NAN to a canonical NAN representation. + f64_to_bits(f64::NAN) + } else { + f64_to_bits(value) + } + } + + /// Returns a tagged u64 of a 32-bits integer. + #[inline(always)] + pub(super) const fn tag_i32(value: i32) -> u64 { + INTEGER32_ZERO | value as u64 & 0xFFFF_FFFFu64 + } + + /// Returns a i32-bits from a tagged integer. + #[inline(always)] + pub(super) const fn untag_i32(value: u64) -> i32 { + ((value & 0xFFFF_FFFFu64) | 0xFFFF_FFFF_0000_0000u64) as i32 + } + + /// Returns a tagged u64 of a boolean. + #[inline(always)] + pub(super) const fn tag_bool(value: bool) -> u64 { + if value { + TRUE + } else { + FALSE + } + } + + /// Returns a tagged u64 of a boxed `[JsBigInt]`. + /// + /// # Safety + /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will + /// result in a panic. Also, the object is not checked for validity. + /// + /// The box is forgotten after this operation. It must be dropped separately, + /// by calling `[Self::drop_pointer]`. + #[inline(always)] + #[allow(clippy::identity_op)] + pub(super) unsafe fn tag_bigint(value: Box) -> u64 { + let value = Box::into_raw(value) as u64; + let value_masked: u64 = value & POINTER_MASK; + + // Assert alignment and location of the pointer. + assert_eq!( + value_masked, value, + "Pointer is not 4-bits aligned or over 51-bits." + ); + // Cannot have a null pointer for bigint. + assert_ne!(value_masked, 0, "Pointer is NULL."); + + // Simply cast for bits. + POINTER_BIGINT_START | value_masked + } + + /// Returns a tagged u64 of a boxed `[JsObject]`. + /// + /// # Safety + /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will + /// result in a panic. Also, the object is not checked for validity. + /// + /// The box is forgotten after this operation. It must be dropped separately, + /// by calling `[Self::drop_pointer]`. + #[inline(always)] + pub(super) unsafe fn tag_object(value: Box) -> u64 { + let value = Box::into_raw(value) as u64; + let value_masked: u64 = value & POINTER_MASK; + + // Assert alignment and location of the pointer. + assert_eq!( + value_masked, value, + "Pointer is not 4-bits aligned or over 51-bits." + ); + // Cannot have a null pointer for bigint. + assert_ne!(value_masked, 0, "Pointer is NULL."); + + // Simply cast for bits. + POINTER_OBJECT_START | value_masked + } + + /// Returns a tagged u64 of a boxed `[JsSymbol]`. + /// + /// # Safety + /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will + /// result in a panic. Also, the object is not checked for validity. + /// + /// The box is forgotten after this operation. It must be dropped separately, + /// by calling `[Self::drop_pointer]`. + #[inline(always)] + pub(super) unsafe fn tag_symbol(value: Box) -> u64 { + let value = Box::into_raw(value) as u64; + let value_masked: u64 = value & POINTER_MASK; + + // Assert alignment and location of the pointer. + assert_eq!( + value_masked, value, + "Pointer is not 4-bits aligned or over 51-bits." + ); + // Cannot have a null pointer for bigint. + assert_ne!(value_masked, 0, "Pointer is NULL."); + + // Simply cast for bits. + POINTER_SYMBOL_START | value_masked + } + + /// Returns a tagged u64 of a boxed `[JsString]`. + /// + /// # Safety + /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will + /// result in a panic. Also, the object is not checked for validity. + /// + /// The box is forgotten after this operation. It must be dropped separately, + /// by calling `[Self::drop_pointer]`. + #[inline(always)] + pub(super) unsafe fn tag_string(value: Box) -> u64 { + let value = Box::into_raw(value) as u64; + let value_masked: u64 = value & POINTER_MASK; + + // Assert alignment and location of the pointer. + assert_eq!( + value_masked, value, + "Pointer is not 4-bits aligned or over 51-bits." + ); + // Cannot have a null pointer for bigint. + assert_ne!(value_masked, 0, "Pointer is NULL."); + + // Simply cast for bits. + POINTER_STRING_START | value_masked + } + + /// Returns a reference to T from a tagged value. + /// + /// # Safety + /// The pointer must be a valid pointer to a T on the heap, otherwise this + /// will result in undefined behavior. + #[inline(always)] + pub(super) const unsafe fn untag_pointer<'a, T>(value: u64) -> &'a T { + // This is safe since we already checked the pointer is not null as this point. + unsafe { NonNull::new_unchecked((value & POINTER_MASK) as *mut T).as_ref() } + } +} + +// Verify that all representations of NanBitTag ARE NAN, but don't match static NAN. +// The only exception to this rule is BigInt, which assumes that the pointer is +// non-null. The static f64::NAN is equal to BigInt. +const_assert!(f64_is_nan(f64_from_bits(bits::UNDEFINED))); +const_assert!(f64_is_nan(f64_from_bits(bits::NULL))); +const_assert!(f64_is_nan(f64_from_bits(bits::FALSE))); +const_assert!(f64_is_nan(f64_from_bits(bits::TRUE))); +const_assert!(f64_is_nan(f64_from_bits(bits::INTEGER32_ZERO))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_BIGINT_START))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_BIGINT_END))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_OBJECT_START))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_OBJECT_END))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_SYMBOL_START))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_SYMBOL_END))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_STRING_START))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_STRING_END))); + +/// A NaN-boxed `[JsValue]`'s inner. +pub(crate) struct NanBoxedValue(pub u64); + +impl fmt::Debug for NanBoxedValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.as_variant() { + JsVariant::Undefined => f.debug_tuple("Undefined").finish(), + JsVariant::Null => f.debug_tuple("Null").finish(), + JsVariant::Boolean(b) => f.debug_tuple("Boolean").field(&b).finish(), + JsVariant::Float64(n) => f.debug_tuple("Float64").field(&n).finish(), + JsVariant::Integer32(n) => f.debug_tuple("Integer32").field(&n).finish(), + JsVariant::BigInt(n) => f.debug_tuple("BigInt").field(&n).finish(), + JsVariant::Object(n) => f.debug_tuple("Object").field(&n).finish(), + JsVariant::Symbol(n) => f.debug_tuple("Symbol").field(&n).finish(), + JsVariant::String(n) => f.debug_tuple("String").field(&n).finish(), + } + } +} + +impl Finalize for NanBoxedValue { + fn finalize(&self) { + if let Some(o) = self.as_object() { + o.finalize(); + } + } +} + +#[allow(unsafe_op_in_unsafe_fn)] +unsafe impl Trace for NanBoxedValue { + custom_trace! {this, mark, { + if let Some(o) = this.as_object() { + mark(o); + } + }} +} + +impl Clone for NanBoxedValue { + #[inline(always)] + fn clone(&self) -> Self { + if let Some(o) = self.as_object() { + Self::object(o.clone()) + } else if let Some(b) = self.as_bigint() { + Self::bigint(b.clone()) + } else if let Some(s) = self.as_symbol() { + Self::symbol(s.clone()) + } else if let Some(s) = self.as_string() { + Self::string(s.clone()) + } else { + Self(self.0) + } + } +} + +impl NanBoxedValue { + /// Creates a new `InnerValue` from an u64 value without checking the validity + /// of the value. + #[must_use] + #[inline(always)] + const fn from_inner_unchecked(inner: u64) -> Self { + Self(inner) + } + + /// Returns a `InnerValue` from a Null. + #[must_use] + #[inline(always)] + pub(crate) const fn null() -> Self { + Self::from_inner_unchecked(bits::NULL) + } + + /// Returns a `InnerValue` from an undefined. + #[must_use] + #[inline(always)] + pub(crate) const fn undefined() -> Self { + Self::from_inner_unchecked(bits::UNDEFINED) + } + + /// Returns a `InnerValue` from a 64-bits float. If the float is `NaN`, + /// it will be reduced to a canonical `NaN` representation. + #[must_use] + #[inline(always)] + pub(crate) const fn float64(value: f64) -> Self { + Self::from_inner_unchecked(bits::tag_f64(value)) + } + + /// Returns a `InnerValue` from a 32-bits integer. + #[must_use] + #[inline(always)] + pub(crate) const fn integer32(value: i32) -> Self { + Self::from_inner_unchecked(bits::tag_i32(value)) + } + + /// Returns a `InnerValue` from a boolean. + #[must_use] + #[inline(always)] + pub(crate) const fn boolean(value: bool) -> Self { + Self::from_inner_unchecked(bits::tag_bool(value)) + } + + /// Returns a `InnerValue` from a boxed `[JsBigInt]`. + #[must_use] + #[inline(always)] + pub(crate) fn bigint(value: JsBigInt) -> Self { + Self::from_inner_unchecked(unsafe { bits::tag_bigint(Box::new(value)) }) + } + + /// Returns a `InnerValue` from a boxed `[JsObject]`. + #[must_use] + #[inline(always)] + pub(crate) fn object(value: JsObject) -> Self { + Self::from_inner_unchecked(unsafe { bits::tag_object(Box::new(value)) }) + } + + /// Returns a `InnerValue` from a boxed `[JsSymbol]`. + #[must_use] + #[inline(always)] + pub(crate) fn symbol(value: JsSymbol) -> Self { + Self::from_inner_unchecked(unsafe { bits::tag_symbol(Box::new(value)) }) + } + + /// Returns a `InnerValue` from a boxed `[JsString]`. + #[must_use] + #[inline(always)] + pub(crate) fn string(value: JsString) -> Self { + Self::from_inner_unchecked(unsafe { bits::tag_string(Box::new(value)) }) + } + + /// Returns true if a value is undefined. + #[must_use] + #[inline(always)] + pub(crate) const fn is_undefined(&self) -> bool { + bits::is_undefined(self.0) + } + + /// Returns true if a value is null. + #[must_use] + #[inline(always)] + pub(crate) const fn is_null(&self) -> bool { + bits::is_null(self.0) + } + + /// Returns true if a value is a boolean. + #[must_use] + #[inline(always)] + pub(crate) const fn is_bool(&self) -> bool { + bits::is_bool(self.0) + } + + /// Returns true if a value is a 64-bits float. + #[must_use] + #[inline(always)] + pub(crate) const fn is_float64(&self) -> bool { + bits::is_float(self.0) + } + + /// Returns true if a value is a 32-bits integer. + #[must_use] + #[inline(always)] + pub(crate) const fn is_integer32(&self) -> bool { + bits::is_integer32(self.0) + } + + /// Returns true if a value is a `[JsBigInt]`. A `NaN` will not match here. + #[must_use] + #[inline(always)] + pub(crate) const fn is_bigint(&self) -> bool { + bits::is_bigint(self.0) + } + + /// Returns true if a value is a boxed Object. + #[must_use] + #[inline(always)] + pub(crate) const fn is_object(&self) -> bool { + bits::is_object(self.0) + } + + /// Returns true if a value is a boxed Symbol. + #[must_use] + #[inline(always)] + pub(crate) const fn is_symbol(&self) -> bool { + bits::is_symbol(self.0) + } + + /// Returns true if a value is a boxed String. + #[must_use] + #[inline(always)] + pub(crate) const fn is_string(&self) -> bool { + bits::is_string(self.0) + } + + /// Returns the value as a f64 if it is a float. + #[must_use] + #[inline(always)] + pub(crate) const fn as_float64(&self) -> Option { + if self.is_float64() { + Some(f64_from_bits(self.0)) + } else { + None + } + } + + /// Returns the value as an i32 if it is an integer. + #[must_use] + #[inline(always)] + pub(crate) const fn as_integer32(&self) -> Option { + if self.is_integer32() { + Some(bits::untag_i32(self.0)) + } else { + None + } + } + + /// Returns the value as a boolean if it is a boolean. + #[must_use] + #[inline(always)] + pub(crate) const fn as_bool(&self) -> Option { + match self.0 { + bits::FALSE => Some(false), + bits::TRUE => Some(true), + _ => None, + } + } + + /// Returns the value as a boxed `[JsBigInt]`. + #[must_use] + #[inline(always)] + pub(crate) const fn as_bigint(&self) -> Option<&JsBigInt> { + if self.is_bigint() { + Some(unsafe { bits::untag_pointer::<'_, JsBigInt>(self.0) }) + } else { + None + } + } + + /// Returns the value as a boxed `[JsObject]`. + #[must_use] + #[inline(always)] + pub(crate) const fn as_object(&self) -> Option<&JsObject> { + if self.is_object() { + Some(unsafe { bits::untag_pointer::<'_, JsObject>(self.0) }) + } else { + None + } + } + + /// Returns the value as a boxed `[JsSymbol]`. + #[must_use] + #[inline(always)] + pub(crate) const fn as_symbol(&self) -> Option<&JsSymbol> { + if self.is_symbol() { + Some(unsafe { bits::untag_pointer::<'_, JsSymbol>(self.0) }) + } else { + None + } + } + + /// Returns the value as a boxed `[JsString]`. + #[must_use] + #[inline(always)] + pub(crate) const fn as_string(&self) -> Option<&JsString> { + if self.is_string() { + Some(unsafe { bits::untag_pointer::<'_, JsString>(self.0) }) + } else { + None + } + } + + /// Returns the `[JsVariant]` of this inner value. + #[must_use] + #[inline(always)] + pub(crate) const fn as_variant(&self) -> JsVariant<'_> { + match self.0 { + bits::UNDEFINED => JsVariant::Undefined, + bits::NULL => JsVariant::Null, + bits::FALSE => JsVariant::Boolean(false), + bits::TRUE => JsVariant::Boolean(true), + bits::INTEGER32_ZERO..=bits::INTEGER32_MAX => { + JsVariant::Integer32(bits::untag_i32(self.0)) + } + bits::NAN => JsVariant::Float64(f64::NAN), + bits::POINTER_BIGINT_START..=bits::POINTER_BIGINT_END => { + JsVariant::BigInt(unsafe { bits::untag_pointer(self.0) }) + } + bits::POINTER_OBJECT_START..=bits::POINTER_OBJECT_END => { + JsVariant::Object(unsafe { bits::untag_pointer(self.0) }) + } + bits::POINTER_SYMBOL_START..=bits::POINTER_SYMBOL_END => { + JsVariant::Symbol(unsafe { bits::untag_pointer(self.0) }) + } + bits::POINTER_STRING_START..=bits::POINTER_STRING_END => { + JsVariant::String(unsafe { bits::untag_pointer(self.0) }) + } + _ => JsVariant::Float64(f64_from_bits(self.0)), + } + } +} + +impl Drop for NanBoxedValue { + fn drop(&mut self) { + let maybe_ptr = self.0 & bits::POINTER_MASK; + + // Drop the pointer if it is a pointer. + if self.is_object() { + drop(unsafe { Box::from_raw(maybe_ptr as *mut JsObject) }); + } else if self.is_bigint() { + drop(unsafe { Box::from_raw(maybe_ptr as *mut JsBigInt) }); + } else if self.is_symbol() { + drop(unsafe { Box::from_raw(maybe_ptr as *mut JsSymbol) }); + } else if self.is_string() { + drop(unsafe { Box::from_raw(maybe_ptr as *mut JsString) }); + } + } +} + +#[cfg(test)] +macro_rules! assert_type { + (@@is $value: ident, $u: literal, $n: literal, $b: literal, $i: literal, $f: literal, $bi: literal, $s: literal, $o: literal, $sy: literal) => { + assert_eq!($u != 0, $value.is_undefined()); + assert_eq!($n != 0, $value.is_null()); + assert_eq!($b != 0, $value.is_bool()); + assert_eq!($i != 0, $value.is_integer32()); + assert_eq!($f != 0, $value.is_float64()); + assert_eq!($bi != 0, $value.is_bigint()); + assert_eq!($s != 0, $value.is_string()); + assert_eq!($o != 0, $value.is_object()); + assert_eq!($sy != 0, $value.is_symbol()); + }; + (@@as $value: ident, $u: literal, $n: literal, $b: literal, $i: literal, $f: literal, $bi: literal, $s: literal, $o: literal, $sy: literal) => { + if $b == 0 { assert_eq!($value.as_bool(), None); } + if $i == 0 { assert_eq!($value.as_integer32(), None); } + if $f == 0 { assert_eq!($value.as_float64(), None); } + if $bi == 0 { assert_eq!($value.as_bigint(), None); } + if $s == 0 { assert_eq!($value.as_string(), None); } + if $o == 0 { assert_eq!($value.as_object(), None); } + if $sy == 0 { assert_eq!($value.as_symbol(), None); } + }; + ($value: ident is undefined) => { + assert_type!(@@is $value, 1, 0, 0, 0, 0, 0, 0, 0, 0); + assert_eq!($value.as_variant(), JsVariant::Undefined); + }; + ($value: ident is null) => { + assert_type!(@@is $value, 0, 1, 0, 0, 0, 0, 0, 0, 0); + assert_eq!($value.as_variant(), JsVariant::Null); + }; + ($value: ident is bool($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 1, 0, 0, 0, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 1, 0, 0, 0, 0, 0, 0); + assert_eq!(Some($scalar), $value.as_bool()); + assert_eq!($value.as_variant(), JsVariant::Boolean($scalar)); + }; + ($value: ident is integer($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 1, 0, 0, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 1, 0, 0, 0, 0, 0); + assert_eq!(Some($scalar), $value.as_integer32()); + assert_eq!($value.as_variant(), JsVariant::Integer32($scalar)); + }; + ($value: ident is float($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); + assert_eq!(Some($scalar), $value.as_float64()); + // Verify parity. + assert_eq!(Some(1.0 / $scalar), $value.as_float64().map(|f| 1.0 / f)); + assert_eq!($value.as_variant(), JsVariant::Float64($scalar)); + + // Verify that the clone is still the same. + let new_value = $value.clone(); + + assert_eq!(Some($scalar), new_value.as_float64()); + assert_eq!($value.as_float64(), new_value.as_float64()); + // Verify parity. + assert_eq!(Some(1.0 / $scalar), new_value.as_float64().map(|f| 1.0 / f)); + assert_eq!(new_value.as_variant(), JsVariant::Float64($scalar)); + + let JsVariant::Float64(new_scalar) = new_value.as_variant() else { + panic!("Expected Float64, got {:?}", new_value.as_variant()); + }; + assert_eq!(Some(new_scalar), new_value.as_float64()); + assert_eq!($value.as_float64(), new_value.as_float64()); + // Verify parity. + assert_eq!(Some(1.0 / new_scalar), new_value.as_float64().map(|f| 1.0 / f)); + assert_eq!(new_value.as_variant(), JsVariant::Float64(new_scalar)); + }; + ($value: ident is nan) => { + assert_type!(@@is $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); + assert!($value.as_float64().unwrap().is_nan()); + assert!(matches!($value.as_variant(), JsVariant::Float64(f) if f.is_nan())); + }; + ($value: ident is bigint($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 0, 1, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 0, 1, 0, 0, 0); + assert_eq!(Some(&$scalar), $value.as_bigint()); + assert_eq!($value.as_variant(), JsVariant::BigInt(&$scalar)); + }; + ($value: ident is object($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 0, 1, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 0, 1, 0); + assert_eq!(Some(&$scalar), $value.as_object()); + assert_eq!($value.as_variant(), JsVariant::Object(&$scalar)); + }; + ($value: ident is symbol($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 0, 0, 1); + assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 0, 0, 1); + assert_eq!(Some(&$scalar), $value.as_symbol()); + assert_eq!($value.as_variant(), JsVariant::Symbol(&$scalar)); + }; + ($value: ident is string($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 1, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 1, 0, 0); + assert_eq!(Some(&$scalar), $value.as_string()); + assert_eq!($value.as_variant(), JsVariant::String(&$scalar)); + }; +} + +#[test] +fn null() { + let v = NanBoxedValue::null(); + assert_type!(v is null); +} + +#[test] +fn undefined() { + let v = NanBoxedValue::undefined(); + assert_type!(v is undefined); +} + +#[test] +fn boolean() { + let v = NanBoxedValue::boolean(true); + assert_type!(v is bool(true)); + + let v = NanBoxedValue::boolean(false); + assert_type!(v is bool(false)); +} + +#[test] +fn integer() { + fn assert_integer(i: i32) { + let v = NanBoxedValue::integer32(i); + assert_type!(v is integer(i)); + } + + assert_integer(0); + assert_integer(1); + assert_integer(-1); + assert_integer(42); + assert_integer(-42); + assert_integer(i32::MAX); + assert_integer(i32::MIN); + assert_integer(i32::MAX - 1); + assert_integer(i32::MIN + 1); +} + +#[test] +#[allow(clippy::float_cmp)] +fn float() { + fn assert_float(f: f64) { + let v = NanBoxedValue::float64(f); + assert_type!(v is float(f)); + } + + assert_float(0.0); + assert_float(-0.0); + assert_float(0.1 + 0.2); + assert_float(-42.123); + assert_float(f64::INFINITY); + assert_float(f64::NEG_INFINITY); + + // Some edge cases around zeroes. + let neg_zero = NanBoxedValue::float64(-0.0); + assert!(neg_zero.as_float64().unwrap().is_sign_negative()); + assert_eq!(0.0f64, neg_zero.as_float64().unwrap()); + + let pos_zero = NanBoxedValue::float64(0.0); + assert!(!pos_zero.as_float64().unwrap().is_sign_negative()); + assert_eq!(0.0f64, pos_zero.as_float64().unwrap()); + + assert_eq!(pos_zero.as_float64(), neg_zero.as_float64()); + + let nan = NanBoxedValue::float64(f64::NAN); + assert_type!(nan is nan); +} + +#[test] +fn bigint() { + let bigint = JsBigInt::from(42); + let v = NanBoxedValue::bigint(bigint.clone()); + assert_type!(v is bigint(bigint)); +} + +#[test] +fn object() { + let object = JsObject::with_null_proto(); + let v = NanBoxedValue::object(object.clone()); + assert_type!(v is object(object)); +} + +#[test] +fn string() { + let str = crate::js_string!("Hello World"); + let v = NanBoxedValue::string(str.clone()); + assert_type!(v is string(str)); +} + +#[test] +fn symbol() { + let sym = JsSymbol::new(Some(JsString::from("Hello World"))).unwrap(); + let v = NanBoxedValue::symbol(sym.clone()); + assert_type!(v is symbol(sym)); + + let sym = JsSymbol::new(None).unwrap(); + let v = NanBoxedValue::symbol(sym.clone()); + assert_type!(v is symbol(sym)); +} diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index 0d55e3fc781..251c79d61cf 100644 --- a/core/engine/src/value/mod.rs +++ b/core/engine/src/value/mod.rs @@ -13,7 +13,7 @@ use num_integer::Integer; use num_traits::{ToPrimitive, Zero}; use once_cell::sync::Lazy; -use boa_gc::{custom_trace, Finalize, Trace}; +use boa_gc::{Finalize, Trace}; #[doc(inline)] pub use boa_macros::TryFromJs; pub use boa_macros::TryIntoJs; @@ -47,6 +47,7 @@ mod conversions; pub(crate) mod display; mod equality; mod hash; +mod inner; mod integer; mod operations; mod r#type; @@ -65,32 +66,6 @@ static TWO_E_63: Lazy = Lazy::new(|| { BigInt::from(TWO_E_63) }); -/// The Inner type of [`JsValue`]. This is the actual value that the `JsValue` holds. -/// This is not a public API and should not be used directly. -/// -/// If you need access to the variant, use [`JsValue::variant`] instead. -#[derive(Finalize, Debug, Clone, PartialEq)] -enum InnerValue { - Null, - Undefined, - Boolean(bool), - Float64(f64), - Integer32(i32), - BigInt(JsBigInt), - String(JsString), - Symbol(JsSymbol), - Object(JsObject), -} - -#[allow(unsafe_op_in_unsafe_fn)] -unsafe impl Trace for InnerValue { - custom_trace! {this, mark, { - if let Self::Object(o) = this { - mark(o); - } - }} -} - /// A generic Javascript value. This can be any ECMAScript language valid value. /// /// This is a wrapper around the actual value, which is stored in an opaque type. @@ -103,17 +78,18 @@ unsafe impl Trace for InnerValue { /// assert_eq!(value.to_string(&mut context), Ok(js_string!("3"))); /// ``` #[derive(Finalize, Debug, Clone, Trace)] -pub struct JsValue { - inner: InnerValue, -} +pub struct JsValue(inner::InnerValue); impl JsValue { /// Create a new [`JsValue`] from an inner value. - const fn from_inner(inner: InnerValue) -> Self { - Self { inner } + #[inline] + const fn from_inner(inner: inner::InnerValue) -> Self { + Self(inner) } /// Create a new [`JsValue`]. + #[inline] + #[must_use] pub fn new(value: T) -> Self where T: Into, @@ -124,79 +100,71 @@ impl JsValue { /// Return the variant of this value. #[inline] #[must_use] - pub fn variant(&self) -> JsVariant<'_> { - (&self.inner).into() + pub const fn variant(&self) -> JsVariant<'_> { + self.0.as_variant() } /// Creates a new `undefined` value. #[inline] #[must_use] pub const fn undefined() -> Self { - Self::from_inner(InnerValue::Undefined) + Self::from_inner(inner::InnerValue::undefined()) } /// Creates a new `null` value. #[inline] #[must_use] pub const fn null() -> Self { - Self::from_inner(InnerValue::Null) + Self::from_inner(inner::InnerValue::null()) } /// Creates a new number with `NaN` value. #[inline] #[must_use] pub const fn nan() -> Self { - Self::from_inner(InnerValue::Float64(f64::NAN)) + Self::from_inner(inner::InnerValue::float64(f64::NAN)) } /// Creates a new number with `Infinity` value. #[inline] #[must_use] pub const fn positive_infinity() -> Self { - Self::from_inner(InnerValue::Float64(f64::INFINITY)) + Self::from_inner(inner::InnerValue::float64(f64::INFINITY)) } /// Creates a new number with `-Infinity` value. #[inline] #[must_use] pub const fn negative_infinity() -> Self { - Self::from_inner(InnerValue::Float64(f64::NEG_INFINITY)) + Self::from_inner(inner::InnerValue::float64(f64::NEG_INFINITY)) } /// Creates a new number from a float. - #[inline] + // #[inline] #[must_use] - pub const fn rational(rational: f64) -> Self { - Self::from_inner(InnerValue::Float64(rational)) + pub fn rational(rational: f64) -> Self { + Self::from_inner(inner::InnerValue::float64(rational)) } /// Returns true if the value is an object. #[inline] #[must_use] pub const fn is_object(&self) -> bool { - matches!(self.inner, InnerValue::Object(_)) + self.0.is_object() } /// Returns the object if the value is object, otherwise `None`. #[inline] #[must_use] pub const fn as_object(&self) -> Option<&JsObject> { - if let InnerValue::Object(obj) = &self.inner { - Some(obj) - } else { - None - } + self.0.as_object() } /// Consumes the value and return the inner object if it was an object. #[inline] #[must_use] pub fn into_object(self) -> Option { - if let InnerValue::Object(ref obj) = self.inner { - Some(obj.clone()) - } else { - None - } + self.0.as_object().cloned() } /// It determines if the value is a callable function with a `[[Call]]` internal method. @@ -208,22 +176,14 @@ impl JsValue { #[inline] #[must_use] pub fn is_callable(&self) -> bool { - if let InnerValue::Object(obj) = &self.inner { - obj.is_callable() - } else { - false - } + self.as_object().map_or(false, JsObject::is_callable) } /// Returns the callable value if the value is callable, otherwise `None`. #[inline] #[must_use] pub fn as_callable(&self) -> Option<&JsObject> { - if let InnerValue::Object(obj) = &self.inner { - obj.is_callable().then_some(obj) - } else { - None - } + self.as_object().filter(|obj| obj.is_callable()) } /// Returns a [`JsFunction`] if the value is callable, otherwise `None`. @@ -240,7 +200,7 @@ impl JsValue { #[inline] #[must_use] pub fn is_constructor(&self) -> bool { - matches!(&self.inner, InnerValue::Object(obj) if obj.is_constructor()) + self.as_object().map_or(false, JsObject::is_constructor) } /// Returns the constructor if the value is a constructor, otherwise `None`. @@ -254,22 +214,14 @@ impl JsValue { #[inline] #[must_use] pub fn is_promise(&self) -> bool { - if let InnerValue::Object(obj) = &self.inner { - obj.is::() - } else { - false - } + self.as_object().map_or(false, |obj| obj.is::()) } /// Returns the value as an object if the value is a promise, otherwise `None`. #[inline] #[must_use] pub(crate) fn as_promise_object(&self) -> Option<&JsObject> { - if let InnerValue::Object(obj) = &self.inner { - obj.is::().then_some(obj) - } else { - None - } + self.as_object().filter(|obj| obj.is::()) } /// Returns the value as a promise if the value is a promise, otherwise `None`. @@ -285,11 +237,7 @@ impl JsValue { #[inline] #[must_use] pub fn is_regexp(&self) -> bool { - if let InnerValue::Object(obj) = &self.inner { - obj.is::() - } else { - false - } + self.as_object().map_or(false, |obj| obj.is::()) } /// Returns the value as a regular expression if the value is a regexp, otherwise `None`. @@ -306,32 +254,28 @@ impl JsValue { #[inline] #[must_use] pub const fn is_symbol(&self) -> bool { - matches!(self.inner, InnerValue::Symbol(_)) + self.0.is_symbol() } /// Returns the symbol if the value is a symbol, otherwise `None`. #[inline] #[must_use] pub fn as_symbol(&self) -> Option { - if let InnerValue::Symbol(symbol) = &self.inner { - Some(symbol.clone()) - } else { - None - } + self.0.as_symbol().cloned() } /// Returns true if the value is undefined. #[inline] #[must_use] pub const fn is_undefined(&self) -> bool { - matches!(&self.inner, InnerValue::Undefined) + self.0.is_undefined() } /// Returns true if the value is null. #[inline] #[must_use] pub const fn is_null(&self) -> bool { - matches!(self.inner, InnerValue::Null) + self.0.is_null() } /// Returns true if the value is null or undefined. @@ -351,14 +295,17 @@ impl JsValue { #[must_use] #[allow(clippy::float_cmp)] pub const fn as_i32(&self) -> Option { - match self.inner { - InnerValue::Integer32(integer) => Some(integer), - // If it can fit in a i32 and the truncated version is - // equal to the original then it is an integer. - InnerValue::Float64(rational) if rational == ((rational as i32) as f64) => { + if let Some(integer) = self.0.as_integer32() { + Some(integer) + } else if let Some(rational) = self.0.as_float64() { + // Use this poor-man's check as `[f64::fract]` isn't const. + if rational == ((rational as i32) as f64) { Some(rational as i32) + } else { + None } - _ => None, + } else { + None } } @@ -366,19 +313,16 @@ impl JsValue { #[inline] #[must_use] pub const fn is_number(&self) -> bool { - matches!( - self.inner, - InnerValue::Float64(_) | InnerValue::Integer32(_) - ) + self.0.is_integer32() || self.0.is_float64() } /// Returns the number if the value is a number, otherwise `None`. #[inline] #[must_use] pub fn as_number(&self) -> Option { - match self.inner { - InnerValue::Integer32(integer) => Some(integer.into()), - InnerValue::Float64(rational) => Some(rational), + match self.variant() { + JsVariant::Integer32(i) => Some(f64::from(i)), + JsVariant::Float64(f) => Some(f), _ => None, } } @@ -387,52 +331,42 @@ impl JsValue { #[inline] #[must_use] pub const fn is_string(&self) -> bool { - matches!(self.inner, InnerValue::String(_)) + self.0.is_string() } /// Returns the string if the value is a string, otherwise `None`. #[inline] #[must_use] pub const fn as_string(&self) -> Option<&JsString> { - if let InnerValue::String(string) = &self.inner { - Some(string) - } else { - None - } + self.0.as_string() } /// Returns true if the value is a boolean. #[inline] #[must_use] pub const fn is_boolean(&self) -> bool { - matches!(self.inner, InnerValue::Boolean(_)) + self.0.is_bool() } /// Returns the boolean if the value is a boolean, otherwise `None`. #[inline] #[must_use] pub const fn as_boolean(&self) -> Option { - match &self.inner { - InnerValue::Boolean(boolean) => Some(*boolean), - _ => None, - } + self.0.as_bool() } /// Returns true if the value is a bigint. #[inline] #[must_use] pub const fn is_bigint(&self) -> bool { - matches!(self.inner, InnerValue::BigInt(_)) + self.0.is_bigint() } /// Returns an optional reference to a `BigInt` if the value is a `BigInt` primitive. #[inline] #[must_use] pub const fn as_bigint(&self) -> Option<&JsBigInt> { - match &self.inner { - InnerValue::BigInt(bigint) => Some(bigint), - _ => None, - } + self.0.as_bigint() } /// Converts the value to a `bool` type. @@ -443,13 +377,13 @@ impl JsValue { /// [spec]: https://tc39.es/ecma262/#sec-toboolean #[must_use] pub fn to_boolean(&self) -> bool { - match self.inner { - InnerValue::Symbol(_) | InnerValue::Object(_) => true, - InnerValue::String(ref s) if !s.is_empty() => true, - InnerValue::Float64(n) if n != 0.0 && !n.is_nan() => true, - InnerValue::Integer32(n) if n != 0 => true, - InnerValue::BigInt(ref n) if !n.is_zero() => true, - InnerValue::Boolean(v) => v, + match self.variant() { + JsVariant::Symbol(_) | JsVariant::Object(_) => true, + JsVariant::String(s) if !s.is_empty() => true, + JsVariant::Float64(n) if n != 0.0 && !n.is_nan() => true, + JsVariant::Integer32(n) if n != 0 => true, + JsVariant::BigInt(n) if !n.is_zero() => true, + JsVariant::Boolean(v) => v, _ => false, } } @@ -517,14 +451,14 @@ impl JsValue { /// /// [spec]: https://tc39.es/ecma262/#sec-tobigint pub fn to_bigint(&self, context: &mut Context) -> JsResult { - match &self.inner { - InnerValue::Null => Err(JsNativeError::typ() + match self.variant() { + JsVariant::Null => Err(JsNativeError::typ() .with_message("cannot convert null to a BigInt") .into()), - InnerValue::Undefined => Err(JsNativeError::typ() + JsVariant::Undefined => Err(JsNativeError::typ() .with_message("cannot convert undefined to a BigInt") .into()), - InnerValue::String(ref string) => JsBigInt::from_js_string(string).map_or_else( + JsVariant::String(string) => JsBigInt::from_js_string(string).map_or_else( || { Err(JsNativeError::syntax() .with_message(format!( @@ -535,17 +469,17 @@ impl JsValue { }, Ok, ), - InnerValue::Boolean(true) => Ok(JsBigInt::one()), - InnerValue::Boolean(false) => Ok(JsBigInt::zero()), - InnerValue::Integer32(_) | InnerValue::Float64(_) => Err(JsNativeError::typ() + JsVariant::Boolean(true) => Ok(JsBigInt::one()), + JsVariant::Boolean(false) => Ok(JsBigInt::zero()), + JsVariant::Integer32(_) | JsVariant::Float64(_) => Err(JsNativeError::typ() .with_message("cannot convert Number to a BigInt") .into()), - InnerValue::BigInt(b) => Ok(b.clone()), - InnerValue::Object(_) => { + JsVariant::BigInt(b) => Ok(b.clone()), + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_bigint(context) } - InnerValue::Symbol(_) => Err(JsNativeError::typ() + JsVariant::Symbol(_) => Err(JsNativeError::typ() .with_message("cannot convert Symbol to a BigInt") .into()), } @@ -578,19 +512,19 @@ impl JsValue { /// /// This function is equivalent to `String(value)` in JavaScript. pub fn to_string(&self, context: &mut Context) -> JsResult { - match self.inner { - InnerValue::Null => Ok(js_string!("null")), - InnerValue::Undefined => Ok(js_string!("undefined")), - InnerValue::Boolean(true) => Ok(js_string!("true")), - InnerValue::Boolean(false) => Ok(js_string!("false")), - InnerValue::Float64(rational) => Ok(JsString::from(rational)), - InnerValue::Integer32(integer) => Ok(JsString::from(integer)), - InnerValue::String(ref string) => Ok(string.clone()), - InnerValue::Symbol(_) => Err(JsNativeError::typ() + match self.variant() { + JsVariant::Null => Ok(js_string!("null")), + JsVariant::Undefined => Ok(js_string!("undefined")), + JsVariant::Boolean(true) => Ok(js_string!("true")), + JsVariant::Boolean(false) => Ok(js_string!("false")), + JsVariant::Float64(rational) => Ok(JsString::from(rational)), + JsVariant::Integer32(integer) => Ok(JsString::from(integer)), + JsVariant::String(string) => Ok(string.clone()), + JsVariant::Symbol(_) => Err(JsNativeError::typ() .with_message("can't convert symbol to string") .into()), - InnerValue::BigInt(ref bigint) => Ok(bigint.to_string().into()), - InnerValue::Object(_) => { + JsVariant::BigInt(bigint) => Ok(bigint.to_string().into()), + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::String)?; primitive.to_string(context) } @@ -603,41 +537,41 @@ impl JsValue { /// /// See: pub fn to_object(&self, context: &mut Context) -> JsResult { - match &self.inner { - InnerValue::Undefined | InnerValue::Null => Err(JsNativeError::typ() + match self.variant() { + JsVariant::Undefined | JsVariant::Null => Err(JsNativeError::typ() .with_message("cannot convert 'null' or 'undefined' to object") .into()), - InnerValue::Boolean(boolean) => Ok(context + JsVariant::Boolean(boolean) => Ok(context .intrinsics() .templates() .boolean() - .create(*boolean, Vec::default())), - InnerValue::Integer32(integer) => Ok(context + .create(boolean, Vec::default())), + JsVariant::Integer32(integer) => Ok(context .intrinsics() .templates() .number() - .create(f64::from(*integer), Vec::default())), - InnerValue::Float64(rational) => Ok(context + .create(f64::from(integer), Vec::default())), + JsVariant::Float64(rational) => Ok(context .intrinsics() .templates() .number() - .create(*rational, Vec::default())), - InnerValue::String(ref string) => Ok(context + .create(rational, Vec::default())), + JsVariant::String(string) => Ok(context .intrinsics() .templates() .string() .create(string.clone(), vec![string.len().into()])), - InnerValue::Symbol(ref symbol) => Ok(context + JsVariant::Symbol(symbol) => Ok(context .intrinsics() .templates() .symbol() .create(symbol.clone(), Vec::default())), - InnerValue::BigInt(ref bigint) => Ok(context + JsVariant::BigInt(bigint) => Ok(context .intrinsics() .templates() .bigint() .create(bigint.clone(), Vec::default())), - InnerValue::Object(jsobject) => Ok(jsobject.clone()), + JsVariant::Object(jsobject) => Ok(jsobject.clone()), } } @@ -645,18 +579,18 @@ impl JsValue { /// /// See pub fn to_property_key(&self, context: &mut Context) -> JsResult { - Ok(match &self.inner { + Ok(match self.variant() { // Fast path: - InnerValue::String(string) => string.clone().into(), - InnerValue::Symbol(symbol) => symbol.clone().into(), - InnerValue::Integer32(integer) => (*integer).into(), + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), + JsVariant::Integer32(integer) => integer.into(), // Slow path: - InnerValue::Object(_) => { + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::String)?; - match primitive.inner { - InnerValue::String(ref string) => string.clone().into(), - InnerValue::Symbol(ref symbol) => symbol.clone().into(), - InnerValue::Integer32(integer) => integer.into(), + match primitive.variant() { + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), + JsVariant::Integer32(integer) => integer.into(), _ => primitive.to_string(context)?.into(), } } @@ -687,7 +621,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 InnerValue::Integer32(number) = self.inner { + if let Some(number) = self.0.as_integer32() { if let Ok(number) = u32::try_from(number) { return Ok(number); } @@ -702,7 +636,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 InnerValue::Integer32(number) = self.inner { + if let Some(number) = self.0.as_integer32() { return Ok(number); } let number = self.to_number(context)?; @@ -974,20 +908,20 @@ impl JsValue { /// /// See: pub fn to_number(&self, context: &mut Context) -> JsResult { - match self.inner { - InnerValue::Null => Ok(0.0), - InnerValue::Undefined => Ok(f64::NAN), - InnerValue::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), - InnerValue::String(ref string) => Ok(string.to_number()), - InnerValue::Float64(number) => Ok(number), - InnerValue::Integer32(integer) => Ok(f64::from(integer)), - InnerValue::Symbol(_) => Err(JsNativeError::typ() + 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.to_number()), + JsVariant::Float64(number) => Ok(number), + JsVariant::Integer32(integer) => Ok(f64::from(integer)), + JsVariant::Symbol(_) => Err(JsNativeError::typ() .with_message("argument must not be a symbol") .into()), - InnerValue::BigInt(_) => Err(JsNativeError::typ() + JsVariant::BigInt(_) => Err(JsNativeError::typ() .with_message("argument must not be a bigint") .into()), - InnerValue::Object(_) => { + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_number(context) } diff --git a/core/engine/src/value/operations.rs b/core/engine/src/value/operations.rs index 2b0d659c4d2..eb717967150 100644 --- a/core/engine/src/value/operations.rs +++ b/core/engine/src/value/operations.rs @@ -1,4 +1,3 @@ -use crate::value::InnerValue; use crate::{ builtins::{ number::{f64_to_int32, f64_to_uint32}, @@ -7,37 +6,33 @@ use crate::{ error::JsNativeError, js_string, value::{JsSymbol, Numeric, PreferredType}, - Context, JsBigInt, JsResult, JsValue, + Context, JsBigInt, JsResult, JsValue, JsVariant, }; impl JsValue { /// Perform the binary `+` operator on the value and return the result. pub fn add(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: // Numeric add - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x - .checked_add(*y) - .map_or_else(|| Self::new(f64::from(*x) + f64::from(*y)), Self::new), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Self::new(x + y), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(f64::from(*x) + y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(x + f64::from(*y)), - (InnerValue::BigInt(ref x), InnerValue::BigInt(ref y)) => { - Self::new(JsBigInt::add(x, y)) - } + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x + .checked_add(y) + .map_or_else(|| Self::new(f64::from(x) + f64::from(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x + y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) + y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x + f64::from(y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::add(x, y)), // String concat - (InnerValue::String(ref x), InnerValue::String(ref y)) => Self::from(js_string!(x, y)), + (JsVariant::String(x), JsVariant::String(y)) => Self::from(js_string!(x, y)), // Slow path: (_, _) => { let x = self.to_primitive(context, PreferredType::Default)?; let y = other.to_primitive(context, PreferredType::Default)?; - match (&x.inner, &y.inner) { - (InnerValue::String(ref x), _) => { - Self::from(js_string!(x, &y.to_string(context)?)) - } - (_, InnerValue::String(y)) => Self::from(js_string!(&x.to_string(context)?, y)), + match (x.variant(), y.variant()) { + (JsVariant::String(x), _) => Self::from(js_string!(x, &y.to_string(context)?)), + (_, JsVariant::String(y)) => Self::from(js_string!(&x.to_string(context)?, y)), (_, _) => { match (x.to_numeric(context)?, y.to_numeric(context)?) { (Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y), @@ -58,18 +53,16 @@ impl JsValue { /// Perform the binary `-` operator on the value and return the result. pub fn sub(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x - .checked_sub(*y) - .map_or_else(|| Self::new(f64::from(*x) - f64::from(*y)), Self::new), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Self::new(x - y), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(f64::from(*x) - y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(x - f64::from(*y)), - - (InnerValue::BigInt(ref x), InnerValue::BigInt(ref y)) => { - Self::new(JsBigInt::sub(x, y)) - } + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x + .checked_sub(y) + .map_or_else(|| Self::new(f64::from(x) - f64::from(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x - y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) - y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x - f64::from(y)), + + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::sub(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -86,18 +79,16 @@ impl JsValue { /// Perform the binary `*` operator on the value and return the result. pub fn mul(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x - .checked_mul(*y) - .map_or_else(|| Self::new(f64::from(*x) * f64::from(*y)), Self::new), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Self::new(x * y), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(f64::from(*x) * y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(x * f64::from(*y)), - - (InnerValue::BigInt(ref x), InnerValue::BigInt(ref y)) => { - Self::new(JsBigInt::mul(x, y)) - } + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x + .checked_mul(y) + .map_or_else(|| Self::new(f64::from(x) * f64::from(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x * y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) * y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x * f64::from(y)), + + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::mul(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -114,17 +105,17 @@ impl JsValue { /// Perform the binary `/` operator on the value and return the result. pub fn div(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x - .checked_div(*y) - .filter(|div| *y * div == *x) - .map_or_else(|| Self::new(f64::from(*x) / f64::from(*y)), Self::new), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Self::new(x / y), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(f64::from(*x) / y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(x / f64::from(*y)), - - (InnerValue::BigInt(ref x), InnerValue::BigInt(ref y)) => { + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x + .checked_div(y) + .filter(|div| y * div == x) + .map_or_else(|| Self::new(f64::from(x) / f64::from(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x / y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) / y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x / f64::from(y)), + + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => { if y.is_zero() { return Err(JsNativeError::range() .with_message("BigInt division by zero") @@ -155,29 +146,29 @@ impl JsValue { /// Perform the binary `%` operator on the value and return the result. pub fn rem(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => { - if *y == 0 { + (JsVariant::Integer32(x), JsVariant::Integer32(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), } } } - (InnerValue::Float64(x), InnerValue::Float64(y)) => Self::new((x % y).copysign(*x)), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - let x = f64::from(*x); + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new((x % y).copysign(x)), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + let x = f64::from(x); Self::new((x % y).copysign(x)) } - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Self::new((x % f64::from(*y)).copysign(*x)) + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Self::new((x % f64::from(y)).copysign(x)) } - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => { + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => { if y.is_zero() { return Err(JsNativeError::range() .with_message("BigInt division by zero") @@ -210,30 +201,28 @@ impl JsValue { // NOTE: There are some cases in the spec where we have to compare floats #[allow(clippy::float_cmp)] pub fn pow(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => u32::try_from(*y) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => u32::try_from(y) .ok() .and_then(|y| x.checked_pow(y)) - .map_or_else(|| Self::new(f64::from(*x).powi(*y)), Self::new), - (InnerValue::Float64(x), InnerValue::Float64(y)) => { + .map_or_else(|| Self::new(f64::from(x).powi(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => { if x.abs() == 1.0 && y.is_infinite() { Self::nan() } else { - Self::new(x.powf(*y)) + Self::new(x.powf(y)) } } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { if x.wrapping_abs() == 1 && y.is_infinite() { Self::nan() } else { - Self::new(f64::from(*x).powf(*y)) + Self::new(f64::from(x).powf(y)) } } - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(x.powi(*y)), - (InnerValue::BigInt(ref a), InnerValue::BigInt(ref b)) => { - Self::new(JsBigInt::pow(a, b)?) - } + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x.powi(y)), + (JsVariant::BigInt(a), JsVariant::BigInt(b)) => Self::new(JsBigInt::pow(a, b)?), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -256,16 +245,16 @@ impl JsValue { /// Perform the binary `&` operator on the value and return the result. pub fn bitand(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => Self::new(x & y), - (InnerValue::Float64(x), InnerValue::Float64(y)) => { - Self::new(f64_to_int32(*x) & f64_to_int32(*y)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x & y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x) & f64_to_int32(y)) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(x & f64_to_int32(*y)), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(f64_to_int32(*x) & y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x & f64_to_int32(y)), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) & y), - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => Self::new(JsBigInt::bitand(x, y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitand(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -286,16 +275,16 @@ impl JsValue { /// Perform the binary `|` operator on the value and return the result. pub fn bitor(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => Self::new(x | y), - (InnerValue::Float64(x), InnerValue::Float64(y)) => { - Self::new(f64_to_int32(*x) | f64_to_int32(*y)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x | y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x) | f64_to_int32(y)) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(x | f64_to_int32(*y)), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(f64_to_int32(*x) | y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x | f64_to_int32(y)), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) | y), - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => Self::new(JsBigInt::bitor(x, y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitor(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -316,16 +305,16 @@ impl JsValue { /// Perform the binary `^` operator on the value and return the result. pub fn bitxor(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => Self::new(x ^ y), - (InnerValue::Float64(x), InnerValue::Float64(y)) => { - Self::new(f64_to_int32(*x) ^ f64_to_int32(*y)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x ^ y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x) ^ f64_to_int32(y)) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(x ^ f64_to_int32(*y)), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(f64_to_int32(*x) ^ y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x ^ f64_to_int32(y)), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) ^ y), - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => Self::new(JsBigInt::bitxor(x, y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitxor(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -346,24 +335,22 @@ impl JsValue { /// Perform the binary `<<` operator on the value and return the result. pub fn shl(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => { - Self::new(x.wrapping_shl(*y as u32)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => { + Self::new(x.wrapping_shl(y as u32)) } - (InnerValue::Float64(x), InnerValue::Float64(y)) => { - Self::new(f64_to_int32(*x).wrapping_shl(f64_to_uint32(*y))) + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x).wrapping_shl(f64_to_uint32(y))) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - Self::new(x.wrapping_shl(f64_to_uint32(*y))) + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + Self::new(x.wrapping_shl(f64_to_uint32(y))) } - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Self::new(f64_to_int32(*x).wrapping_shl(*y as u32)) + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Self::new(f64_to_int32(x).wrapping_shl(y as u32)) } - (InnerValue::BigInt(a), InnerValue::BigInt(b)) => { - Self::new(JsBigInt::shift_left(a, b)?) - } + (JsVariant::BigInt(a), JsVariant::BigInt(b)) => Self::new(JsBigInt::shift_left(a, b)?), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -384,24 +371,22 @@ impl JsValue { /// Perform the binary `>>` operator on the value and return the result. pub fn shr(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => { - Self::new(x.wrapping_shr(*y as u32)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => { + Self::new(x.wrapping_shr(y as u32)) } - (InnerValue::Float64(x), InnerValue::Float64(y)) => { - Self::new(f64_to_int32(*x).wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x).wrapping_shr(f64_to_uint32(y))) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - Self::new(x.wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + Self::new(x.wrapping_shr(f64_to_uint32(y))) } - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Self::new(f64_to_int32(*x).wrapping_shr(*y as u32)) + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Self::new(f64_to_int32(x).wrapping_shr(y as u32)) } - (InnerValue::BigInt(a), InnerValue::BigInt(b)) => { - Self::new(JsBigInt::shift_right(a, b)?) - } + (JsVariant::BigInt(a), JsVariant::BigInt(b)) => Self::new(JsBigInt::shift_right(a, b)?), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -422,19 +407,19 @@ impl JsValue { /// Perform the binary `>>>` operator on the value and return the result. pub fn ushr(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => { - Self::new((*x as u32).wrapping_shr(*y as u32)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => { + Self::new((x as u32).wrapping_shr(y as u32)) } - (InnerValue::Float64(x), InnerValue::Float64(y)) => { - Self::new(f64_to_uint32(*x).wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y))) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - Self::new((*x as u32).wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + Self::new((x as u32).wrapping_shr(f64_to_uint32(y))) } - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Self::new(f64_to_uint32(*x).wrapping_shr(*y as u32)) + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Self::new(f64_to_uint32(x).wrapping_shr(y as u32)) } // Slow path: @@ -497,20 +482,20 @@ impl JsValue { /// Returns the negated value. pub fn neg(&self, context: &mut Context) -> JsResult { - Ok(match self.inner { - InnerValue::Symbol(_) | InnerValue::Undefined => Self::new(f64::NAN), - InnerValue::Object(_) => Self::new( + Ok(match self.variant() { + JsVariant::Symbol(_) | JsVariant::Undefined => Self::new(f64::NAN), + JsVariant::Object(_) => Self::new( self.to_numeric_number(context) .map_or(f64::NAN, std::ops::Neg::neg), ), - InnerValue::String(ref str) => Self::new(-str.to_number()), - InnerValue::Float64(num) => Self::new(-num), - InnerValue::Integer32(0) | InnerValue::Boolean(false) | InnerValue::Null => { - Self::new(-f64::from(0)) - } - InnerValue::Integer32(num) => Self::new(-num), - InnerValue::Boolean(true) => Self::new(-f64::from(1)), - InnerValue::BigInt(ref x) => Self::new(JsBigInt::neg(x)), + JsVariant::String(str) => Self::new(-str.to_number()), + JsVariant::Float64(num) => Self::new(-num), + JsVariant::Integer32(0) | JsVariant::Boolean(false) | JsVariant::Null => { + Self::new(-0.0) + } + JsVariant::Integer32(num) => Self::new(-num), + JsVariant::Boolean(true) => Self::new(-1), + JsVariant::BigInt(x) => Self::new(JsBigInt::neg(x)), }) } @@ -543,17 +528,13 @@ impl JsValue { left_first: bool, context: &mut Context, ) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path (for some common operations): - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => (x < y).into(), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - Number::less_than(f64::from(*x), *y) - } - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Number::less_than(*x, f64::from(*y)) - } - (InnerValue::Float64(x), InnerValue::Float64(y)) => Number::less_than(*x, *y), - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => (x < y).into(), + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => (x < y).into(), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::less_than(f64::from(x), y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::less_than(x, f64::from(y)), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::less_than(x, y), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => (x < y).into(), // Slow path: (_, _) => { @@ -568,16 +549,12 @@ impl JsValue { (px, py) }; - match (&px.inner, &py.inner) { - (InnerValue::String(x), InnerValue::String(ref y)) => (x < y).into(), - (InnerValue::BigInt(x), InnerValue::String(ref y)) => { - JsBigInt::from_js_string(y) - .map_or(AbstractRelation::Undefined, |y| (*x < y).into()) - } - (InnerValue::String(ref x), InnerValue::BigInt(ref y)) => { - JsBigInt::from_js_string(x) - .map_or(AbstractRelation::Undefined, |x| (x < *y).into()) - } + match (px.variant(), py.variant()) { + (JsVariant::String(x), JsVariant::String(y)) => (x < y).into(), + (JsVariant::BigInt(x), JsVariant::String(y)) => JsBigInt::from_js_string(y) + .map_or(AbstractRelation::Undefined, |ref y| (x < y).into()), + (JsVariant::String(x), JsVariant::BigInt(y)) => JsBigInt::from_js_string(x) + .map_or(AbstractRelation::Undefined, |ref x| (x < y).into()), (_, _) => 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(), @@ -590,7 +567,7 @@ impl JsValue { } if let Ok(y) = JsBigInt::try_from(y) { - return Ok((*x < y).into()); + return Ok((x < &y).into()); } (x.to_f64() < y).into() @@ -604,7 +581,7 @@ impl JsValue { } if let Ok(x) = JsBigInt::try_from(x) { - return Ok((x < *y).into()); + return Ok((&x < y).into()); } (x < y.to_f64()).into() diff --git a/core/engine/src/value/type.rs b/core/engine/src/value/type.rs index 6d4b6d9c184..1ae4566c743 100644 --- a/core/engine/src/value/type.rs +++ b/core/engine/src/value/type.rs @@ -1,4 +1,5 @@ -use super::{InnerValue, JsValue}; +use super::JsValue; +use crate::JsVariant; /// Possible types of values as defined at . #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -37,15 +38,15 @@ impl JsValue { /// Check [`JsValue::type_of`] if you need to call the `typeof` operator. #[must_use] pub const fn get_type(&self) -> Type { - match self.inner { - InnerValue::Float64(_) | InnerValue::Integer32(_) => Type::Number, - InnerValue::String(_) => Type::String, - InnerValue::Boolean(_) => Type::Boolean, - InnerValue::Symbol(_) => Type::Symbol, - InnerValue::Null => Type::Null, - InnerValue::Undefined => Type::Undefined, - InnerValue::BigInt(_) => Type::BigInt, - InnerValue::Object(_) => Type::Object, + match self.variant() { + JsVariant::Float64(_) | JsVariant::Integer32(_) => Type::Number, + JsVariant::String(_) => Type::String, + JsVariant::Boolean(_) => Type::Boolean, + JsVariant::Symbol(_) => Type::Symbol, + JsVariant::Null => Type::Null, + JsVariant::Undefined => Type::Undefined, + JsVariant::BigInt(_) => Type::BigInt, + JsVariant::Object(_) => Type::Object, } } } diff --git a/core/engine/src/value/variant.rs b/core/engine/src/value/variant.rs index dec2827e264..fa8a81a3d18 100644 --- a/core/engine/src/value/variant.rs +++ b/core/engine/src/value/variant.rs @@ -1,4 +1,3 @@ -use super::InnerValue; use crate::{JsBigInt, JsObject, JsSymbol, JsValue}; use boa_engine::js_string; use boa_string::JsString; @@ -6,7 +5,7 @@ use boa_string::JsString; /// A non-mutable variant of a `JsValue`. /// Represents either a primitive value ([`bool`], [`f64`], [`i32`]) or a reference /// to a heap allocated value ([`JsString`], [`JsSymbol`]). -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum JsVariant<'a> { /// `null` - A null value, for when a value doesn't exist. Null, @@ -30,22 +29,6 @@ pub enum JsVariant<'a> { Symbol(&'a JsSymbol), } -impl<'a> From<&'a InnerValue> for JsVariant<'a> { - fn from(value: &'a InnerValue) -> Self { - match value { - InnerValue::Null => JsVariant::Null, - InnerValue::Undefined => JsVariant::Undefined, - InnerValue::Integer32(i) => JsVariant::Integer32(*i), - InnerValue::Float64(d) => JsVariant::Float64(*d), - InnerValue::Boolean(b) => JsVariant::Boolean(*b), - InnerValue::Object(inner) => JsVariant::Object(inner), - InnerValue::String(inner) => JsVariant::String(inner), - InnerValue::Symbol(inner) => JsVariant::Symbol(inner), - InnerValue::BigInt(inner) => JsVariant::BigInt(inner), - } - } -} - impl<'a> From> for JsValue { fn from(value: JsVariant<'a>) -> Self { match value {