From e35790875dc73ad0aae89015a55e23a675a8059a Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 19 Dec 2024 22:15:08 -0800 Subject: [PATCH] Finish the boxing and add tests --- core/engine/src/builtins/symbol/mod.rs | 1 + core/engine/src/value/conversions/mod.rs | 20 +- .../src/value/conversions/serde_json.rs | 24 +- .../src/value/conversions/try_from_js.rs | 67 +- core/engine/src/value/equality.rs | 122 ++-- core/engine/src/value/hash.rs | 23 +- core/engine/src/value/inner.rs | 627 +++++++++++++++--- core/engine/src/value/mod.rs | 284 +++----- core/engine/src/value/operations.rs | 291 ++++---- core/engine/src/value/type.rs | 21 +- core/engine/src/value/variant.rs | 19 +- 11 files changed, 912 insertions(+), 587 deletions(-) diff --git a/core/engine/src/builtins/symbol/mod.rs b/core/engine/src/builtins/symbol/mod.rs index 43e3651c2fb..06153c5e5cc 100644 --- a/core/engine/src/builtins/symbol/mod.rs +++ b/core/engine/src/builtins/symbol/mod.rs @@ -236,6 +236,7 @@ impl Symbol { fn this_symbol_value(value: &JsValue) -> JsResult { value .as_symbol() + .cloned() .or_else(|| { value .as_object() diff --git a/core/engine/src/value/conversions/mod.rs b/core/engine/src/value/conversions/mod.rs index daee6456064..2d81fdd159c 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)) } } @@ -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::from_inner(InnerValue::float64(value)) } } @@ -79,7 +79,7 @@ macro_rules! impl_from_integer { i32::try_from(value) .map_or_else( |_| Self::from(value as f64), - |value| Self::from_inner(InnerValue::Integer32(value)), + |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..4a5214e088e 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,12 @@ 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(f) = value.as_number() { + Ok(f) + } else { + Err(JsNativeError::typ() .with_message("cannot convert value to a f64") - .into()), + .into()) } } } @@ -184,23 +187,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 index e5724170ff1..10ebeb03905 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -36,9 +36,10 @@ //! This only works on 4-bits aligned values, which is asserted when the //! `InnerValue` is created. -use crate::{JsBigInt, JsObject, JsSymbol}; +use crate::{JsBigInt, JsObject, JsSymbol, JsVariant}; +use boa_gc::{custom_trace, Finalize, Trace}; use boa_string::JsString; -use num_traits::ToBytes; +use core::fmt; use static_assertions::const_assert; // We cannot NaN-box pointers larger than 64 bits. @@ -52,7 +53,9 @@ const_assert!(align_of::<*mut ()>() >= 4); /// /// This is a utility type that allows to create NaN-boxed values, and to check /// the type of a NaN-boxed value. -#[derive(Copy, Clone)] +/// +/// All the bit masking, tagging and untagging is done in this type. +#[derive(Copy, Clone, Debug)] #[repr(u64)] enum NanBitTag { Undefined = 0x7FF4_0000_0000_0000, @@ -60,15 +63,17 @@ enum NanBitTag { False = 0x7FF6_0000_0000_0000, True = 0x7FF6_0000_0000_0001, Integer32 = 0x7FF7_0000_0000_0000, - BigInt = 0x7FF8_0000_0000_0000, - Object = 0x7FF8_0000_0000_0001, - Symbol = 0x7FF8_0000_0000_0002, - String = 0x7FF8_0000_0000_0003, + + /// A generic pointer. + Pointer = 0x7FF8_0000_0000_0000, + BigInt = 0x0000_0000_0000_0000, + Object = 0x0000_0000_0000_0001, + Symbol = 0x0000_0000_0000_0002, + String = 0x0000_0000_0000_0003, // Masks - TaggedMask = 0x7FFF_0000_0000_0000, + TaggedMask = 0x7FFC_0000_0000_0000, PointerMask = 0x0007_FFFF_FFFF_FFFC, - PointerTypeMask = 0x0000_0000_0000_0003, } // Verify that all representations of NanBitTag ARE NAN, but don't match static NAN. @@ -77,12 +82,6 @@ enum NanBitTag { const_assert!(f64::from_bits(NanBitTag::Undefined as u64).is_nan()); impl NanBitTag { - /// Checks if the value is a specific tagged value. - #[inline] - const fn is(self, value: u64) -> bool { - (value & self as u64) == self as u64 - } - /// Checks that a value is a valid boolean (either true or false). #[inline] const fn is_bool(value: u64) -> bool { @@ -93,29 +92,82 @@ impl NanBitTag { /// Checks that a value is a valid float, not a tagged nan boxed value. #[inline] const fn is_float(value: u64) -> bool { - (value & NanBitTag::TaggedMask as u64) != NanBitTag::TaggedMask as u64 - } + // Either it is a constant float value, + if value == f64::INFINITY.to_bits() + || value == f64::NEG_INFINITY.to_bits() + // or it is exactly a NaN value, which is the same as the BigInt tag. + // Reminder that pointers cannot be null, so this is safe. + || value == NanBitTag::Pointer as u64 + { + return true; + } - /// Return the tag of this value. - #[inline] - const fn tag_of(value: u64) -> Option { + // Or it is not tagged, match value & NanBitTag::TaggedMask as u64 { - 0x7FF4_0000_0000_0000 => Some(NanBitTag::Undefined), - 0x7FF5_0000_0000_0000 => Some(NanBitTag::Null), - 0x7FF6_0000_0000_0000 => Some(NanBitTag::False), - 0x7FF6_0000_0000_0001 => Some(NanBitTag::True), - 0x7FF7_0000_0000_0000 => Some(NanBitTag::Integer32), - // Verify this is not a NULL pointer. - 0x7FF8_0000_0000_0000 if (value & NanBitTag::PointerMask as u64) != 0 => { - Some(NanBitTag::BigInt) - } - 0x7FF8_0000_0000_0001 => Some(NanBitTag::Object), - 0x7FF8_0000_0000_0002 => Some(NanBitTag::Symbol), - 0x7FF8_0000_0000_0003 => Some(NanBitTag::String), - _ => None, + 0x7FF4_0000_0000_0000 => false, + 0x7FF5_0000_0000_0000 => false, + 0x7FF6_0000_0000_0000 => false, + 0x7FF6_0000_0000_0001 => false, + 0x7FF7_0000_0000_0000 => false, + 0x7FF8_0000_0000_0000 => false, + 0x7FF9_0000_0000_0000 => false, + 0x7FFA_0000_0000_0000 => false, + 0x7FFB_0000_0000_0000 => false, + 0x7FFC_0000_0000_0000 => false, + 0x7FFD_0000_0000_0000 => false, + 0x7FFE_0000_0000_0000 => false, + 0x7FFF_0000_0000_0000 => false, + _ => true, } } + /// Checks that a value is a valid undefined. + #[inline] + const fn is_undefined(value: u64) -> bool { + value == NanBitTag::Undefined as u64 + } + + /// Checks that a value is a valid null. + #[inline] + const fn is_null(value: u64) -> bool { + value == NanBitTag::Null as u64 + } + + /// Checks that a value is a valid integer32. + #[inline] + const fn is_integer32(value: u64) -> bool { + value & NanBitTag::Integer32 as u64 == NanBitTag::Integer32 as u64 + } + + /// Checks that a value is a valid BigInt. + #[inline] + const fn is_bigint(value: u64) -> bool { + (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) + && (value & 0x3 == Self::BigInt as u64) + && (value & NanBitTag::PointerMask as u64) != 0 + } + + /// Checks that a value is a valid Object. + #[inline] + const fn is_object(value: u64) -> bool { + (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) + && (value & 0x3 == Self::Object as u64) + } + + /// Checks that a value is a valid Symbol. + #[inline] + const fn is_symbol(value: u64) -> bool { + (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) + && (value & 0x3 == Self::Symbol as u64) + } + + /// Checks that a value is a valid String. + #[inline] + const fn is_string(value: u64) -> bool { + (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) + && (value & 0x3 == Self::String as u64) + } + /// Returns a tagged u64 of a 64-bits float. #[inline] const fn tag_f64(value: f64) -> u64 { @@ -130,25 +182,14 @@ impl NanBitTag { /// Returns a tagged u64 of a 32-bits integer. #[inline] const fn tag_i32(value: i32) -> u64 { - // Get the 32-bits integer value inside an u64 as is, in native endian. - let mut tagged = (Self::Integer32 as u64).to_ne_bytes(); - let bytes = value.to_ne_bytes(); - - tagged[4] = bytes[0]; - tagged[5] = bytes[1]; - tagged[6] = bytes[2]; - tagged[7] = bytes[3]; - - u64::from_ne_bytes(tagged) + Self::Integer32 as u64 | value as u64 & 0xFFFF_FFFFu64 } /// Returns a i32-bits from a tagged integer. #[inline] const fn untag_i32(value: u64) -> Option { - if value & NanBitTag::Integer32 as u64 == NanBitTag::Integer32 as u64 { - // Get the 32-bits integer value inside an u64 as is. - let bytes = value.to_ne_bytes(); - Some(i32::from_ne_bytes([bytes[4], bytes[5], bytes[6], bytes[7]])) + if Self::is_integer32(value) { + Some(((value & 0xFFFF_FFFFu64) | 0xFFFF_FFFF_0000_0000u64) as i32) } else { None } @@ -186,7 +227,7 @@ impl NanBitTag { assert_ne!(value & Self::PointerMask as u64, 0, "Pointer is NULL."); // Simply cast for bits. - Self::BigInt as u64 | value + Self::Pointer as u64 | Self::BigInt as u64 | value } /// Returns a tagged u64 of a boxed `[JsObject]`. @@ -209,7 +250,7 @@ impl NanBitTag { ); // Simply cast for bits. - Self::Object as u64 | value + Self::Pointer as u64 | Self::Object as u64 | value } /// Returns a tagged u64 of a boxed `[JsSymbol]`. @@ -232,7 +273,7 @@ impl NanBitTag { ); // Simply cast for bits. - Self::Symbol as u64 | value + Self::Pointer as u64 | Self::Symbol as u64 | value } /// Returns a tagged u64 of a boxed `[JsString]`. @@ -255,7 +296,7 @@ impl NanBitTag { ); // Simply cast for bits. - Self::String as u64 | value + Self::Pointer as u64 | Self::String as u64 | value } /// Drops a value if it is a pointer, otherwise do nothing. @@ -263,27 +304,61 @@ impl NanBitTag { unsafe fn drop_pointer(value: u64) { let value_ptr = value & Self::PointerMask as u64; - match Self::tag_of(value) { - Some(Self::BigInt) => { - drop(unsafe { Box::from_raw(value_ptr as *mut JsBigInt) }); - } - Some(Self::Object) => { - drop(unsafe { Box::from_raw(value_ptr as *mut JsObject) }); - } - Some(Self::Symbol) => { - drop(unsafe { Box::from_raw(value_ptr as *mut JsSymbol) }); - } - Some(Self::String) => { - drop(unsafe { Box::from_raw(value_ptr as *mut JsString) }); - } - _ => {} + if value & NanBitTag::Pointer as u64 != 0 || value == NanBitTag::Pointer as u64 { + return; + } + + match value & 0x3 { + 0 => drop(unsafe { Box::from_raw(value_ptr as *mut JsBigInt) }), + 1 => drop(unsafe { Box::from_raw(value_ptr as *mut JsObject) }), + 2 => drop(unsafe { Box::from_raw(value_ptr as *mut JsSymbol) }), + 3 => drop(unsafe { Box::from_raw(value_ptr as *mut JsString) }), + _ => unreachable!(), } } } /// A NaN-boxed `[super::JsValue]`'s inner. -pub(super) struct InnerValue { - inner: u64, +#[derive(PartialEq)] +pub(super) struct InnerValue(u64); + +impl fmt::Debug for InnerValue { + 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 InnerValue {} + +#[allow(unsafe_op_in_unsafe_fn)] +unsafe impl Trace for InnerValue { + custom_trace! {this, mark, { + if let JsVariant::Object(o) = this.as_variant() { + mark(o); + } + }} +} + +impl Clone for InnerValue { + fn clone(&self) -> Self { + match self.as_variant() { + JsVariant::BigInt(n) => Self::bigint(n.clone()), + JsVariant::Object(n) => Self::object(n.clone()), + JsVariant::Symbol(n) => Self::symbol(n.clone()), + JsVariant::String(n) => Self::string(n.clone()), + _ => Self(self.0), + } + } } impl InnerValue { @@ -291,21 +366,21 @@ impl InnerValue { /// of the value. #[must_use] #[inline] - fn from_inner_unchecked(inner: u64) -> Self { - Self { inner } + const fn from_inner_unchecked(inner: u64) -> Self { + Self(inner) } /// Returns a `InnerValue` from a Null. #[must_use] #[inline] - pub(super) fn null() -> Self { + pub(super) const fn null() -> Self { Self::from_inner_unchecked(NanBitTag::Null as u64) } /// Returns a `InnerValue` from an undefined. #[must_use] #[inline] - pub(super) fn undefined() -> Self { + pub(super) const fn undefined() -> Self { Self::from_inner_unchecked(NanBitTag::Undefined as u64) } @@ -313,21 +388,21 @@ impl InnerValue { /// it will be reduced to a canonical `NaN` representation. #[must_use] #[inline] - pub(super) fn float64(value: f64) -> Self { + pub(super) const fn float64(value: f64) -> Self { Self::from_inner_unchecked(NanBitTag::tag_f64(value)) } /// Returns a `InnerValue` from a 32-bits integer. #[must_use] #[inline] - pub(super) fn integer32(value: i32) -> Self { + pub(super) const fn integer32(value: i32) -> Self { Self::from_inner_unchecked(NanBitTag::tag_i32(value)) } /// Returns a `InnerValue` from a boolean. #[must_use] #[inline] - pub(super) fn boolean(value: bool) -> Self { + pub(super) const fn boolean(value: bool) -> Self { Self::from_inner_unchecked(NanBitTag::tag_bool(value)) } @@ -362,72 +437,72 @@ impl InnerValue { /// Returns true if a value is undefined. #[must_use] #[inline] - pub(super) fn is_undefined(&self) -> bool { - NanBitTag::Undefined.is(self.inner) + pub(super) const fn is_undefined(&self) -> bool { + NanBitTag::is_undefined(self.0) } /// Returns true if a value is null. #[must_use] #[inline] - pub(super) fn is_null(&self) -> bool { - NanBitTag::Null.is(self.inner) + pub(super) const fn is_null(&self) -> bool { + NanBitTag::is_null(self.0) } /// Returns true if a value is a boolean. #[must_use] #[inline] - pub(super) fn is_bool(&self) -> bool { - NanBitTag::is_bool(self.inner) + pub(super) const fn is_bool(&self) -> bool { + NanBitTag::is_bool(self.0) } /// Returns true if a value is a 64-bits float. #[must_use] #[inline] - pub(super) fn is_float64(&self) -> bool { - NanBitTag::is_float(self.inner) + pub(super) const fn is_float64(&self) -> bool { + NanBitTag::is_float(self.0) } /// Returns true if a value is a 32-bits integer. #[must_use] #[inline] - pub(super) fn is_integer32(&self) -> bool { - NanBitTag::Integer32.is(self.inner) + pub(super) const fn is_integer32(&self) -> bool { + NanBitTag::is_integer32(self.0) } /// Returns true if a value is a `[JsBigInt]`. A `NaN` will not match here. #[must_use] #[inline] - pub(super) fn is_bigint(&self) -> bool { - NanBitTag::BigInt.is(self.inner) && (self.inner & NanBitTag::PointerMask as u64) != 0 + pub(super) const fn is_bigint(&self) -> bool { + NanBitTag::is_bigint(self.0) } /// Returns true if a value is a boxed Object. #[must_use] #[inline] - pub(super) fn is_object(&self) -> bool { - NanBitTag::Object.is(self.inner) + pub(super) const fn is_object(&self) -> bool { + NanBitTag::is_object(self.0) } /// Returns true if a value is a boxed Symbol. #[must_use] #[inline] - pub(super) fn is_symbol(&self) -> bool { - NanBitTag::Symbol.is(self.inner) + pub(super) const fn is_symbol(&self) -> bool { + NanBitTag::is_symbol(self.0) } /// Returns true if a value is a boxed String. #[must_use] #[inline] - pub(super) fn is_string(&self) -> bool { - NanBitTag::String.is(self.inner) + pub(super) const fn is_string(&self) -> bool { + NanBitTag::is_string(self.0) } /// Returns the value as an f64 if it is a float. #[must_use] #[inline] - pub(super) fn as_float64(&self) -> Option { + pub(super) const fn as_float64(&self) -> Option { if self.is_float64() { - Some(f64::from_bits(self.inner)) + Some(f64::from_bits(self.0)) } else { None } @@ -436,20 +511,376 @@ impl InnerValue { /// Returns the value as an i32 if it is an integer. #[must_use] #[inline] - pub(super) fn as_integer32(&self) -> Option { - if self.is_integer32() { - Some(self.inner as i32) + pub(super) const fn as_integer32(&self) -> Option { + NanBitTag::untag_i32(self.0) + } + + /// Returns the value as a boolean if it is a boolean. + #[must_use] + #[inline] + pub(super) const fn as_bool(&self) -> Option { + if self.0 == NanBitTag::False as u64 { + Some(false) + } else if self.0 == NanBitTag::True as u64 { + Some(true) } else { None } } + + /// Returns the value as a boxed `[JsBigInt]`. + #[must_use] + #[inline] + pub(super) const fn as_bigint(&self) -> Option<&JsBigInt> { + if self.is_bigint() { + // This is safe because the boxed object will always be on the heap. + let ptr = self.0 & NanBitTag::PointerMask as u64; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } + + /// Returns the value as a boxed `[JsObject]`. + #[must_use] + #[inline] + pub(super) const fn as_object(&self) -> Option<&JsObject> { + if self.is_object() { + // This is safe because the boxed object will always be on the heap. + let ptr = self.0 & NanBitTag::PointerMask as u64; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } + + /// Returns the value as a boxed `[JsSymbol]`. + #[must_use] + #[inline] + pub(super) const fn as_symbol(&self) -> Option<&JsSymbol> { + if self.is_symbol() { + // This is safe because the boxed object will always be on the heap. + let ptr = self.0 & NanBitTag::PointerMask as u64; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } + + /// Returns the value as a boxed `[JsString]`. + #[must_use] + #[inline] + pub(super) const fn as_string(&self) -> Option<&JsString> { + if self.is_string() { + // This is safe because the boxed object will always be on the heap. + let ptr = self.0 & NanBitTag::PointerMask as u64; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } + + /// Returns the `[JsVariant]` of this inner value. + #[must_use] + #[inline] + pub(super) const fn as_variant(&self) -> JsVariant<'_> { + if self.is_undefined() { + JsVariant::Undefined + } else if self.is_null() { + JsVariant::Null + } else if let Some(b) = self.as_bool() { + JsVariant::Boolean(b) + } else if let Some(f) = self.as_float64() { + JsVariant::Float64(f) + } else if let Some(i) = self.as_integer32() { + JsVariant::Integer32(i) + } else if let Some(bigint) = self.as_bigint() { + JsVariant::BigInt(bigint) + } else if let Some(obj) = self.as_object() { + JsVariant::Object(obj) + } else if let Some(sym) = self.as_symbol() { + JsVariant::Symbol(sym) + } else if let Some(str) = self.as_string() { + JsVariant::String(str) + } else { + unreachable!() + } + } } impl Drop for InnerValue { fn drop(&mut self) { // Drop the pointer if it is a pointer. unsafe { - NanBitTag::drop_pointer(self.inner); + NanBitTag::drop_pointer(self.0); } } } + +#[test] +fn float() { + fn assert_float(f: f64) { + let v = InnerValue::float64(f); + + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), Some(f)); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); + } + + assert_float(0.0); + assert_float(-0.0); + assert_float(3.14); + assert_float(-3.14); + assert_float(f64::INFINITY); + assert_float(f64::NEG_INFINITY); + + // Special care has to be taken for NaN, because NaN != NaN. + let v = InnerValue::float64(f64::NAN); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert!(v.as_float64().unwrap().is_nan()); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); +} + +#[test] +fn integer() { + let int = 42; + let v = InnerValue::integer32(int); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), Some(int)); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); + + let int = -42; + let v = InnerValue::integer32(int); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), Some(int)); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); + + let int = 0; + let v = InnerValue::integer32(int); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), Some(int)); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); +} + +#[test] +fn boolean() { + let v = InnerValue::boolean(true); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), Some(true)); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); + + let v = InnerValue::boolean(false); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), Some(false)); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); +} + +#[test] +fn bigint() { + let bigint = JsBigInt::from(42); + let v = InnerValue::bigint(bigint.clone()); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), Some(&bigint)); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); +} + +#[test] +fn object() { + let object = JsObject::with_null_proto(); + let v = InnerValue::object(object.clone()); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), Some(&object)); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); +} + +#[test] +fn string() { + let str = crate::js_string!("Hello World"); + let v = InnerValue::string(str.clone()); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), Some(&str)); + assert_eq!(v.as_symbol(), None); +} + +#[test] +fn symbol() { + let sym = JsSymbol::new(Some(JsString::from("Hello World"))).unwrap(); + let v = InnerValue::symbol(sym.clone()); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), Some(&sym)); + + let sym = JsSymbol::new(None).unwrap(); + let v = InnerValue::symbol(sym.clone()); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), Some(&sym)); +} diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index bec7848bfd9..b4d5b7b1e26 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; @@ -66,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. @@ -104,14 +78,12 @@ 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 } + const fn from_inner(inner: inner::InnerValue) -> Self { + Self(inner) } /// Create a new [`JsValue`]. @@ -125,79 +97,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] #[must_use] pub const fn rational(rational: f64) -> Self { - Self::from_inner(InnerValue::Float64(rational)) + 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. @@ -209,22 +173,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, |obj| obj.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`. @@ -241,7 +197,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, |obj| obj.is_constructor()) } /// Returns the constructor if the value is a constructor, otherwise `None`. @@ -255,22 +211,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`. @@ -286,11 +234,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`. @@ -307,32 +251,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 - } + pub fn as_symbol(&self) -> Option<&JsSymbol> { + self.0.as_symbol() } /// 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. @@ -352,14 +292,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 } } @@ -367,20 +310,17 @@ 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), - _ => None, + if let Some(i) = self.as_i32() { + Some(i as f64) + } else { + self.0.as_float64() } } @@ -388,52 +328,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. @@ -444,13 +374,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, } } @@ -518,14 +448,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(ref string) => JsBigInt::from_js_string(string).map_or_else( || { Err(JsNativeError::syntax() .with_message(format!( @@ -536,17 +466,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()), } @@ -579,19 +509,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) } @@ -604,41 +534,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()), } } @@ -646,18 +576,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(), } } @@ -688,7 +618,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); } @@ -703,7 +633,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)?; @@ -975,20 +905,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..c82bab759a3 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 => { + JsVariant::String(str) => Self::new(-str.to_number()), + JsVariant::Float64(num) => Self::new(-num), + JsVariant::Integer32(0) | JsVariant::Boolean(false) | JsVariant::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::Integer32(num) => Self::new(-num), + JsVariant::Boolean(true) => Self::new(-f64::from(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 {