diff --git a/core/engine/src/builtins/array/mod.rs b/core/engine/src/builtins/array/mod.rs index f392efbe74f..ebbc0c80f9c 100644 --- a/core/engine/src/builtins/array/mod.rs +++ b/core/engine/src/builtins/array/mod.rs @@ -38,7 +38,9 @@ use std::cmp::{min, Ordering}; use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; mod array_iterator; +use crate::value::JsVariant; pub(crate) use array_iterator::ArrayIterator; + #[cfg(test)] mod tests; @@ -537,9 +539,9 @@ impl Array { // 3. Else, // a. If IsCallable(mapfn) is false, throw a TypeError exception. // b. Let mapping be true. - let mapping = match mapfn { - JsValue::Undefined => None, - JsValue::Object(o) if o.is_callable() => Some(o), + let mapping = match mapfn.variant() { + JsVariant::Undefined => None, + JsVariant::Object(o) if o.is_callable() => Some(o), _ => { return Err(JsNativeError::typ() .with_message(format!("`{}` is not callable", mapfn.type_of())) @@ -2665,9 +2667,9 @@ impl Array { context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - let comparefn = match args.get_or_undefined(0) { - JsValue::Object(ref obj) if obj.is_callable() => Some(obj), - JsValue::Undefined => None, + let comparefn = match args.get_or_undefined(0).variant() { + JsVariant::Object(obj) if obj.is_callable() => Some(obj), + JsVariant::Undefined => None, _ => { return Err(JsNativeError::typ() .with_message("The comparison function must be either a function or undefined") @@ -2728,9 +2730,9 @@ impl Array { context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - let comparefn = match args.get_or_undefined(0) { - JsValue::Object(ref obj) if obj.is_callable() => Some(obj), - JsValue::Undefined => None, + let comparefn = match args.get_or_undefined(0).variant() { + JsVariant::Object(obj) if obj.is_callable() => Some(obj), + JsVariant::Undefined => None, _ => { return Err(JsNativeError::typ() .with_message("The comparison function must be either a function or undefined") @@ -3319,7 +3321,7 @@ fn compare_array_elements( let args = [x.clone(), y.clone()]; // a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)). let v = cmp - .call(&JsValue::Undefined, &args, context)? + .call(&JsValue::undefined(), &args, context)? .to_number(context)?; // b. If v is NaN, return +0𝔽. // c. Return v. diff --git a/core/engine/src/builtins/array/tests.rs b/core/engine/src/builtins/array/tests.rs index 4df066ca815..738bd93512f 100644 --- a/core/engine/src/builtins/array/tests.rs +++ b/core/engine/src/builtins/array/tests.rs @@ -875,7 +875,8 @@ fn array_spread_non_iterable() { fn get_relative_start() { #[track_caller] fn assert(context: &mut Context, arg: Option<&JsValue>, len: u64, expected: u64) { - let arg = arg.unwrap_or(&JsValue::Undefined); + const UNDEFINED: &JsValue = &JsValue::undefined(); + let arg = arg.unwrap_or(UNDEFINED); assert_eq!( Array::get_relative_start(context, arg, len).unwrap(), expected @@ -902,7 +903,8 @@ fn get_relative_start() { fn get_relative_end() { #[track_caller] fn assert(context: &mut Context, arg: Option<&JsValue>, len: u64, expected: u64) { - let arg = arg.unwrap_or(&JsValue::Undefined); + const UNDEFINED: &JsValue = &JsValue::undefined(); + let arg = arg.unwrap_or(UNDEFINED); assert_eq!( Array::get_relative_end(context, arg, len).unwrap(), expected diff --git a/core/engine/src/builtins/array_buffer/mod.rs b/core/engine/src/builtins/array_buffer/mod.rs index 7c2e3197022..49f2ea4ec9b 100644 --- a/core/engine/src/builtins/array_buffer/mod.rs +++ b/core/engine/src/builtins/array_buffer/mod.rs @@ -880,7 +880,7 @@ impl ArrayBuffer { // 8. If allocatingResizableBuffer is true, then // c. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength. max_byte_len, - detach_key: JsValue::Undefined, + detach_key: JsValue::undefined(), }, ); diff --git a/core/engine/src/builtins/array_buffer/tests.rs b/core/engine/src/builtins/array_buffer/tests.rs index 604bf6140fe..f1af0936fc8 100644 --- a/core/engine/src/builtins/array_buffer/tests.rs +++ b/core/engine/src/builtins/array_buffer/tests.rs @@ -1,4 +1,4 @@ -use crate::Context; +use crate::{run_test_actions, Context, TestAction}; #[test] fn create_byte_data_block() { @@ -19,3 +19,122 @@ fn create_shared_byte_data_block() { // Rainy day assert!(super::shared::create_shared_byte_data_block(u64::MAX, context).is_err()); } + +#[test] +fn get_values() { + run_test_actions([ + TestAction::run( + r#" + var buffer = new ArrayBuffer(12); + var sample = new DataView(buffer, 0); + + sample.setUint8(0, 127); + sample.setUint8(1, 255); + sample.setUint8(2, 255); + sample.setUint8(3, 255); + sample.setUint8(4, 128); + sample.setUint8(5, 0); + sample.setUint8(6, 0); + sample.setUint8(7, 0); + sample.setUint8(8, 1); + sample.setUint8(9, 0); + sample.setUint8(10, 0); + sample.setUint8(11, 0); + "#, + ), + TestAction::assert("sample.getUint32(0, false) == 2147483647"), + TestAction::assert("sample.getUint32(1, false) == 4294967168"), + TestAction::assert("sample.getUint32(2, false) == 4294934528"), + TestAction::assert("sample.getUint32(3, false) == 4286578688"), + TestAction::assert("sample.getUint32(4, false) == 2147483648"), + TestAction::assert("sample.getUint32(5, false) == 1"), + TestAction::assert("sample.getUint32(6, false) == 256"), + TestAction::assert("sample.getUint32(7, false) == 65536"), + TestAction::assert("sample.getUint32(8, false) == 16777216"), + TestAction::assert("sample.getUint32(0, true) == 4294967167"), + TestAction::assert("sample.getUint32(1, true) == 2164260863"), + TestAction::assert("sample.getUint32(2, true) == 8454143"), + TestAction::assert("sample.getUint32(3, true) == 33023"), + TestAction::assert("sample.getUint32(4, true) == 128"), + TestAction::assert("sample.getUint32(5, true) == 16777216"), + TestAction::assert("sample.getUint32(6, true) == 65536"), + TestAction::assert("sample.getUint32(7, true) == 256"), + TestAction::assert("sample.getUint32(8, true) == 1"), + ]); +} + +#[test] +fn sort() { + run_test_actions([ + TestAction::run( + r#" + function cmp(a, b) { + return a.length === b.length && a.every((v, i) => v === b[i]); + } + + var TypedArrayCtor = [ + Int8Array, + Uint8Array, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array, + ]; + + var descending = TypedArrayCtor.map((ctor) => new ctor([4, 3, 2, 1]).sort()); + var mixed = TypedArrayCtor.map((ctor) => new ctor([3, 4, 1, 2]).sort()); + var repeating = TypedArrayCtor.map((ctor) => new ctor([0, 1, 1, 2, 3, 3, 4]).sort()); + "#, + ), + // Descending + TestAction::assert("cmp(descending[0], [1, 2, 3, 4])"), + TestAction::assert("cmp(descending[1], [1, 2, 3, 4])"), + TestAction::assert("cmp(descending[2], [1, 2, 3, 4])"), + TestAction::assert("cmp(descending[3], [1, 2, 3, 4])"), + TestAction::assert("cmp(descending[4], [1, 2, 3, 4])"), + TestAction::assert("cmp(descending[5], [1, 2, 3, 4])"), + TestAction::assert("cmp(descending[6], [1, 2, 3, 4])"), + TestAction::assert("cmp(descending[7], [1, 2, 3, 4])"), + // Mixed + TestAction::assert("cmp(mixed[0], [1, 2, 3, 4])"), + TestAction::assert("cmp(mixed[1], [1, 2, 3, 4])"), + TestAction::assert("cmp(mixed[2], [1, 2, 3, 4])"), + TestAction::assert("cmp(mixed[3], [1, 2, 3, 4])"), + TestAction::assert("cmp(mixed[4], [1, 2, 3, 4])"), + TestAction::assert("cmp(mixed[5], [1, 2, 3, 4])"), + TestAction::assert("cmp(mixed[6], [1, 2, 3, 4])"), + TestAction::assert("cmp(mixed[7], [1, 2, 3, 4])"), + // Repeating + TestAction::assert("cmp(repeating[0], [0, 1, 1, 2, 3, 3, 4])"), + TestAction::assert("cmp(repeating[1], [0, 1, 1, 2, 3, 3, 4])"), + TestAction::assert("cmp(repeating[2], [0, 1, 1, 2, 3, 3, 4])"), + TestAction::assert("cmp(repeating[3], [0, 1, 1, 2, 3, 3, 4])"), + TestAction::assert("cmp(repeating[4], [0, 1, 1, 2, 3, 3, 4])"), + TestAction::assert("cmp(repeating[5], [0, 1, 1, 2, 3, 3, 4])"), + TestAction::assert("cmp(repeating[6], [0, 1, 1, 2, 3, 3, 4])"), + TestAction::assert("cmp(repeating[7], [0, 1, 1, 2, 3, 3, 4])"), + ]); +} + +#[test] +fn sort_negative_zero() { + run_test_actions([ + TestAction::run( + r#" + function cmp(a, b) { + return a.length === b.length && a.every((v, i) => v === b[i]); + } + + var TypedArrayCtor = [Float32Array, Float64Array]; + var negativeZero = TypedArrayCtor.map((ctor) => new ctor([1, 0, -0, 2]).sort()); + var infinities = TypedArrayCtor.map((ctor) => new ctor([3, 4, Infinity, -Infinity, 1, 2]).sort()); + "#, + ), + TestAction::assert("cmp(negativeZero[0], [-0, 0, 1, 2])"), + TestAction::assert("cmp(negativeZero[1], [-0, 0, 1, 2])"), + TestAction::assert("cmp(infinities[0], [-Infinity, 1, 2, 3, 4, Infinity])"), + TestAction::assert("cmp(infinities[1], [-Infinity, 1, 2, 3, 4, Infinity])"), + ]); +} diff --git a/core/engine/src/builtins/date/mod.rs b/core/engine/src/builtins/date/mod.rs index b90d76e6b59..dbf5051b7de 100644 --- a/core/engine/src/builtins/date/mod.rs +++ b/core/engine/src/builtins/date/mod.rs @@ -423,7 +423,7 @@ impl Date { // 4. If t is NaN, return NaN. if t.is_nan() { - return Ok(JsValue::from(f64::NAN)); + return Ok(JsValue::new(f64::NAN)); }; if LOCAL { diff --git a/core/engine/src/builtins/function/mod.rs b/core/engine/src/builtins/function/mod.rs index 94751379289..39ac251ed8b 100644 --- a/core/engine/src/builtins/function/mod.rs +++ b/core/engine/src/builtins/function/mod.rs @@ -511,12 +511,12 @@ impl BuiltInFunctionObject { // It is a Syntax Error if FormalParameters Contains YieldExpression is true. if generator && contains(¶meters, ContainsSymbol::YieldExpression) { return Err(JsNativeError::syntax().with_message( - if r#async { - "yield expression is not allowed in formal parameter list of async generator" - } else { - "yield expression is not allowed in formal parameter list of generator" - } - ).into()); + if r#async { + "yield expression is not allowed in formal parameter list of async generator" + } else { + "yield expression is not allowed in formal parameter list of generator" + } + ).into()); } // It is a Syntax Error if FormalParameters Contains AwaitExpression is true. diff --git a/core/engine/src/builtins/intl/locale/mod.rs b/core/engine/src/builtins/intl/locale/mod.rs index 39e3cb9d3e8..dc50e03a396 100644 --- a/core/engine/src/builtins/intl/locale/mod.rs +++ b/core/engine/src/builtins/intl/locale/mod.rs @@ -649,7 +649,7 @@ impl Locale { .keywords .get(&key!("kn")) .map(Value::as_tinystr_slice); - Ok(JsValue::Boolean(match kn { + Ok(JsValue::new(match kn { Some([]) => true, Some([kn]) if kn == "true" => true, _ => false, diff --git a/core/engine/src/builtins/intl/mod.rs b/core/engine/src/builtins/intl/mod.rs index c7128618577..ae269ca52e6 100644 --- a/core/engine/src/builtins/intl/mod.rs +++ b/core/engine/src/builtins/intl/mod.rs @@ -174,7 +174,7 @@ impl Intl { let ll = locale::canonicalize_locale_list(locales, context)?; // 2. Return CreateArrayFromList(ll). - Ok(JsValue::Object(Array::create_array_from_list( + Ok(JsValue::new(Array::create_array_from_list( ll.into_iter().map(|loc| js_string!(loc.to_string()).into()), context, ))) diff --git a/core/engine/src/builtins/intl/number_format/mod.rs b/core/engine/src/builtins/intl/number_format/mod.rs index 12c77c0a3c2..d21cd08bd38 100644 --- a/core/engine/src/builtins/intl/number_format/mod.rs +++ b/core/engine/src/builtins/intl/number_format/mod.rs @@ -19,6 +19,12 @@ use num_bigint::BigInt; use num_traits::Num; pub(crate) use options::*; +use super::{ + locale::{canonicalize_locale_list, filter_locales, resolve_locale, validate_extension}, + options::{coerce_options_to_object, IntlOptions}, + Service, +}; +use crate::value::JsVariant; use crate::{ builtins::{ builder::BuiltInBuilder, options::get_option, string::is_trimmable_whitespace, @@ -41,12 +47,6 @@ use crate::{ NativeFunction, }; -use super::{ - locale::{canonicalize_locale_list, filter_locales, resolve_locale, validate_extension}, - options::{coerce_options_to_object, IntlOptions}, - Service, -}; - #[cfg(test)] mod tests; @@ -338,7 +338,7 @@ impl BuiltInConstructor for NumberFormat { break 'block default_use_grouping; } // 3. If value is true, return true. - if let &JsValue::Boolean(true) = &value { + if let Some(true) = value.as_boolean() { break 'block GroupingStrategy::Always; } @@ -750,8 +750,8 @@ fn unwrap_number_format(nf: &JsValue, context: &mut Context) -> JsResult() { + if let Some(nf) = nf.as_object() { + if let Ok(nf) = nf.clone().downcast::() { return Ok(nf); } } @@ -771,16 +771,16 @@ fn to_intl_mathematical_value(value: &JsValue, context: &mut Context) -> JsResul // TODO: Add support in `FixedDecimal` for infinity and NaN, which // should remove the returned errors. - match prim_value { + match prim_value.variant() { // 2. If Type(primValue) is BigInt, return ℝ(primValue). - JsValue::BigInt(bi) => { + JsVariant::BigInt(bi) => { let bi = bi.to_string(); FixedDecimal::try_from(bi.as_bytes()) .map_err(|err| JsNativeError::range().with_message(err.to_string()).into()) } // 3. If Type(primValue) is String, then // a. Let str be primValue. - JsValue::String(s) => { + JsVariant::String(s) => { // 5. Let text be StringToCodePoints(str). // 6. Let literal be ParseText(text, StringNumericLiteral). // 7. If literal is a List of errors, return not-a-number. @@ -791,18 +791,18 @@ fn to_intl_mathematical_value(value: &JsValue, context: &mut Context) -> JsResul // c. If rounded is +∞𝔽, return positive-infinity. // d. If rounded is +0𝔽 and intlMV < 0, return negative-zero. // e. If rounded is +0𝔽, return 0. - js_string_to_fixed_decimal(&s).ok_or_else(|| { + js_string_to_fixed_decimal(s).ok_or_else(|| { JsNativeError::syntax() .with_message("could not parse the provided string") .into() }) } // 4. Else, - other => { + _ => { // a. Let x be ? ToNumber(primValue). // b. If x is -0𝔽, return negative-zero. // c. Let str be Number::toString(x, 10). - let x = other.to_number(context)?; + let x = prim_value.to_number(context)?; FixedDecimal::try_from_f64(x, FloatPrecision::Floating) .map_err(|err| JsNativeError::range().with_message(err.to_string()).into()) diff --git a/core/engine/src/builtins/iterable/async_from_sync_iterator.rs b/core/engine/src/builtins/iterable/async_from_sync_iterator.rs index 00c51940f9e..9d7dd896a7c 100644 --- a/core/engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/core/engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -172,7 +172,7 @@ impl AsyncFromSyncIterator { // b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iterResult »). promise_capability .resolve() - .call(&JsValue::Undefined, &[iter_result], context) + .call(&JsValue::undefined(), &[iter_result], context) .expect("cannot fail according to spec"); // c. Return promiseCapability.[[Promise]]. diff --git a/core/engine/src/builtins/iterable/mod.rs b/core/engine/src/builtins/iterable/mod.rs index ba5a1d6ad57..ae06b53798d 100644 --- a/core/engine/src/builtins/iterable/mod.rs +++ b/core/engine/src/builtins/iterable/mod.rs @@ -314,8 +314,8 @@ impl IteratorResult { /// Gets a new `IteratorResult` from a value. Returns `Err` if /// the value is not a [`JsObject`] pub(crate) fn from_value(value: JsValue) -> JsResult { - if let JsValue::Object(o) = value { - Ok(Self { object: o }) + if let Some(object) = value.into_object() { + Ok(Self { object }) } else { Err(JsNativeError::typ() .with_message("next value should be an object") diff --git a/core/engine/src/builtins/map/mod.rs b/core/engine/src/builtins/map/mod.rs index bdd39e52c76..b76257f5f91 100644 --- a/core/engine/src/builtins/map/mod.rs +++ b/core/engine/src/builtins/map/mod.rs @@ -33,7 +33,9 @@ mod map_iterator; pub(crate) use map_iterator::MapIterator; pub mod ordered_map; +use crate::value::JsVariant; use ordered_map::OrderedMap; + #[cfg(test)] mod tests; @@ -238,11 +240,11 @@ impl Map { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. if let Some(mut map) = object.downcast_mut::>() { - let key = match key { - JsValue::Rational(r) => { + let key = match key.variant() { + JsVariant::Float64(r) => { // 5. If key is -0𝔽, set key to +0𝔽. if r.is_zero() { - JsValue::Rational(0f64) + JsValue::new(0) } else { key.clone() } @@ -306,10 +308,9 @@ impl Map { /// [spec]: https://tc39.es/ecma262/#sec-map.prototype.delete /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete pub(crate) fn delete(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { - const JS_ZERO: &JsValue = &JsValue::Integer(0); let key = args.get_or_undefined(0); let key = match key.as_number() { - Some(n) if n.is_zero() => JS_ZERO, + Some(n) if n.is_zero() => &JsValue::new(0), _ => key, }; @@ -342,15 +343,14 @@ impl Map { /// [spec]: https://tc39.es/ecma262/#sec-map.prototype.get /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get pub(crate) fn get(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { - const JS_ZERO: &JsValue = &JsValue::Integer(0); let key = args.get_or_undefined(0); let key = match key.as_number() { - Some(n) if n.is_zero() => JS_ZERO, + Some(n) if n.is_zero() => &JsValue::new(0), _ => key, }; // 1. Let M be the this value. - if let JsValue::Object(ref object) = this { + if let Some(object) = this.as_object() { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. if let Some(map) = object.downcast_ref::>() { @@ -407,15 +407,14 @@ impl Map { /// [spec]: https://tc39.es/ecma262/#sec-map.prototype.has /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has pub(crate) fn has(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { - const JS_ZERO: &JsValue = &JsValue::Integer(0); let key = args.get_or_undefined(0); let key = match key.as_number() { - Some(n) if n.is_zero() => JS_ZERO, + Some(n) if n.is_zero() => &JsValue::new(0), _ => key, }; // 1. Let M be the this value. - if let JsValue::Object(ref object) = this { + if let Some(object) = this.as_object() { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. if let Some(map) = object.downcast_ref::>() { diff --git a/core/engine/src/builtins/number/conversions.rs b/core/engine/src/builtins/number/conversions.rs index 9457febdf86..f572a5b70bc 100644 --- a/core/engine/src/builtins/number/conversions.rs +++ b/core/engine/src/builtins/number/conversions.rs @@ -88,7 +88,7 @@ pub(crate) fn f64_to_int32(number: f64) -> i32 { "fjcvtzs {dst:w}, {src:d}", src = in(vreg) number, dst = out(reg) ret, - ) + ); } ret } diff --git a/core/engine/src/builtins/number/globals.rs b/core/engine/src/builtins/number/globals.rs index 6fa8813f8d9..c9ef2a4b2b8 100644 --- a/core/engine/src/builtins/number/globals.rs +++ b/core/engine/src/builtins/number/globals.rs @@ -254,7 +254,7 @@ pub(crate) fn parse_int(_: &JsValue, args: &[JsValue], context: &mut Context) -> return Ok(JsValue::new(-0_f64)); } - return Ok(JsValue::new(0_f64)); + return Ok(JsValue::new(0)); } // 16. Return 𝔽(sign × mathInt). diff --git a/core/engine/src/builtins/number/mod.rs b/core/engine/src/builtins/number/mod.rs index 99147f903f2..9cf22e569e3 100644 --- a/core/engine/src/builtins/number/mod.rs +++ b/core/engine/src/builtins/number/mod.rs @@ -33,9 +33,9 @@ pub(crate) use globals::{IsFinite, IsNaN, ParseFloat, ParseInt}; mod conversions; -pub(crate) use conversions::{f64_to_int32, f64_to_uint32}; - use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; +use crate::value::JsVariant; +pub(crate) use conversions::{f64_to_int32, f64_to_uint32}; #[cfg(test)] mod tests; @@ -224,7 +224,8 @@ impl Number { // 1. Let x be ? thisNumberValue(this value). let this_num = Self::this_number_value(this)?; let precision = match args.first() { - None | Some(JsValue::Undefined) => None, + None => None, + Some(x) if x.is_undefined() => None, // 2. Let f be ? ToIntegerOrInfinity(fractionDigits). Some(n) => Some(n.to_integer_or_infinity(context)?), }; @@ -743,11 +744,14 @@ impl Number { // 1. If number is not a Number, return false. // 2. If number is not finite, return false. // 3. Otherwise, return true. - Ok(JsValue::new(args.first().map_or(false, |val| match val { - JsValue::Integer(_) => true, - JsValue::Rational(number) => number.is_finite(), - _ => false, - }))) + Ok(JsValue::new(args.first().map_or( + false, + |val| match val.variant() { + JsVariant::Integer32(_) => true, + JsVariant::Float64(number) => number.is_finite(), + _ => false, + }, + ))) } /// `Number.isInteger( number )` @@ -790,7 +794,7 @@ impl Number { _ctx: &mut Context, ) -> JsResult { Ok(JsValue::new( - if let Some(&JsValue::Rational(number)) = args.first() { + if let Some(number) = args.first().and_then(JsValue::as_number) { number.is_nan() } else { false @@ -818,9 +822,9 @@ impl Number { args: &[JsValue], _ctx: &mut Context, ) -> JsResult { - Ok(JsValue::new(match args.first() { - Some(JsValue::Integer(_)) => true, - Some(JsValue::Rational(number)) if Self::is_float_integer(*number) => { + Ok(JsValue::new(match args.first().map(JsValue::variant) { + Some(JsVariant::Integer32(_)) => true, + Some(JsVariant::Float64(number)) if Self::is_float_integer(number) => { number.abs() <= Self::MAX_SAFE_INTEGER } _ => false, @@ -834,9 +838,9 @@ impl Number { /// /// [spec]: https://tc39.es/ecma262/#sec-isinteger pub(crate) fn is_integer(val: &JsValue) -> bool { - match val { - JsValue::Integer(_) => true, - JsValue::Rational(number) => Self::is_float_integer(*number), + match val.variant() { + JsVariant::Integer32(_) => true, + JsVariant::Float64(number) => Self::is_float_integer(number), _ => false, } } diff --git a/core/engine/src/builtins/object/mod.rs b/core/engine/src/builtins/object/mod.rs index 7d1b18c4d16..d820da17b08 100644 --- a/core/engine/src/builtins/object/mod.rs +++ b/core/engine/src/builtins/object/mod.rs @@ -16,6 +16,7 @@ use super::{ error::ErrorObject, Array, BuiltInBuilder, BuiltInConstructor, Date, IntrinsicObject, RegExp, }; +use crate::value::JsVariant; use crate::{ builtins::{iterable::IteratorHint, map, BuiltInObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, @@ -215,7 +216,7 @@ impl OrdinaryObject { // 2. Return ? O.[[GetPrototypeOf]](). let proto = obj.__get_prototype_of__(&mut InternalMethodContext::new(context))?; - Ok(proto.map_or(JsValue::Null, JsValue::new)) + Ok(proto.map_or(JsValue::null(), JsValue::new)) } /// `set Object.prototype.__proto__` @@ -238,14 +239,14 @@ impl OrdinaryObject { let this = this.require_object_coercible()?; // 2. If Type(proto) is neither Object nor Null, return undefined. - let proto = match args.get_or_undefined(0) { - JsValue::Object(proto) => Some(proto.clone()), - JsValue::Null => None, + let proto = match args.get_or_undefined(0).variant() { + JsVariant::Object(proto) => Some(proto.clone()), + JsVariant::Null => None, _ => return Ok(JsValue::undefined()), }; // 3. If Type(O) is not Object, return undefined. - let JsValue::Object(object) = this else { + let JsVariant::Object(object) = this.variant() else { return Ok(JsValue::undefined()); }; @@ -455,12 +456,14 @@ impl OrdinaryObject { let prototype = args.get_or_undefined(0); let properties = args.get_or_undefined(1); - let obj = match prototype { - JsValue::Object(_) | JsValue::Null => JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype.as_object().cloned(), - OrdinaryObject, - ), + let obj = match prototype.variant() { + JsVariant::Object(_) | JsVariant::Null => { + JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + prototype.as_object().cloned(), + OrdinaryObject, + ) + } _ => { return Err(JsNativeError::typ() .with_message(format!( @@ -650,7 +653,7 @@ impl OrdinaryObject { // 2. Return ? obj.[[GetPrototypeOf]](). Ok(obj .__get_prototype_of__(&mut InternalMethodContext::new(context))? - .map_or(JsValue::Null, JsValue::new)) + .map_or(JsValue::null(), JsValue::new)) } /// Set the `prototype` of an object. @@ -680,9 +683,9 @@ impl OrdinaryObject { .require_object_coercible()? .clone(); - let proto = match args.get_or_undefined(1) { - JsValue::Object(obj) => Some(obj.clone()), - JsValue::Null => None, + let proto = match args.get_or_undefined(1).variant() { + JsVariant::Object(obj) => Some(obj.clone()), + JsVariant::Null => None, // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception. val => { return Err(JsNativeError::typ() @@ -751,15 +754,14 @@ impl OrdinaryObject { args: &[JsValue], context: &mut Context, ) -> JsResult { - let object = args.get_or_undefined(0); - if let JsValue::Object(object) = object { + if let Some(object) = args.get_or_undefined(0).as_object() { let key = args .get(1) - .unwrap_or(&JsValue::Undefined) + .unwrap_or(&JsValue::undefined()) .to_property_key(context)?; let desc = args .get(2) - .unwrap_or(&JsValue::Undefined) + .unwrap_or(&JsValue::undefined()) .to_property_descriptor(context)?; object.define_property_or_throw(key, desc, context)?; @@ -788,7 +790,7 @@ impl OrdinaryObject { context: &mut Context, ) -> JsResult { let arg = args.get_or_undefined(0); - if let JsValue::Object(obj) = arg { + if let Some(obj) = arg.as_object() { let props = args.get_or_undefined(1); object_define_properties(obj, props, context)?; Ok(arg.clone()) diff --git a/core/engine/src/builtins/options.rs b/core/engine/src/builtins/options.rs index db5c84b4ec9..cf580919337 100644 --- a/core/engine/src/builtins/options.rs +++ b/core/engine/src/builtins/options.rs @@ -2,6 +2,7 @@ use std::{fmt, str::FromStr}; +use crate::value::JsVariant; use crate::{object::JsObject, Context, JsNativeError, JsResult, JsString, JsValue}; /// A type used as an option parameter for [`get_option`]. @@ -74,14 +75,14 @@ pub(crate) fn get_option( /// /// [spec]: https://tc39.es/ecma402/#sec-getoptionsobject pub(crate) fn get_options_object(options: &JsValue) -> JsResult { - match options { + match options.variant() { // If options is undefined, then - JsValue::Undefined => { + JsVariant::Undefined => { // a. Return OrdinaryObjectCreate(null). Ok(JsObject::with_null_proto()) } // 2. If Type(options) is Object, then - JsValue::Object(obj) => { + JsVariant::Object(obj) => { // a. Return options. Ok(obj.clone()) } diff --git a/core/engine/src/builtins/promise/mod.rs b/core/engine/src/builtins/promise/mod.rs index d97be26b683..2305b6aeaa0 100644 --- a/core/engine/src/builtins/promise/mod.rs +++ b/core/engine/src/builtins/promise/mod.rs @@ -275,7 +275,7 @@ impl PromiseCapability { promise_capability.reject = reject.clone(); // e. Return undefined. - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) }, promise_capability.clone(), ), @@ -430,7 +430,7 @@ impl BuiltInConstructor for Promise { // 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ). let completion = executor.call( - &JsValue::Undefined, + &JsValue::undefined(), &[ resolving_functions.resolve.clone().into(), resolving_functions.reject.clone().into(), @@ -444,7 +444,7 @@ impl BuiltInConstructor for Promise { // a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »). resolving_functions .reject - .call(&JsValue::Undefined, &[e], context)?; + .call(&JsValue::undefined(), &[e], context)?; } // 11. Return promise. @@ -658,7 +658,7 @@ impl Promise { // 4. Repeat, while let Some(next) = iterator_record.step_value(context)? { // c. Append undefined to values. - values.borrow_mut().push(JsValue::Undefined); + values.borrow_mut().push(JsValue::undefined()); // d. Let nextPromise be ? Call(promiseResolve, constructor, « next »). let next_promise = @@ -1535,7 +1535,7 @@ impl Promise { promise_capability .functions .resolve - .call(&JsValue::Undefined, &[x], context)?; + .call(&JsValue::undefined(), &[x], context)?; // 4. Return promiseCapability.[[Promise]]. Ok(promise_capability.promise.clone()) @@ -2126,7 +2126,7 @@ impl Promise { reject_promise(&promise, self_resolution_error.into(), context); // c. Return undefined. - return Ok(JsValue::Undefined); + return Ok(JsValue::undefined()); } let Some(then) = resolution.as_object() else { @@ -2135,7 +2135,7 @@ impl Promise { fulfill_promise(&promise, resolution.clone(), context); // b. Return undefined. - return Ok(JsValue::Undefined); + return Ok(JsValue::undefined()); }; // 9. Let then be Completion(Get(resolution, "then")). @@ -2146,7 +2146,7 @@ impl Promise { reject_promise(&promise, e.to_opaque(context), context); // b. Return undefined. - return Ok(JsValue::Undefined); + return Ok(JsValue::undefined()); } // 11. Let thenAction be then.[[Value]]. Ok(then) => then, @@ -2162,7 +2162,7 @@ impl Promise { fulfill_promise(&promise, resolution.clone(), context); // b. Return undefined. - return Ok(JsValue::Undefined); + return Ok(JsValue::undefined()); }; // 13. Let thenJobCallback be HostMakeJobCallback(thenAction). @@ -2181,7 +2181,7 @@ impl Promise { context.job_queue().enqueue_promise_job(job, context); // 16. Return undefined. - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) }, promise.clone(), ), @@ -2216,7 +2216,7 @@ impl Promise { reject_promise(&promise, args.get_or_undefined(0).clone(), context); // 8. Return undefined. - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) }, promise, ), @@ -2279,7 +2279,7 @@ fn new_promise_reaction_job( // e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)). Some(handler) => context .host_hooks() - .call_job_callback(handler, &JsValue::Undefined, &[argument.clone()], context) + .call_job_callback(handler, &JsValue::undefined(), &[argument.clone()], context) .map_err(|e| e.to_opaque(context)), }; @@ -2293,7 +2293,7 @@ fn new_promise_reaction_job( ); // ii. Return empty. - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) } Some(promise_capability_record) => { // g. Assert: promiseCapability is a PromiseCapability Record. @@ -2306,13 +2306,13 @@ fn new_promise_reaction_job( // h. If handlerResult is an abrupt completion, then Err(value) => { // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). - reject.call(&JsValue::Undefined, &[value], context) + reject.call(&JsValue::undefined(), &[value], context) } // i. Else, Ok(value) => { // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). - resolve.call(&JsValue::Undefined, &[value], context) + resolve.call(&JsValue::undefined(), &[value], context) } } } @@ -2366,7 +2366,7 @@ fn new_promise_resolve_thenable_job( // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). return resolving_functions .reject - .call(&JsValue::Undefined, &[value], context); + .call(&JsValue::undefined(), &[value], context); } // d. Return ? thenCallResult. diff --git a/core/engine/src/builtins/proxy/mod.rs b/core/engine/src/builtins/proxy/mod.rs index f99ed9bc07a..33a294a4bab 100644 --- a/core/engine/src/builtins/proxy/mod.rs +++ b/core/engine/src/builtins/proxy/mod.rs @@ -10,6 +10,8 @@ //! [spec]: https://tc39.es/ecma262/#sec-proxy-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy +use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject, OrdinaryObject}; +use crate::value::JsVariant; use crate::{ builtins::{array, BuiltInObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, @@ -33,8 +35,6 @@ use crate::{ use boa_gc::{Finalize, GcRefCell, Trace}; use boa_profiler::Profiler; use rustc_hash::FxHashSet; - -use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject, OrdinaryObject}; /// Javascript `Proxy` object. #[derive(Debug, Clone, Trace, Finalize)] pub struct Proxy { @@ -279,9 +279,9 @@ pub(crate) fn proxy_exotic_get_prototype_of( let handler_proto = trap.call(&handler.into(), &[target.clone().into()], context)?; // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception. - let handler_proto = match &handler_proto { - JsValue::Object(obj) => Some(obj.clone()), - JsValue::Null => None, + let handler_proto = match handler_proto.variant() { + JsVariant::Object(obj) => Some(obj.clone()), + JsVariant::Null => None, _ => { return Err(JsNativeError::typ() .with_message("Proxy trap result is neither object nor null") @@ -343,7 +343,7 @@ pub(crate) fn proxy_exotic_set_prototype_of( &handler.into(), &[ target.clone().into(), - val.clone().map_or(JsValue::Null, Into::into), + val.clone().map_or(JsValue::null(), Into::into), ], context, )? @@ -923,8 +923,8 @@ pub(crate) fn proxy_exotic_set( // b. If IsAccessorDescriptor(targetDesc) is true, then if target_desc.is_accessor_descriptor() { // i. If targetDesc.[[Set]] is undefined, throw a TypeError exception. - match target_desc.set() { - None | Some(&JsValue::Undefined) => { + match target_desc.set().map(JsValue::is_undefined) { + None | Some(true) => { return Err(JsNativeError::typ() .with_message("Proxy trap set unexpected accessor descriptor") .into()); @@ -1042,8 +1042,8 @@ pub(crate) fn proxy_exotic_own_property_keys( let mut unchecked_result_keys: FxHashSet = FxHashSet::default(); let mut trap_result = Vec::new(); for value in &trap_result_raw { - match value { - JsValue::String(s) => { + match value.variant() { + JsVariant::String(s) => { if !unchecked_result_keys.insert(s.clone().into()) { return Err(JsNativeError::typ() .with_message("Proxy trap result contains duplicate string property keys") @@ -1051,7 +1051,7 @@ pub(crate) fn proxy_exotic_own_property_keys( } trap_result.push(s.clone().into()); } - JsValue::Symbol(s) => { + JsVariant::Symbol(s) => { if !unchecked_result_keys.insert(s.clone().into()) { return Err(JsNativeError::typ() .with_message("Proxy trap result contains duplicate symbol property keys") diff --git a/core/engine/src/builtins/reflect/mod.rs b/core/engine/src/builtins/reflect/mod.rs index 841a24d4b3d..33b54c25309 100644 --- a/core/engine/src/builtins/reflect/mod.rs +++ b/core/engine/src/builtins/reflect/mod.rs @@ -11,6 +11,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect use super::{Array, BuiltInBuilder, IntrinsicObject}; +use crate::value::JsVariant; use crate::{ builtins::{self, BuiltInObject}, context::intrinsics::Intrinsics, @@ -272,7 +273,7 @@ impl Reflect { .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; Ok(target .__get_prototype_of__(&mut InternalMethodContext::new(context))? - .map_or(JsValue::Null, JsValue::new)) + .map_or(JsValue::null(), JsValue::new)) } /// Returns `true` if the object has the property, `false` otherwise. @@ -417,9 +418,9 @@ impl Reflect { .first() .and_then(JsValue::as_object) .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; - let proto = match args.get_or_undefined(1) { - JsValue::Object(obj) => Some(obj.clone()), - JsValue::Null => None, + let proto = match args.get_or_undefined(1).variant() { + JsVariant::Object(obj) => Some(obj.clone()), + JsVariant::Null => None, _ => { return Err(JsNativeError::typ() .with_message("proto must be an object or null") diff --git a/core/engine/src/builtins/set/mod.rs b/core/engine/src/builtins/set/mod.rs index 1b7d463ea50..0d57bcf89ed 100644 --- a/core/engine/src/builtins/set/mod.rs +++ b/core/engine/src/builtins/set/mod.rs @@ -226,8 +226,6 @@ impl Set { /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.add /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add pub(crate) fn add(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { - const JS_ZERO: &JsValue = &JsValue::Integer(0); - // 1. Let S be the this value. // 2. Perform ? RequireInternalSlot(S, [[SetData]]). let Some(mut set) = this @@ -245,7 +243,7 @@ impl Set { // 4. If value is -0𝔽, set value to +0𝔽. let value = args.get_or_undefined(0); let value = match value.as_number() { - Some(n) if n.is_zero() => JS_ZERO, + Some(n) if n.is_zero() => &JsValue::new(0), _ => value, }; @@ -293,8 +291,6 @@ impl Set { /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.delete /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete pub(crate) fn delete(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { - const JS_ZERO: &JsValue = &JsValue::Integer(0); - // 1. Let S be the this value. // 2. Perform ? RequireInternalSlot(S, [[SetData]]). let Some(mut set) = this @@ -308,7 +304,7 @@ impl Set { let value = args.get_or_undefined(0); let value = match value.as_number() { - Some(n) if n.is_zero() => JS_ZERO, + Some(n) if n.is_zero() => &JsValue::new(0), _ => value, }; @@ -426,7 +422,7 @@ impl Set { drop(lock); // 8. Return undefined. - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) } /// `Map.prototype.has( key )` @@ -440,8 +436,6 @@ impl Set { /// [spec]: https://tc39.es/ecma262/#sec-map.prototype.has /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has pub(crate) fn has(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { - const JS_ZERO: &JsValue = &JsValue::Integer(0); - // 1. Let S be the this value. // 2. Perform ? RequireInternalSlot(S, [[SetData]]). let Some(set) = this @@ -455,7 +449,7 @@ impl Set { let value = args.get_or_undefined(0); let value = match value.as_number() { - Some(n) if n.is_zero() => JS_ZERO, + Some(n) if n.is_zero() => &JsValue::new(0), _ => value, }; diff --git a/core/engine/src/builtins/string/mod.rs b/core/engine/src/builtins/string/mod.rs index baa4b24d771..e049aaf0549 100644 --- a/core/engine/src/builtins/string/mod.rs +++ b/core/engine/src/builtins/string/mod.rs @@ -218,8 +218,12 @@ impl BuiltInConstructor for String { let string = match args.first() { // 2. Else, // a. If NewTarget is undefined and Type(value) is Symbol, return SymbolDescriptiveString(value). - Some(JsValue::Symbol(ref sym)) if new_target.is_undefined() => { - return Ok(sym.descriptive_string().into()) + Some(value) if new_target.is_undefined() && value.is_symbol() => { + return Ok(value + .as_symbol() + .expect("Already checked for a symbol") + .descriptive_string() + .into()) } // b. Let s be ? ToString(value). Some(value) => value.to_string(context)?, @@ -840,10 +844,11 @@ impl String { let len = string.len() as i64; // 7. If position is undefined, let pos be 0; else let pos be ? ToIntegerOrInfinity(position). - let pos = match args.get_or_undefined(1) { - &JsValue::Undefined => IntegerOrInfinity::Integer(0), - position => position.to_integer_or_infinity(context)?, - }; + let pos = args + .get_or_undefined(1) + .map_or(Ok(IntegerOrInfinity::Integer(0)), |pos| { + pos.to_integer_or_infinity(context) + })?; // 8. Let start be the result of clamping pos between 0 and len. let start = pos.clamp_finite(0, len) as usize; @@ -1496,7 +1501,7 @@ impl String { let s = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, undefined). - let rx = RegExp::create(regexp, &JsValue::Undefined, context)?; + let rx = RegExp::create(regexp, &JsValue::undefined(), context)?; // 5. Return ? Invoke(rx, @@match, « S »). rx.invoke(JsSymbol::r#match(), &[JsValue::new(s)], context) @@ -1887,10 +1892,11 @@ impl String { let int_start = args.get_or_undefined(0).to_integer_or_infinity(context)?; // 5. If end is undefined, let intEnd be len; else let intEnd be ? ToIntegerOrInfinity(end). - let int_end = match args.get_or_undefined(1) { - &JsValue::Undefined => IntegerOrInfinity::Integer(len), - end => end.to_integer_or_infinity(context)?, - }; + let int_end = args + .get_or_undefined(1) + .map_or(Ok(IntegerOrInfinity::Integer(len)), |end| { + end.to_integer_or_infinity(context) + })?; // 6. Let finalStart be the result of clamping intStart between 0 and len. let final_start = int_start.clamp_finite(0, len) as usize; @@ -2140,11 +2146,13 @@ impl String { // 6. Let ns be the String value that is the result of normalizing S // into the normalization form named by f as specified in // https://unicode.org/reports/tr15/. - let normalization = match args.get_or_undefined(0) { - // 3. If form is undefined, let f be "NFC". - &JsValue::Undefined => Normalization::Nfc, + let first = args.get_or_undefined(0); + // 3. If form is undefined, let f be "NFC". + let normalization = if first.is_undefined() { + Normalization::Nfc + } else { // 4. Else, let f be ? ToString(form). - f => match f.to_string(context)? { + match first.to_string(context)? { ntype if &ntype == "NFC" => Normalization::Nfc, ntype if &ntype == "NFD" => Normalization::Nfd, ntype if &ntype == "NFKC" => Normalization::Nfkc, @@ -2157,7 +2165,7 @@ impl String { ) .into()); } - }, + } }; let normalizers = { @@ -2224,7 +2232,7 @@ impl String { let string = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, undefined). - let rx = RegExp::create(regexp, &JsValue::Undefined, context)?; + let rx = RegExp::create(regexp, &JsValue::undefined(), context)?; // 5. Return ? Invoke(rx, @@search, « string »). rx.invoke(JsSymbol::search(), &[JsValue::new(string)], context) diff --git a/core/engine/src/builtins/symbol/mod.rs b/core/engine/src/builtins/symbol/mod.rs index c0d915a873f..43e3651c2fb 100644 --- a/core/engine/src/builtins/symbol/mod.rs +++ b/core/engine/src/builtins/symbol/mod.rs @@ -280,7 +280,7 @@ impl Symbol { pub(crate) fn value_of(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Return ? thisSymbolValue(this value). let symbol = Self::this_symbol_value(this)?; - Ok(JsValue::Symbol(symbol)) + Ok(symbol.into()) } /// `get Symbol.prototype.description` diff --git a/core/engine/src/builtins/temporal/calendar/mod.rs b/core/engine/src/builtins/temporal/calendar/mod.rs index 37c3a2ca368..0b105deacb1 100644 --- a/core/engine/src/builtins/temporal/calendar/mod.rs +++ b/core/engine/src/builtins/temporal/calendar/mod.rs @@ -58,7 +58,7 @@ pub(crate) fn to_temporal_calendar_slot_value(calendar_like: &JsValue) -> JsResu } // 3. If temporalCalendarLike is not a String, throw a TypeError exception. - let JsValue::String(calendar_id) = calendar_like else { + let Some(calendar_id) = calendar_like.as_string() else { return Err(JsNativeError::typ() .with_message("temporalCalendarLike is not a string.") .into()); diff --git a/core/engine/src/builtins/temporal/duration/mod.rs b/core/engine/src/builtins/temporal/duration/mod.rs index 57794db61ab..f71c28391b3 100644 --- a/core/engine/src/builtins/temporal/duration/mod.rs +++ b/core/engine/src/builtins/temporal/duration/mod.rs @@ -1,5 +1,10 @@ // Boa's implementation of the `Temporal.Duration` Builtin Object. +use super::{ + options::{get_temporal_unit, TemporalUnitGroup}, + to_integer_if_integral, DateTimeValues, +}; +use crate::value::JsVariant; use crate::{ builtins::{ options::{get_option, get_options_object}, @@ -22,11 +27,6 @@ use temporal_rs::{ Duration as InnerDuration, }; -use super::{ - options::{get_temporal_unit, TemporalUnitGroup}, - to_integer_if_integral, DateTimeValues, -}; - #[cfg(test)] mod tests; @@ -304,16 +304,16 @@ impl Duration { let inner = &duration.inner; match field { - DateTimeValues::Year => Ok(JsValue::Rational(inner.years().as_inner())), - DateTimeValues::Month => Ok(JsValue::Rational(inner.months().as_inner())), - DateTimeValues::Week => Ok(JsValue::Rational(inner.weeks().as_inner())), - DateTimeValues::Day => Ok(JsValue::Rational(inner.days().as_inner())), - DateTimeValues::Hour => Ok(JsValue::Rational(inner.hours().as_inner())), - DateTimeValues::Minute => Ok(JsValue::Rational(inner.minutes().as_inner())), - DateTimeValues::Second => Ok(JsValue::Rational(inner.seconds().as_inner())), - DateTimeValues::Millisecond => Ok(JsValue::Rational(inner.milliseconds().as_inner())), - DateTimeValues::Microsecond => Ok(JsValue::Rational(inner.microseconds().as_inner())), - DateTimeValues::Nanosecond => Ok(JsValue::Rational(inner.nanoseconds().as_inner())), + DateTimeValues::Year => Ok(JsValue::new(inner.years().as_inner())), + DateTimeValues::Month => Ok(JsValue::new(inner.months().as_inner())), + DateTimeValues::Week => Ok(JsValue::new(inner.weeks().as_inner())), + DateTimeValues::Day => Ok(JsValue::new(inner.days().as_inner())), + DateTimeValues::Hour => Ok(JsValue::new(inner.hours().as_inner())), + DateTimeValues::Minute => Ok(JsValue::new(inner.minutes().as_inner())), + DateTimeValues::Second => Ok(JsValue::new(inner.seconds().as_inner())), + DateTimeValues::Millisecond => Ok(JsValue::new(inner.milliseconds().as_inner())), + DateTimeValues::Microsecond => Ok(JsValue::new(inner.microseconds().as_inner())), + DateTimeValues::Nanosecond => Ok(JsValue::new(inner.nanoseconds().as_inner())), DateTimeValues::MonthCode => unreachable!( "Any other DateTimeValue fields on Duration would be an implementation error." ), @@ -635,15 +635,15 @@ impl Duration { JsNativeError::typ().with_message("this value must be a Duration object.") })?; - let round_to = match args.first() { + let round_to = match args.first().map(JsValue::variant) { // 3. If roundTo is undefined, then - None | Some(JsValue::Undefined) => { + None | Some(JsVariant::Undefined) => { return Err(JsNativeError::typ() .with_message("roundTo cannot be undefined.") .into()) } // 4. If Type(roundTo) is String, then - Some(JsValue::String(rt)) => { + Some(JsVariant::String(rt)) => { // a. Let paramString be roundTo. let param_string = rt.clone(); // b. Set roundTo to OrdinaryObjectCreate(null). @@ -658,8 +658,9 @@ impl Duration { } // 5. Else, Some(round_to) => { + // TODO: remove this clone. // a. Set roundTo to ? GetOptionsObject(roundTo). - get_options_object(round_to)? + get_options_object(&JsValue::from(round_to))? } }; @@ -731,15 +732,15 @@ impl Duration { let total_of = args.get_or_undefined(0); - let total_of = match total_of { + let total_of = match total_of.variant() { // 3. If totalOf is undefined, throw a TypeError exception. - JsValue::Undefined => { + JsVariant::Undefined => { return Err(JsNativeError::typ() .with_message("totalOf cannot be undefined.") .into()); } // 4. If Type(totalOf) is String, then - JsValue::String(param_string) => { + JsVariant::String(param_string) => { // a. Let paramString be totalOf. // b. Set totalOf to OrdinaryObjectCreate(null). let total_of = JsObject::with_null_proto(); @@ -831,9 +832,9 @@ pub(crate) fn to_temporal_duration_record( context: &mut Context, ) -> JsResult { // 1. If Type(temporalDurationLike) is not Object, then - let JsValue::Object(duration_obj) = temporal_duration_like else { + let Some(duration_obj) = temporal_duration_like.as_object() else { // a. If temporalDurationLike is not a String, throw a TypeError exception. - let JsValue::String(duration_string) = temporal_duration_like else { + let Some(duration_string) = temporal_duration_like.as_string() else { return Err(JsNativeError::typ() .with_message("Invalid TemporalDurationLike value.") .into()); @@ -920,7 +921,7 @@ pub(crate) fn to_temporal_partial_duration( context: &mut Context, ) -> JsResult { // 1. If Type(temporalDurationLike) is not Object, then - let JsValue::Object(unknown_object) = duration_like else { + let Some(unknown_object) = duration_like.as_object() else { // a. Throw a TypeError exception. return Err(JsNativeError::typ() .with_message("temporalDurationLike must be an object.") diff --git a/core/engine/src/builtins/temporal/duration/tests.rs b/core/engine/src/builtins/temporal/duration/tests.rs index 4deadf08195..fb050899ee1 100644 --- a/core/engine/src/builtins/temporal/duration/tests.rs +++ b/core/engine/src/builtins/temporal/duration/tests.rs @@ -25,3 +25,48 @@ fn duration_abs() { TestAction::assert_eq("abs.milliseconds", 0), ]); } + +#[test] +fn basic() { + run_test_actions([ + TestAction::run( + r#" + var dur = new Temporal.Duration(5, 5, 5, 5, 5, 5, 5, 5, 5, 0); + "#, + ), + TestAction::assert_eq("dur.years", 5), + TestAction::assert_eq("dur.months", 5), + TestAction::assert_eq("dur.weeks", 5), + TestAction::assert_eq("dur.days", 5), + TestAction::assert_eq("dur.hours", 5), + TestAction::assert_eq("dur.minutes", 5), + TestAction::assert_eq("dur.seconds", 5), + TestAction::assert_eq("dur.milliseconds", 5), + TestAction::assert_eq("dur.microseconds", 5), + TestAction::assert_eq("dur.nanoseconds", 0), + // Negative + TestAction::run("dur = new Temporal.Duration(-5, -5, -5, -5, -5, -5, -5, -5, -5, 0)"), + TestAction::assert_eq("dur.years", -5), + TestAction::assert_eq("dur.months", -5), + TestAction::assert_eq("dur.weeks", -5), + TestAction::assert_eq("dur.days", -5), + TestAction::assert_eq("dur.hours", -5), + TestAction::assert_eq("dur.minutes", -5), + TestAction::assert_eq("dur.seconds", -5), + TestAction::assert_eq("dur.milliseconds", -5), + TestAction::assert_eq("dur.microseconds", -5), + TestAction::assert_eq("dur.nanoseconds", 0), + // Negative Zero + TestAction::run("dur = new Temporal.Duration(-0, -0, -0, -0, -0, -0, -0, -0, -0, 0)"), + TestAction::assert_eq("dur.years", 0), + TestAction::assert_eq("dur.months", 0), + TestAction::assert_eq("dur.weeks", 0), + TestAction::assert_eq("dur.days", 0), + TestAction::assert_eq("dur.hours", 0), + TestAction::assert_eq("dur.minutes", 0), + TestAction::assert_eq("dur.seconds", 0), + TestAction::assert_eq("dur.milliseconds", 0), + TestAction::assert_eq("dur.microseconds", 0), + TestAction::assert_eq("dur.nanoseconds", 0), + ]); +} diff --git a/core/engine/src/builtins/temporal/instant/mod.rs b/core/engine/src/builtins/temporal/instant/mod.rs index 1103adc5d5a..182491fbc79 100644 --- a/core/engine/src/builtins/temporal/instant/mod.rs +++ b/core/engine/src/builtins/temporal/instant/mod.rs @@ -1,5 +1,7 @@ //! Boa's implementation of ECMAScript's `Temporal.Instant` builtin object. +use super::options::get_difference_settings; +use crate::value::JsVariant; use crate::{ builtins::{ options::{get_option, get_options_object}, @@ -28,8 +30,6 @@ use temporal_rs::{ Instant as InnerInstant, }; -use super::options::get_difference_settings; - /// The `Temporal.Instant` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] // SAFETY: Instant does not contain any traceable values. @@ -352,15 +352,15 @@ impl Instant { JsNativeError::typ().with_message("the this object must be an instant object.") })?; - let round_to = match args.first() { + let round_to = match args.first().map(JsValue::variant) { // 3. If roundTo is undefined, then - None | Some(JsValue::Undefined) => { + None | Some(JsVariant::Undefined) => { return Err(JsNativeError::typ() .with_message("roundTo cannot be undefined.") .into()) } // 4. If Type(roundTo) is String, then - Some(JsValue::String(rt)) => { + Some(JsVariant::String(rt)) => { // a. Let paramString be roundTo. let param_string = rt.clone(); // b. Set roundTo to OrdinaryObjectCreate(null). @@ -375,8 +375,9 @@ impl Instant { } // 5. Else, Some(round_to) => { + // TODO: remove this clone. // a. Set roundTo to ? GetOptionsObject(roundTo). - get_options_object(round_to)? + get_options_object(&JsValue::from(round_to))? } }; diff --git a/core/engine/src/builtins/temporal/mod.rs b/core/engine/src/builtins/temporal/mod.rs index 243cf1453b8..0769ef4af23 100644 --- a/core/engine/src/builtins/temporal/mod.rs +++ b/core/engine/src/builtins/temporal/mod.rs @@ -26,6 +26,7 @@ pub use self::{ plain_time::*, plain_year_month::*, zoneddatetime::*, }; +use crate::value::JsVariant; use crate::{ builtins::{iterable::IteratorRecord, BuiltInBuilder, BuiltInObject, IntrinsicObject}, context::intrinsics::Intrinsics, @@ -243,10 +244,10 @@ pub(crate) fn to_relative_temporal_object( context: &mut Context, ) -> RelativeTemporalObjectResult { let relative_to = options.get(js_string!("relativeTo"), context)?; - let plain_date = match relative_to { - JsValue::String(relative_to_str) => JsValue::from(relative_to_str), - JsValue::Object(relative_to_obj) => JsValue::from(relative_to_obj), - JsValue::Undefined => return Ok((None, None)), + let plain_date = match relative_to.variant() { + JsVariant::String(relative_to_str) => JsValue::from(relative_to_str.clone()), + JsVariant::Object(relative_to_obj) => JsValue::from(relative_to_obj.clone()), + JsVariant::Undefined => return Ok((None, None)), _ => { return Err(JsNativeError::typ() .with_message("Invalid type for converting to relativeTo object") @@ -353,17 +354,17 @@ pub(crate) fn to_integer_with_truncation(value: &JsValue, context: &mut Context) /// Abstract operation 13.45 `ToIntegerIfIntegral( argument )` #[inline] -pub(crate) fn to_integer_if_integral(arg: &JsValue, context: &mut Context) -> JsResult { +pub(crate) fn to_integer_if_integral(arg: &JsValue, _context: &mut Context) -> JsResult { // 1. Let number be ? ToNumber(argument). // 2. If IsIntegralNumber(number) is false, throw a RangeError exception. // 3. Return ℝ(number). - if !arg.is_integral_number() { + let Some(arg) = arg.as_i32() else { return Err(JsNativeError::range() .with_message("value to convert is not an integral number.") .into()); - } + }; - arg.to_i32(context) + Ok(arg) } // 13.46 `PrepareTemporalFields ( fields, fieldNames, requiredFields [ , duplicateBehaviour ] )` diff --git a/core/engine/src/builtins/temporal/plain_date/mod.rs b/core/engine/src/builtins/temporal/plain_date/mod.rs index 1895cee55a0..f58411ecab1 100644 --- a/core/engine/src/builtins/temporal/plain_date/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date/mod.rs @@ -924,7 +924,7 @@ pub(crate) fn to_temporal_date( } // 5. If item is not a String, throw a TypeError exception. - let JsValue::String(date_like_string) = item else { + let Some(date_like_string) = item.as_string() else { return Err(JsNativeError::typ() .with_message("ToTemporalDate item must be an object or string.") .into()); @@ -967,9 +967,8 @@ pub(crate) fn to_partial_date_record( let month_code = partial_object .get(js_string!("monthCode"), context)? .map(|v| { - let JsValue::String(month_code) = - v.to_primitive(context, crate::value::PreferredType::String)? - else { + let v = v.to_primitive(context, crate::value::PreferredType::String)?; + let Some(month_code) = v.as_string() else { return Err(JsNativeError::typ() .with_message("The monthCode field value must be a string.") .into()); @@ -989,9 +988,8 @@ pub(crate) fn to_partial_date_record( let era = partial_object .get(js_string!("era"), context)? .map(|v| { - let JsValue::String(era) = - v.to_primitive(context, crate::value::PreferredType::String)? - else { + let v = v.to_primitive(context, crate::value::PreferredType::String)?; + let Some(era) = v.as_string() else { return Err(JsError::from( JsNativeError::typ() .with_message("The monthCode field value must be a string."), diff --git a/core/engine/src/builtins/temporal/plain_date_time/mod.rs b/core/engine/src/builtins/temporal/plain_date_time/mod.rs index 39395e643b1..73811ecc439 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/mod.rs @@ -22,18 +22,18 @@ use boa_profiler::Profiler; #[cfg(test)] mod tests; -use temporal_rs::{ - options::{ArithmeticOverflow, RoundingIncrement, RoundingOptions, TemporalRoundingMode}, - partial::PartialDateTime, - PlainDateTime as InnerDateTime, PlainTime, -}; - use super::{ calendar::{get_temporal_calendar_slot_value_with_default, to_temporal_calendar_slot_value}, create_temporal_duration, options::{get_difference_settings, get_temporal_unit, TemporalUnitGroup}, to_temporal_duration_record, to_temporal_time, PlainDate, ZonedDateTime, }; +use crate::value::JsVariant; +use temporal_rs::{ + options::{ArithmeticOverflow, RoundingIncrement, RoundingOptions, TemporalRoundingMode}, + partial::PartialDateTime, + PlainDateTime as InnerDateTime, PlainTime, +}; /// The `Temporal.PlainDateTime` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] @@ -826,15 +826,15 @@ impl PlainDateTime { JsNativeError::typ().with_message("the this object must be a PlainTime object.") })?; - let round_to = match args.first() { + let round_to = match args.first().map(JsValue::variant) { // 3. If roundTo is undefined, then - None | Some(JsValue::Undefined) => { + None | Some(JsVariant::Undefined) => { return Err(JsNativeError::typ() .with_message("roundTo cannot be undefined.") .into()) } // 4. If Type(roundTo) is String, then - Some(JsValue::String(rt)) => { + Some(JsVariant::String(rt)) => { // a. Let paramString be roundTo. let param_string = rt.clone(); // b. Set roundTo to OrdinaryObjectCreate(null). @@ -850,7 +850,7 @@ impl PlainDateTime { // 5. Else, Some(round_to) => { // a. Set roundTo to ? GetOptionsObject(roundTo). - get_options_object(round_to)? + get_options_object(&JsValue::from(round_to))? } }; diff --git a/core/engine/src/builtins/temporal/plain_month_day/mod.rs b/core/engine/src/builtins/temporal/plain_month_day/mod.rs index 29d3144891e..1713cc005fc 100644 --- a/core/engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/core/engine/src/builtins/temporal/plain_month_day/mod.rs @@ -337,9 +337,8 @@ fn to_temporal_month_day( let month_code = item .get_v(js_string!("monthCode"), context)? .map(|v| { - let JsValue::String(month_code) = - v.to_primitive(context, crate::value::PreferredType::String)? - else { + let primitive = v.to_primitive(context, crate::value::PreferredType::String)?; + let Some(month_code) = primitive.as_string() else { return Err(JsNativeError::typ() .with_message("The monthCode field value must be a string.") .into()); diff --git a/core/engine/src/builtins/temporal/plain_time/mod.rs b/core/engine/src/builtins/temporal/plain_time/mod.rs index 321338d4809..fd9679b91ec 100644 --- a/core/engine/src/builtins/temporal/plain_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_time/mod.rs @@ -1,5 +1,11 @@ //! Boa's implementation of the ECMAScript `Temporal.PlainTime` builtin object. +use super::{ + create_temporal_duration, + options::{get_difference_settings, get_temporal_unit, TemporalUnitGroup}, + to_integer_with_truncation, to_temporal_duration_record, PlainDateTime, ZonedDateTime, +}; +use crate::value::JsVariant; use crate::{ builtins::{ options::{get_option, get_options_object}, @@ -21,12 +27,6 @@ use temporal_rs::{ PlainTime as PlainTimeInner, }; -use super::{ - create_temporal_duration, - options::{get_difference_settings, get_temporal_unit, TemporalUnitGroup}, - to_integer_with_truncation, to_temporal_duration_record, PlainDateTime, ZonedDateTime, -}; - /// The `Temporal.PlainTime` object. #[derive(Debug, Clone, Copy, Trace, Finalize, JsData)] // Safety: Time does not contain any traceable types. @@ -432,15 +432,15 @@ impl PlainTime { JsNativeError::typ().with_message("the this object must be a PlainTime object.") })?; - let round_to = match args.first() { + let round_to = match args.first().map(JsValue::variant) { // 3. If roundTo is undefined, then - None | Some(JsValue::Undefined) => { + None | Some(JsVariant::Undefined) => { return Err(JsNativeError::typ() .with_message("roundTo cannot be undefined.") .into()) } // 4. If Type(roundTo) is String, then - Some(JsValue::String(rt)) => { + Some(JsVariant::String(rt)) => { // a. Let paramString be roundTo. let param_string = rt.clone(); // b. Set roundTo to OrdinaryObjectCreate(null). @@ -456,7 +456,7 @@ impl PlainTime { // 5. Else, Some(round_to) => { // a. Set roundTo to ? GetOptionsObject(roundTo). - get_options_object(round_to)? + get_options_object(&JsValue::from(round_to))? } }; @@ -620,10 +620,11 @@ pub(crate) fn to_temporal_time( context: &mut Context, ) -> JsResult { // 1.If overflow is not present, set overflow to "constrain". - let options = options.unwrap_or(&JsValue::Undefined); + let binding = JsValue::undefined(); + let options = options.unwrap_or(&binding); // 2. If item is an Object, then - match value { - JsValue::Object(object) => { + match value.variant() { + JsVariant::Object(object) => { // a. If item has an [[InitializedTemporalTime]] internal slot, then if let Some(time) = object.downcast_ref::() { // i. Return item. @@ -678,7 +679,7 @@ pub(crate) fn to_temporal_time( .map_err(Into::into) } // 3. Else, - JsValue::String(str) => { + JsVariant::String(str) => { // b. Let result be ? ParseTemporalTimeString(item). // c. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true. str.to_std_string_escaped() diff --git a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs index bfee3ae071f..bf9449d979e 100644 --- a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs +++ b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs @@ -13,7 +13,7 @@ use crate::{ string::StaticJsStrings, value::{IntoOrUndefined, PreferredType}, Context, JsArgs, JsBigInt, JsData, JsError, JsNativeError, JsObject, JsResult, JsString, - JsSymbol, JsValue, + JsSymbol, JsValue, JsVariant, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; @@ -359,7 +359,7 @@ impl BuiltInConstructor for ZonedDateTime { }; // 4. If timeZone is not a String, throw a TypeError exception. - let JsValue::String(timezone_str) = args.get_or_undefined(1) else { + let Some(timezone_str) = args.get_or_undefined(1).as_string() else { return Err(JsNativeError::typ() .with_message("timeZone must be a string.") .into()); @@ -383,7 +383,7 @@ impl BuiltInConstructor for ZonedDateTime { let calendar = args .get(2) .map(|v| { - if let JsValue::String(calendar_str) = v { + if let Some(calendar_str) = v.as_string() { Calendar::from_str(&calendar_str.to_std_string_escaped()) .map_err(Into::::into) } else { @@ -610,7 +610,7 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Ok((zdt.inner.epoch_milliseconds()).into()) + Ok(zdt.inner.epoch_milliseconds().into()) } /// 6.3.18 get `Temporal.ZonedDateTime.prototype.epochNanosecond` @@ -889,8 +889,8 @@ pub(crate) fn to_temporal_zoneddatetime( // 2. Let offsetBehaviour be option. // 3. Let matchBehaviour be match-exactly. // 4. If item is an Object, then - match value { - JsValue::Object(object) => { + match value.variant() { + JsVariant::Object(object) => { // a. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then if let Some(zdt) = object.downcast_ref::() { // i. NOTE: The following steps, and similar ones below, read options @@ -964,7 +964,7 @@ pub(crate) fn to_temporal_zoneddatetime( context.tz_provider(), )?) } - JsValue::String(zdt_source) => { + JsVariant::String(zdt_source) => { // b. Let result be ? ParseISODateTime(item, « TemporalDateTimeString[+Zoned] »). // c. Let annotation be result.[[TimeZone]].[[TimeZoneAnnotation]]. // d. Assert: annotation is not empty. @@ -1025,7 +1025,7 @@ pub(crate) fn to_temporal_timezone_identifier( } // 2. If temporalTimeZoneLike is not a String, throw a TypeError exception. - let JsValue::String(tz_string) = value else { + let Some(tz_string) = value.as_string() else { return Err(JsNativeError::typ() .with_message("timeZone must be a string or Temporal.ZonedDateTime") .into()); @@ -1048,7 +1048,7 @@ fn to_offset_string(value: &JsValue, context: &mut Context) -> JsResult // 1. Let offset be ? ToPrimitive(argument, string). let offset = value.to_primitive(context, PreferredType::String)?; // 2. If offset is not a String, throw a TypeError exception. - let JsValue::String(offset_string) = offset else { + let Some(offset_string) = offset.as_string() else { return Err(JsNativeError::typ() .with_message("offset must be a String.") .into()); diff --git a/core/engine/src/builtins/typed_array/builtin.rs b/core/engine/src/builtins/typed_array/builtin.rs index 51512ee7bc7..51690f9fb6a 100644 --- a/core/engine/src/builtins/typed_array/builtin.rs +++ b/core/engine/src/builtins/typed_array/builtin.rs @@ -9,6 +9,7 @@ use num_traits::Zero; use super::{ object::typed_array_set_element, ContentType, TypedArray, TypedArrayKind, TypedArrayMarker, }; +use crate::value::JsVariant; use crate::{ builtins::{ array::{find_via_predicate, ArrayIterator, Direction}, @@ -204,20 +205,18 @@ impl BuiltinTypedArray { } }; - let mapping = match args.get(1) { + let mapping = match args.get(1).map(JsValue::variant) { // 3. If mapfn is undefined, let mapping be false. - None | Some(JsValue::Undefined) => None, + None | Some(JsVariant::Undefined) => None, // 4. Else, - Some(v) => match v.as_object() { - // b. Let mapping be true. - Some(obj) if obj.is_callable() => Some(obj), - // a. If IsCallable(mapfn) is false, throw a TypeError exception. - _ => { - return Err(JsNativeError::typ() - .with_message("TypedArray.from called with non-callable mapfn") - .into()) - } - }, + // b. Let mapping be true. + Some(JsVariant::Object(obj)) if obj.is_callable() => Some(obj), + // a. If IsCallable(mapfn) is false, throw a TypeError exception. + _ => { + return Err(JsNativeError::typ() + .with_message("TypedArray.from called with non-callable mapfn") + .into()); + } }; // 5. Let usingIterator be ? GetMethod(source, @@iterator). @@ -2219,9 +2218,9 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - let compare_fn = match args.first() { - None | Some(JsValue::Undefined) => None, - Some(JsValue::Object(obj)) if obj.is_callable() => Some(obj), + let compare_fn = match args.first().map(JsValue::variant) { + None | Some(JsVariant::Undefined) => None, + Some(JsVariant::Object(obj)) if obj.is_callable() => Some(obj), _ => { return Err(JsNativeError::typ() .with_message("TypedArray.sort called with non-callable comparefn") @@ -2271,9 +2270,9 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - let compare_fn = match args.first() { - None | Some(JsValue::Undefined) => None, - Some(JsValue::Object(obj)) if obj.is_callable() => Some(obj), + let compare_fn = match args.first().map(JsValue::variant) { + None | Some(JsVariant::Undefined) => None, + Some(JsVariant::Object(obj)) if obj.is_callable() => Some(obj), _ => { return Err(JsNativeError::typ() .with_message("TypedArray.sort called with non-callable comparefn") @@ -2616,7 +2615,7 @@ impl BuiltinTypedArray { obj.downcast_ref::() .map(|o| o.kind().js_name().into()) }) - .unwrap_or(JsValue::Undefined)) + .unwrap_or(JsValue::undefined())) } /// `TypedArraySpeciesCreate ( exemplar, argumentList )` @@ -2665,7 +2664,7 @@ impl BuiltinTypedArray { // 2. Let taRecord be ? ValidateTypedArray(newTypedArray, seq-cst). let (new_ta, buf_len) = - TypedArray::validate(&JsValue::Object(new_typed_array), Ordering::SeqCst)?; + TypedArray::validate(&JsValue::new(new_typed_array), Ordering::SeqCst)?; // 3. If the number of elements in argumentList is 1 and argumentList[0] is a Number, then if args.len() == 1 { @@ -3138,22 +3137,22 @@ fn compare_typed_array_elements( return Ok(cmp::Ordering::Less); } - match (x, y) { - (JsValue::BigInt(x), JsValue::BigInt(y)) => { + match (x.variant(), y.variant()) { + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => { // Note: Other steps are not relevant for BigInts. // 6. If x < y, return -1𝔽. // 7. If x > y, return 1𝔽. // 10. Return +0𝔽. Ok(x.cmp(y)) } - (JsValue::Integer(x), JsValue::Integer(y)) => { + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => { // Note: Other steps are not relevant for integers. // 6. If x < y, return -1𝔽. // 7. If x > y, return 1𝔽. // 10. Return +0𝔽. - Ok(x.cmp(y)) + Ok(x.cmp(&y)) } - (JsValue::Rational(x), JsValue::Rational(y)) => { + (JsVariant::Float64(x), JsVariant::Float64(y)) => { // 3. If x and y are both NaN, return +0𝔽. if x.is_nan() && y.is_nan() { return Ok(cmp::Ordering::Equal); diff --git a/core/engine/src/lib.rs b/core/engine/src/lib.rs index 7f1ad5d0027..3af1e9df233 100644 --- a/core/engine/src/lib.rs +++ b/core/engine/src/lib.rs @@ -73,11 +73,10 @@ clippy::missing_panics_doc, )] +extern crate self as boa_engine; #[cfg(not(target_has_atomic = "ptr"))] compile_error!("Boa requires a lock free `AtomicUsize` in order to work properly."); -extern crate self as boa_engine; - pub use boa_ast as ast; pub use boa_gc as gc; pub use boa_interner as interner; @@ -125,7 +124,7 @@ pub mod prelude { script::Script, string::{JsStr, JsString}, symbol::JsSymbol, - value::JsValue, + value::{JsValue, JsVariant}, }; pub use boa_gc::{Finalize, Trace}; pub use boa_macros::{js_str, JsData}; @@ -162,8 +161,8 @@ mod try_into_js_result_impls; /// A utility trait to make working with function arguments easier. pub trait JsArgs { - /// Utility function to `get` a parameter from a `[JsValue]` or default to `JsValue::Undefined` - /// if `get` returns `None`. + /// Utility function to `get` a parameter from a `[JsValue]` or default to + /// `JsValue::undefined()` if `get` returns `None`. /// /// Call this if you are thinking of calling something similar to /// `args.get(n).cloned().unwrap_or_default()` or @@ -175,7 +174,7 @@ pub trait JsArgs { impl JsArgs for [JsValue] { fn get_or_undefined(&self, index: usize) -> &JsValue { - const UNDEFINED: &JsValue = &JsValue::Undefined; + const UNDEFINED: &JsValue = &JsValue::undefined(); self.get(index).unwrap_or(UNDEFINED) } } diff --git a/core/engine/src/native_function.rs b/core/engine/src/native_function.rs index 721f53291fd..75f3b933abc 100644 --- a/core/engine/src/native_function.rs +++ b/core/engine/src/native_function.rs @@ -5,6 +5,7 @@ use boa_gc::{custom_trace, Finalize, Gc, Trace}; +use crate::value::JsVariant; use crate::{ builtins::{function::ConstructorKind, OrdinaryObject}, context::intrinsics::StandardConstructors, @@ -418,8 +419,8 @@ fn native_function_construct( let result = function .call(&new_target, &args, context) .map_err(|err| err.inject_realm(context.realm().clone())) - .and_then(|v| match v { - JsValue::Object(ref o) => Ok(o.clone()), + .and_then(|v| match v.variant() { + JsVariant::Object(o) => Ok(o.clone()), val => { if constructor.expect("must be a constructor").is_base() || val.is_undefined() { let prototype = get_prototype_from_constructor( diff --git a/core/engine/src/object/builtins/jsarray.rs b/core/engine/src/object/builtins/jsarray.rs index 7b7aff8d0e7..27457869443 100644 --- a/core/engine/src/object/builtins/jsarray.rs +++ b/core/engine/src/object/builtins/jsarray.rs @@ -439,11 +439,12 @@ impl Deref for JsArray { impl TryFromJs for JsArray { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not an Array object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jsarraybuffer.rs b/core/engine/src/object/builtins/jsarraybuffer.rs index 684e69f3307..bc07d86c830 100644 --- a/core/engine/src/object/builtins/jsarraybuffer.rs +++ b/core/engine/src/object/builtins/jsarraybuffer.rs @@ -120,7 +120,7 @@ impl JsArrayBuffer { let obj = JsObject::new( context.root_shape(), prototype, - ArrayBuffer::from_data(block, JsValue::Undefined), + ArrayBuffer::from_data(block, JsValue::undefined()), ); Ok(Self { inner: obj }) @@ -302,11 +302,12 @@ impl Deref for JsArrayBuffer { impl TryFromJs for JsArrayBuffer { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not an ArrayBuffer object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jsdataview.rs b/core/engine/src/object/builtins/jsdataview.rs index b5157155b36..28cde98acb7 100644 --- a/core/engine/src/object/builtins/jsdataview.rs +++ b/core/engine/src/object/builtins/jsdataview.rs @@ -528,11 +528,12 @@ impl Deref for JsDataView { impl TryFromJs for JsDataView { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() - .with_message("value is not an DataView object") - .into()), + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() + .with_message("value is not a DataView object") + .into()) } } } diff --git a/core/engine/src/object/builtins/jsdate.rs b/core/engine/src/object/builtins/jsdate.rs index 2b080d1c8d6..1000c39e0a9 100644 --- a/core/engine/src/object/builtins/jsdate.rs +++ b/core/engine/src/object/builtins/jsdate.rs @@ -68,7 +68,7 @@ impl JsDate { /// Same as JavaScript's `Date.now()` #[inline] pub fn now(context: &mut Context) -> JsResult { - Date::now(&JsValue::Null, &[JsValue::Null], context) + Date::now(&JsValue::null(), &[JsValue::null()], context) } // DEBUG: Uses RFC3339 internally therefore could match es6 spec of ISO8601 <======== @@ -80,7 +80,7 @@ impl JsDate { /// Same as JavaScript's `Date.parse(value)`. #[inline] pub fn parse(value: JsValue, context: &mut Context) -> JsResult { - Date::parse(&JsValue::Null, &[value], context) + Date::parse(&JsValue::null(), &[value], context) } /// Takes a [year, month, day, hour, minute, second, millisecond] @@ -89,7 +89,7 @@ impl JsDate { /// Same as JavaScript's `Date.UTC()` #[inline] pub fn utc(values: &[JsValue], context: &mut Context) -> JsResult { - Date::utc(&JsValue::Null, values, context) + Date::utc(&JsValue::null(), values, context) } /// Returns the day of the month(1-31) for the specified date @@ -175,7 +175,7 @@ impl JsDate { /// Same as JavaScript's `Date.prototype.getTimezoneOffset()`. #[inline] pub fn get_timezone_offset(&self, context: &mut Context) -> JsResult { - Date::get_timezone_offset(&self.inner.clone().into(), &[JsValue::Null], context) + Date::get_timezone_offset(&self.inner.clone().into(), &[JsValue::null()], context) } /// Returns the day (date) of the month (1–31) in the specified @@ -440,7 +440,7 @@ impl JsDate { /// Same as JavaScript's `Date.prototype.toDateString()`. #[inline] pub fn to_date_string(&self, context: &mut Context) -> JsResult { - Date::to_date_string(&self.inner.clone().into(), &[JsValue::Null], context) + Date::to_date_string(&self.inner.clone().into(), &[JsValue::null()], context) } /// DEPRECATED: This feature is no longer recommended. @@ -451,7 +451,7 @@ impl JsDate { #[deprecated] #[inline] pub fn to_gmt_string(&self, context: &mut Context) -> JsResult { - Date::to_utc_string(&self.inner.clone().into(), &[JsValue::Null], context) + Date::to_utc_string(&self.inner.clone().into(), &[JsValue::null()], context) } /// Returns the given date in the ISO 8601 format according to universal @@ -460,7 +460,7 @@ impl JsDate { /// Same as JavaScript's `Date.prototype.toISOString()`. #[inline] pub fn to_iso_string(&self, context: &mut Context) -> JsResult { - Date::to_iso_string(&self.inner.clone().into(), &[JsValue::Null], context) + Date::to_iso_string(&self.inner.clone().into(), &[JsValue::null()], context) } /// Returns a string representing the Date using `to_iso_string()`. @@ -468,7 +468,7 @@ impl JsDate { /// Same as JavaScript's `Date.prototype.toJSON()`. #[inline] pub fn to_json(&self, context: &mut Context) -> JsResult { - Date::to_json(&self.inner.clone().into(), &[JsValue::Null], context) + Date::to_json(&self.inner.clone().into(), &[JsValue::null()], context) } /// Returns a string representing the date portion of the given Date instance @@ -511,7 +511,7 @@ impl JsDate { /// Same as JavaScript's `Date.prototype.toString()`. #[inline] pub fn to_string(&self, context: &mut Context) -> JsResult { - Date::to_string(&self.inner.clone().into(), &[JsValue::Null], context) + Date::to_string(&self.inner.clone().into(), &[JsValue::null()], context) } /// Returns the "time" portion of the Date as human-readable string. @@ -519,7 +519,7 @@ impl JsDate { /// Same as JavaScript's `Date.prototype.toTimeString()`. #[inline] pub fn to_time_string(&self, context: &mut Context) -> JsResult { - Date::to_time_string(&self.inner.clone().into(), &[JsValue::Null], context) + Date::to_time_string(&self.inner.clone().into(), &[JsValue::null()], context) } /// Returns a string representing the given date using the UTC time zone. @@ -527,7 +527,7 @@ impl JsDate { /// Same as JavaScript's `Date.prototype.toUTCString()`. #[inline] pub fn to_utc_string(&self, context: &mut Context) -> JsResult { - Date::to_utc_string(&self.inner.clone().into(), &[JsValue::Null], context) + Date::to_utc_string(&self.inner.clone().into(), &[JsValue::null()], context) } /// Returns the primitive value pf Date object. @@ -535,7 +535,7 @@ impl JsDate { /// Same as JavaScript's `Date.prototype.valueOf()`. #[inline] pub fn value_of(&self, context: &mut Context) -> JsResult { - Date::value_of(&self.inner.clone().into(), &[JsValue::Null], context) + Date::value_of(&self.inner.clone().into(), &[JsValue::null()], context) } /// Utility create a `Date` object from RFC3339 string @@ -584,11 +584,12 @@ impl Deref for JsDate { impl TryFromJs for JsDate { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not a Date object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jsfunction.rs b/core/engine/src/object/builtins/jsfunction.rs index 16ff8fdd898..efe107231f5 100644 --- a/core/engine/src/object/builtins/jsfunction.rs +++ b/core/engine/src/object/builtins/jsfunction.rs @@ -77,17 +77,18 @@ impl TypedJsFunction { impl TryFromJs for TypedJsFunction { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => JsFunction::from_object(o.clone()) + if let Some(o) = value.as_object() { + JsFunction::from_object(o.clone()) .ok_or_else(|| { JsNativeError::typ() .with_message("object is not a function") .into() }) - .map(JsFunction::typed), - _ => Err(JsNativeError::typ() + .map(JsFunction::typed) + } else { + Err(JsNativeError::typ() .with_message("value is not a Function object") - .into()), + .into()) } } } @@ -183,15 +184,16 @@ impl Deref for JsFunction { impl TryFromJs for JsFunction { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()).ok_or_else(|| { + if let Some(o) = value.as_object() { + Self::from_object(o.clone()).ok_or_else(|| { JsNativeError::typ() .with_message("object is not a function") .into() - }), - _ => Err(JsNativeError::typ() + }) + } else { + Err(JsNativeError::typ() .with_message("value is not a Function object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jsgenerator.rs b/core/engine/src/object/builtins/jsgenerator.rs index 33a6ba6bb5c..460638f364e 100644 --- a/core/engine/src/object/builtins/jsgenerator.rs +++ b/core/engine/src/object/builtins/jsgenerator.rs @@ -83,11 +83,12 @@ impl Deref for JsGenerator { impl TryFromJs for JsGenerator { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not a Generator object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jsmap.rs b/core/engine/src/object/builtins/jsmap.rs index fa27c52dd8d..89dca24b6df 100644 --- a/core/engine/src/object/builtins/jsmap.rs +++ b/core/engine/src/object/builtins/jsmap.rs @@ -447,11 +447,12 @@ impl Deref for JsMap { impl TryFromJs for JsMap { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not a Map object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jsmap_iterator.rs b/core/engine/src/object/builtins/jsmap_iterator.rs index d6fa7a82faf..1efbbdada2c 100644 --- a/core/engine/src/object/builtins/jsmap_iterator.rs +++ b/core/engine/src/object/builtins/jsmap_iterator.rs @@ -57,11 +57,12 @@ impl Deref for JsMapIterator { impl TryFromJs for JsMapIterator { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not a MapIterator object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jspromise.rs b/core/engine/src/object/builtins/jspromise.rs index c8af0652403..777113b8cec 100644 --- a/core/engine/src/object/builtins/jspromise.rs +++ b/core/engine/src/object/builtins/jspromise.rs @@ -1123,7 +1123,7 @@ impl JsPromise { /// /// let context = &mut Context::default(); /// let p1 = JsPromise::new(|fns, context| { - /// fns.resolve.call(&JsValue::Undefined, &[], context) + /// fns.resolve.call(&JsValue::undefined(), &[], context) /// }, context) /// .then( /// Some( @@ -1179,11 +1179,12 @@ impl std::ops::Deref for JsPromise { impl TryFromJs for JsPromise { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not a Promise object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jsproxy.rs b/core/engine/src/object/builtins/jsproxy.rs index 125723f71cb..e29ef3bb410 100644 --- a/core/engine/src/object/builtins/jsproxy.rs +++ b/core/engine/src/object/builtins/jsproxy.rs @@ -72,11 +72,12 @@ impl std::ops::Deref for JsProxy { impl TryFromJs for JsProxy { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not a Proxy object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jsregexp.rs b/core/engine/src/object/builtins/jsregexp.rs index fef40e6e0cc..93a76e62bb2 100644 --- a/core/engine/src/object/builtins/jsregexp.rs +++ b/core/engine/src/object/builtins/jsregexp.rs @@ -272,11 +272,12 @@ impl Deref for JsRegExp { impl TryFromJs for JsRegExp { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not a RegExp object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jsset.rs b/core/engine/src/object/builtins/jsset.rs index 6889fc9c254..f09f37b59f0 100644 --- a/core/engine/src/object/builtins/jsset.rs +++ b/core/engine/src/object/builtins/jsset.rs @@ -63,7 +63,7 @@ impl JsSet { /// Same as JavaScript's `set.clear()`. #[inline] pub fn clear(&self, context: &mut Context) -> JsResult { - Set::clear(&self.inner.clone().into(), &[JsValue::Null], context) + Set::clear(&self.inner.clone().into(), &[JsValue::null()], context) } /// Removes the element associated to the value. @@ -76,8 +76,8 @@ impl JsSet { T: Into, { // TODO: Make `delete` return a native `bool` - match Set::delete(&self.inner.clone().into(), &[value.into()], context)? { - JsValue::Boolean(bool) => Ok(bool), + match Set::delete(&self.inner.clone().into(), &[value.into()], context)?.as_boolean() { + Some(bool) => Ok(bool), _ => unreachable!("`delete` must always return a bool"), } } @@ -91,8 +91,8 @@ impl JsSet { T: Into, { // TODO: Make `has` return a native `bool` - match Set::has(&self.inner.clone().into(), &[value.into()], context)? { - JsValue::Boolean(bool) => Ok(bool), + match Set::has(&self.inner.clone().into(), &[value.into()], context)?.as_boolean() { + Some(bool) => Ok(bool), _ => unreachable!("`has` must always return a bool"), } } @@ -103,7 +103,7 @@ impl JsSet { /// Same as JavaScript's `set.values()`. #[inline] pub fn values(&self, context: &mut Context) -> JsResult { - let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)? + let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::null()], context)? .get_iterator(IteratorHint::Sync, context)?; JsSetIterator::from_object(iterator_object.iterator().clone()) @@ -116,7 +116,7 @@ impl JsSet { /// Same as JavaScript's `set.keys()`. #[inline] pub fn keys(&self, context: &mut Context) -> JsResult { - let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)? + let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::null()], context)? .get_iterator(IteratorHint::Sync, context)?; JsSetIterator::from_object(iterator_object.iterator().clone()) @@ -187,11 +187,12 @@ impl Deref for JsSet { impl TryFromJs for JsSet { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not a Set object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jsset_iterator.rs b/core/engine/src/object/builtins/jsset_iterator.rs index 124b987f334..604b0cc8726 100644 --- a/core/engine/src/object/builtins/jsset_iterator.rs +++ b/core/engine/src/object/builtins/jsset_iterator.rs @@ -28,7 +28,7 @@ impl JsSetIterator { } /// Advances the `JsSetIterator` and gets the next result in the `JsSet`. pub fn next(&self, context: &mut Context) -> JsResult { - SetIterator::next(&self.inner.clone().into(), &[JsValue::Null], context) + SetIterator::next(&self.inner.clone().into(), &[JsValue::null()], context) } } @@ -57,11 +57,12 @@ impl Deref for JsSetIterator { impl TryFromJs for JsSetIterator { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not a SetIterator object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jssharedarraybuffer.rs b/core/engine/src/object/builtins/jssharedarraybuffer.rs index d5dc69f8b5a..dcc9f5cca99 100644 --- a/core/engine/src/object/builtins/jssharedarraybuffer.rs +++ b/core/engine/src/object/builtins/jssharedarraybuffer.rs @@ -117,11 +117,12 @@ impl Deref for JsSharedArrayBuffer { impl TryFromJs for JsSharedArrayBuffer { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not a SharedArrayBuffer object") - .into()), + .into()) } } } diff --git a/core/engine/src/object/builtins/jstypedarray.rs b/core/engine/src/object/builtins/jstypedarray.rs index e694e2d0868..cbd264e5493 100644 --- a/core/engine/src/object/builtins/jstypedarray.rs +++ b/core/engine/src/object/builtins/jstypedarray.rs @@ -426,7 +426,7 @@ impl JsTypedArray { /// assert_eq!(initialized8_array.get(5, context)?, JsValue::new(0)); /// assert_eq!(initialized8_array.get(6, context)?, JsValue::new(0)); /// assert_eq!(initialized8_array.get(7, context)?, JsValue::new(0)); - /// assert_eq!(initialized8_array.get(8, context)?, JsValue::Undefined); + /// assert_eq!(initialized8_array.get(8, context)?, JsValue::undefined()); /// /// # Ok(()) /// # } @@ -506,7 +506,7 @@ impl JsTypedArray { /// .unwrap_or_default() /// .as_number() /// .expect("error at number conversion"); - /// Ok(JsValue::Boolean(element > 10.0)) + /// Ok(JsValue::from(element > 10.0)) /// }), /// ) /// .build(); @@ -564,13 +564,13 @@ impl JsTypedArray { /// .unwrap_or_default() /// .as_number() /// .expect("error at number conversion"); - /// Ok(JsValue::Boolean(element < 200.0)) + /// Ok(JsValue::from(element < 200.0)) /// }), /// ) /// .build(); /// assert_eq!( /// array.find_last(lower_than_200_predicate.clone(), None, context), - /// Ok(JsValue::Integer(199)) + /// Ok(JsValue::new(199)) /// ); /// /// # Ok(()) @@ -614,13 +614,13 @@ impl JsTypedArray { /// .unwrap_or_default() /// .as_number() /// .expect("error at number conversion"); - /// Ok(JsValue::Boolean(element < 200.0)) + /// Ok(JsValue::from(element < 200.0)) /// }), /// ) /// .build(); /// assert_eq!( /// array.find_last(lower_than_200_predicate.clone(), None, context), - /// Ok(JsValue::Integer(199)) + /// Ok(JsValue::new(199)) /// ); /// /// # Ok(()) @@ -673,7 +673,7 @@ impl JsTypedArray { /// .to_uint8(inner_context) /// .expect("error at number conversion"); /// *captures.borrow_mut() += element; - /// Ok(JsValue::Undefined) + /// Ok(JsValue::undefined()) /// }, /// Gc::clone(&num_to_modify), /// ), @@ -912,11 +912,12 @@ impl Deref for JsTypedArray { impl TryFromJs for JsTypedArray { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("value is not a TypedArray object") - .into()), + .into()) } } } @@ -1062,15 +1063,16 @@ macro_rules! JsTypedArrayType { impl TryFromJs for $name { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Self::from_object(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Self::from_object(o.clone()) + } else { + Err(JsNativeError::typ() .with_message(concat!( "value is not a ", stringify!($constructor_function), " object" )) - .into()), + .into()) } } } diff --git a/core/engine/src/object/internal_methods/mod.rs b/core/engine/src/object/internal_methods/mod.rs index ac671752f3c..c7a72a94a85 100644 --- a/core/engine/src/object/internal_methods/mod.rs +++ b/core/engine/src/object/internal_methods/mod.rs @@ -320,7 +320,7 @@ impl JsObject { /// Then, reference this static in the creation phase of an `ObjectData`. /// /// E.g. `ObjectData::string` -pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { +pub(crate) const ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { __get_prototype_of__: ordinary_get_prototype_of, __set_prototype_of__: ordinary_set_prototype_of, __is_extensible__: ordinary_is_extensible, diff --git a/core/engine/src/object/operations.rs b/core/engine/src/object/operations.rs index 9a989b5cc28..1419d8b8221 100644 --- a/core/engine/src/object/operations.rs +++ b/core/engine/src/object/operations.rs @@ -1,3 +1,5 @@ +use super::internal_methods::InternalMethodContext; +use crate::value::JsVariant; use crate::{ builtins::{ function::{set_function_name, BoundFunction, ClassFieldDefinition, OrdinaryFunction}, @@ -14,8 +16,6 @@ use crate::{ Context, JsResult, JsSymbol, JsValue, }; -use super::internal_methods::InternalMethodContext; - /// Object integrity level. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IntegrityLevel { @@ -756,15 +756,18 @@ impl JsObject { // 1. Assert: IsPropertyKey(P) is true. // 2. Let func be ? GetV(V, P). - match &self.__get__( - &key.into(), - self.clone().into(), - &mut InternalMethodContext::new(context), - )? { + match self + .__get__( + &key.into(), + self.clone().into(), + &mut InternalMethodContext::new(context), + )? + .variant() + { // 3. If func is either undefined or null, return undefined. - JsValue::Undefined | JsValue::Null => Ok(None), + JsVariant::Undefined | JsVariant::Null => Ok(None), // 5. Return func. - JsValue::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())), + JsVariant::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())), // 4. If IsCallable(func) is false, throw a TypeError exception. _ => Err(JsNativeError::typ() .with_message("value returned for property of object is not a function") diff --git a/core/engine/src/object/property_map.rs b/core/engine/src/object/property_map.rs index f995f66872a..d639c8092b6 100644 --- a/core/engine/src/object/property_map.rs +++ b/core/engine/src/object/property_map.rs @@ -7,6 +7,7 @@ use super::{ }, JsPrototype, ObjectStorage, PropertyDescriptor, PropertyKey, }; +use crate::value::JsVariant; use crate::{property::PropertyDescriptorBuilder, JsValue}; use boa_gc::{custom_trace, Finalize, Trace}; use indexmap::IndexMap; @@ -160,10 +161,10 @@ impl IndexedProperties { // equal to the original then it is an integer. let is_rational_integer = |n: f64| n.to_bits() == f64::from(n as i32).to_bits(); - let value = match value { - JsValue::Integer(n) => n, - JsValue::Rational(n) if is_rational_integer(n) => n as i32, - JsValue::Rational(value) => { + let value = match value.variant() { + JsVariant::Integer32(n) => n, + JsVariant::Float64(n) if is_rational_integer(n) => n as i32, + JsVariant::Float64(value) => { let mut vec = vec.iter().copied().map(f64::from).collect::>(); // If the key is pointing one past the last element, we push it! @@ -180,7 +181,7 @@ impl IndexedProperties { *self = Self::DenseF64(vec); return true; } - value => { + _ => { let mut vec = vec .iter() .copied() @@ -624,21 +625,21 @@ impl PropertyMap { // equal to the original then it is an integer. let is_rational_integer = |n: f64| n.to_bits() == f64::from(n as i32).to_bits(); - let value = match value { - JsValue::Integer(n) => *n, - JsValue::Rational(n) if is_rational_integer(*n) => *n as i32, - JsValue::Rational(value) => { + let value = match value.variant() { + JsVariant::Integer32(n) => n, + JsVariant::Float64(n) if is_rational_integer(n) => n as i32, + JsVariant::Float64(value) => { let mut properties = properties .iter() .copied() .map(f64::from) .collect::>(); - properties[index] = *value; + properties[index] = value; self.indexed_properties = IndexedProperties::DenseF64(properties); return true; } - value => { + _ => { let mut properties = properties .iter() .copied() diff --git a/core/engine/src/optimizer/pass/constant_folding.rs b/core/engine/src/optimizer/pass/constant_folding.rs index c1725ddfa38..8dd6e07d98b 100644 --- a/core/engine/src/optimizer/pass/constant_folding.rs +++ b/core/engine/src/optimizer/pass/constant_folding.rs @@ -1,3 +1,4 @@ +use crate::value::JsVariant; use crate::{ builtins::Number, bytecompiler::ToJsString, optimizer::PassAction, value::Numeric, Context, JsBigInt, JsValue, @@ -27,21 +28,21 @@ fn literal_to_js_value(literal: &Literal, context: &mut Context) -> JsValue { } } -fn js_value_to_literal(value: JsValue, context: &mut Context) -> Literal { - match value { - JsValue::Null => Literal::Null, - JsValue::Undefined => Literal::Undefined, - JsValue::Boolean(v) => Literal::Bool(v), - JsValue::String(v) => { +fn js_value_to_literal(value: &JsValue, context: &mut Context) -> Literal { + match value.variant() { + JsVariant::Null => Literal::Null, + JsVariant::Undefined => Literal::Undefined, + JsVariant::Boolean(v) => Literal::Bool(v), + JsVariant::String(v) => { // TODO: Replace JStrRef with JsStr this would eliminate the to_vec call. let v = v.to_vec(); Literal::String(context.interner_mut().get_or_intern(JStrRef::Utf16(&v))) } - JsValue::Rational(v) => Literal::Num(v), - JsValue::Integer(v) => Literal::Int(v), - JsValue::BigInt(v) => Literal::BigInt(Box::new(v.as_inner().clone())), - JsValue::Object(_) | JsValue::Symbol(_) => { - unreachable!("value must not be a object or symbol") + JsVariant::Float64(v) => Literal::Num(v), + JsVariant::Integer32(v) => Literal::Int(v), + JsVariant::BigInt(v) => Literal::BigInt(Box::new(v.as_inner().clone())), + JsVariant::Object(_) | JsVariant::Symbol(_) => { + unreachable!("value must not be an object or symbol") } } } @@ -101,7 +102,7 @@ impl ConstantFolding { return PassAction::Keep; }; - PassAction::Replace(Expression::Literal(js_value_to_literal(value, context))) + PassAction::Replace(Expression::Literal(js_value_to_literal(&value, context))) } fn constant_fold_binary_expr( @@ -228,6 +229,6 @@ impl ConstantFolding { return PassAction::Keep; }; - PassAction::Replace(Expression::Literal(js_value_to_literal(value, context))) + PassAction::Replace(Expression::Literal(js_value_to_literal(&value, context))) } } diff --git a/core/engine/src/value/conversions/convert.rs b/core/engine/src/value/conversions/convert.rs index a73344d6855..92e89b554b0 100644 --- a/core/engine/src/value/conversions/convert.rs +++ b/core/engine/src/value/conversions/convert.rs @@ -33,11 +33,11 @@ use crate::{Context, JsData, JsResult, JsString, JsValue}; /// # use boa_engine::value::{Convert, TryFromJs}; /// # let mut context = Context::default(); /// let Convert(conv0): Convert = -/// Convert::try_from_js(&JsValue::Integer(0), &mut context).unwrap(); +/// Convert::try_from_js(&JsValue::new(0), &mut context).unwrap(); /// let Convert(conv5): Convert = -/// Convert::try_from_js(&JsValue::Integer(5), &mut context).unwrap(); +/// Convert::try_from_js(&JsValue::new(5), &mut context).unwrap(); /// let Convert(conv_nan): Convert = -/// Convert::try_from_js(&JsValue::Rational(f64::NAN), &mut context).unwrap(); +/// Convert::try_from_js(&JsValue::new(f64::NAN), &mut context).unwrap(); /// /// assert_eq!(conv0, false); /// assert_eq!(conv5, true); diff --git a/core/engine/src/value/conversions/either.rs b/core/engine/src/value/conversions/either.rs index fa8eebe99fb..b64ed5018aa 100644 --- a/core/engine/src/value/conversions/either.rs +++ b/core/engine/src/value/conversions/either.rs @@ -25,7 +25,7 @@ where #[test] fn either() { - let v = JsValue::Integer(123); + let v = JsValue::new(123); let mut context = Context::default(); assert_eq!( diff --git a/core/engine/src/value/conversions/mod.rs b/core/engine/src/value/conversions/mod.rs index 3fa3841212e..daee6456064 100644 --- a/core/engine/src/value/conversions/mod.rs +++ b/core/engine/src/value/conversions/mod.rs @@ -2,7 +2,7 @@ use crate::{js_string, string::JsStr}; -use super::{JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler}; +use super::{InnerValue, JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler}; mod either; mod serde_json; @@ -15,7 +15,7 @@ impl From> for JsValue { fn from(value: JsStr<'_>) -> Self { let _timer = Profiler::global().start_event("From>", "value"); - Self::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::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::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"); - Self::Rational(value.into()) + JsValue::from(f64::from(value)) } } @@ -63,97 +63,38 @@ impl From for JsValue { fn from(value: f64) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::Rational(value) + Self::from_inner(InnerValue::Float64(value)) } } -impl From for JsValue { - #[inline] - fn from(value: u8) -> Self { - let _timer = Profiler::global().start_event("From", "value"); +macro_rules! impl_from_integer { + ( $( $type_:ty ),* ) => { + $( + impl From<$type_> for JsValue { + #[inline] + #[allow(clippy::cast_lossless)] + fn from(value: $type_) -> Self { + let _timer = Profiler::global().start_event(concat!("From<", stringify!($type_), ">"), "value"); - Self::Integer(value.into()) - } + i32::try_from(value) + .map_or_else( + |_| Self::from(value as f64), + |value| Self::from_inner(InnerValue::Integer32(value)), + ) + } + } + )* + }; } -impl From for JsValue { - #[inline] - fn from(value: i8) -> Self { - let _timer = Profiler::global().start_event("From", "value"); - - Self::Integer(value.into()) - } -} - -impl From for JsValue { - #[inline] - fn from(value: u16) -> Self { - let _timer = Profiler::global().start_event("From", "value"); - - Self::Integer(value.into()) - } -} - -impl From for JsValue { - #[inline] - fn from(value: i16) -> Self { - let _timer = Profiler::global().start_event("From", "value"); - - Self::Integer(value.into()) - } -} - -impl From for JsValue { - #[inline] - fn from(value: u32) -> Self { - let _timer = Profiler::global().start_event("From", "value"); - - i32::try_from(value).map_or_else(|_| Self::Rational(value.into()), Self::Integer) - } -} - -impl From for JsValue { - #[inline] - fn from(value: i32) -> Self { - let _timer = Profiler::global().start_event("From", "value"); - - Self::Integer(value) - } -} +impl_from_integer!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize); impl From for JsValue { #[inline] fn from(value: JsBigInt) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::BigInt(value) - } -} - -impl From for JsValue { - #[inline] - fn from(value: usize) -> Self { - let _timer = Profiler::global().start_event("From", "value"); - - i32::try_from(value).map_or(Self::Rational(value as f64), Self::Integer) - } -} - -impl From for JsValue { - #[inline] - fn from(value: u64) -> Self { - let _timer = Profiler::global().start_event("From", "value"); - - i32::try_from(value).map_or(Self::Rational(value as f64), Self::Integer) - } -} - -impl From for JsValue { - #[inline] - fn from(value: i64) -> Self { - let _timer = Profiler::global().start_event("From", "value"); - - i32::try_from(value).map_or(Self::Rational(value as f64), Self::Integer) + Self::from_inner(InnerValue::BigInt(value)) } } @@ -162,7 +103,7 @@ impl From for JsValue { fn from(value: bool) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::Boolean(value) + Self::from_inner(InnerValue::Boolean(value)) } } @@ -171,7 +112,7 @@ impl From for JsValue { fn from(object: JsObject) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::Object(object) + Self::from_inner(InnerValue::Object(object)) } } @@ -200,6 +141,9 @@ where { #[inline] fn into_or_undefined(self) -> JsValue { - self.map_or_else(JsValue::undefined, Into::into) + match self { + Some(value) => value.into(), + None => JsValue::undefined(), + } } } diff --git a/core/engine/src/value/conversions/serde_json.rs b/core/engine/src/value/conversions/serde_json.rs index 95bbd5e884f..cad9e9933cf 100644 --- a/core/engine/src/value/conversions/serde_json.rs +++ b/core/engine/src/value/conversions/serde_json.rs @@ -1,6 +1,6 @@ //! This module implements the conversions from and into [`serde_json::Value`]. -use super::JsValue; +use super::{InnerValue, JsValue}; use crate::{ builtins::Array, error::JsNativeError, @@ -44,13 +44,13 @@ impl JsValue { const MIN_INT: i64 = i32::MIN as i64; match json { - Value::Null => Ok(Self::Null), - Value::Bool(b) => Ok(Self::Boolean(*b)), + Value::Null => Ok(Self::null()), + Value::Bool(b) => Ok(Self::new(*b)), Value::Number(num) => num .as_i64() .filter(|n| (MIN_INT..=MAX_INT).contains(n)) - .map(|i| Self::Integer(i as i32)) - .or_else(|| num.as_f64().map(Self::Rational)) + .map(|i| Self::new(i as i32)) + .or_else(|| num.as_f64().map(Self::new)) .ok_or_else(|| { JsNativeError::typ() .with_message(format!("could not convert JSON number {num} to JsValue")) @@ -113,17 +113,17 @@ impl JsValue { /// /// Panics if the `JsValue` is `Undefined`. pub fn to_json(&self, context: &mut Context) -> JsResult { - match self { - Self::Null => Ok(Value::Null), - Self::Undefined => todo!("undefined to JSON"), - &Self::Boolean(b) => Ok(b.into()), - Self::String(string) => Ok(string.to_std_string_escaped().into()), - &Self::Rational(rat) => Ok(rat.into()), - &Self::Integer(int) => Ok(int.into()), - Self::BigInt(_bigint) => Err(JsNativeError::typ() + 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() .with_message("cannot convert bigint to JSON") .into()), - Self::Object(obj) => { + InnerValue::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)) } } - Self::Symbol(_sym) => Err(JsNativeError::typ() + InnerValue::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 42068c6c9ab..63844269612 100644 --- a/core/engine/src/value/conversions/try_from_js.rs +++ b/core/engine/src/value/conversions/try_from_js.rs @@ -3,6 +3,7 @@ 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; @@ -27,11 +28,12 @@ impl JsValue { impl TryFromJs for bool { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Boolean(b) => Ok(*b), - _ => Err(JsNativeError::typ() + if let Some(b) = value.as_boolean() { + Ok(b) + } else { + Err(JsNativeError::typ() .with_message("cannot convert value to a boolean") - .into()), + .into()) } } } @@ -44,23 +46,24 @@ impl TryFromJs for () { impl TryFromJs for String { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::String(s) => s.to_std_string().map_err(|e| { + if let Some(s) = value.as_string() { + s.to_std_string().map_err(|e| { JsNativeError::typ() .with_message(format!("could not convert JsString to Rust string: {e}")) .into() - }), - _ => Err(JsNativeError::typ() + }) + } else { + Err(JsNativeError::typ() .with_message("cannot convert value to a String") - .into()), + .into()) } } } impl TryFromJs for JsString { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::String(s) => Ok(s.clone()), + match &value.inner { + InnerValue::String(s) => Ok(s.clone()), _ => Err(JsNativeError::typ() .with_message("cannot convert value to a String") .into()), @@ -73,9 +76,11 @@ where T: TryFromJs, { fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { - match value { - JsValue::Null | JsValue::Undefined => Ok(None), - value => Ok(Some(T::try_from_js(value, context)?)), + // TODO: remove NULL -> None conversion. + if value.is_null_or_undefined() { + Ok(None) + } else { + Ok(Some(T::try_from_js(value, context)?)) } } } @@ -85,7 +90,7 @@ where T: TryFromJs, { fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { - let JsValue::Object(object) = value else { + let InnerValue::Object(object) = &value.inner else { return Err(JsNativeError::typ() .with_message("cannot convert value to a Vec") .into()); @@ -114,8 +119,8 @@ where impl TryFromJs for JsObject { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Object(o) => Ok(o.clone()), + match &value.inner { + InnerValue::Object(o) => Ok(o.clone()), _ => Err(JsNativeError::typ() .with_message("cannot convert value to a Object") .into()), @@ -125,8 +130,8 @@ impl TryFromJs for JsObject { impl TryFromJs for JsBigInt { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::BigInt(b) => Ok(b.clone()), + match &value.inner { + InnerValue::BigInt(b) => Ok(b.clone()), _ => Err(JsNativeError::typ() .with_message("cannot convert value to a BigInt") .into()), @@ -136,8 +141,8 @@ impl TryFromJs for JsBigInt { impl TryFromJs for BigInt { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::BigInt(b) => Ok(b.as_inner().clone()), + match &value.inner { + InnerValue::BigInt(b) => Ok(b.as_inner().clone()), _ => Err(JsNativeError::typ() .with_message("cannot convert value to a BigInt") .into()), @@ -153,9 +158,9 @@ impl TryFromJs for JsValue { impl TryFromJs for f64 { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Integer(i) => Ok((*i).into()), - JsValue::Rational(r) => Ok(*r), + match &value.inner { + InnerValue::Integer32(i) => Ok((*i).into()), + InnerValue::Float64(r) => Ok(*r), _ => Err(JsNativeError::typ() .with_message("cannot convert value to a f64") .into()), @@ -174,213 +179,36 @@ where None } -impl TryFromJs for i8 { - fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Integer(i) => (*i).try_into().map_err(|e| { - JsNativeError::typ() - .with_message(format!("cannot convert value to a i8: {e}")) - .into() - }), - JsValue::Rational(f) => from_f64(*f).ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot convert value to a i8") - .into() - }), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a i8") - .into()), - } - } -} - -impl TryFromJs for u8 { - fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Integer(i) => (*i).try_into().map_err(|e| { - JsNativeError::typ() - .with_message(format!("cannot convert value to a u8: {e}")) - .into() - }), - JsValue::Rational(f) => from_f64(*f).ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot convert value to a u8") - .into() - }), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a u8") - .into()), - } - } -} - -impl TryFromJs for i16 { - fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Integer(i) => (*i).try_into().map_err(|e| { - JsNativeError::typ() - .with_message(format!("cannot convert value to a i16: {e}")) - .into() - }), - JsValue::Rational(f) => from_f64(*f).ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot convert value to a i16") - .into() - }), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a i16") - .into()), - } - } -} - -impl TryFromJs for u16 { - fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Integer(i) => (*i).try_into().map_err(|e| { - JsNativeError::typ() - .with_message(format!("cannot convert value to a iu16: {e}")) - .into() - }), - JsValue::Rational(f) => from_f64(*f).ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot convert value to a u16") - .into() - }), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a u16") - .into()), - } - } -} - -impl TryFromJs for i32 { - fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Integer(i) => Ok(*i), - JsValue::Rational(f) => from_f64(*f).ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot convert value to a i32") - .into() - }), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a i32") - .into()), - } - } -} - -impl TryFromJs for u32 { - fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Integer(i) => (*i).try_into().map_err(|e| { - JsNativeError::typ() - .with_message(format!("cannot convert value to a u32: {e}")) - .into() - }), - JsValue::Rational(f) => from_f64(*f).ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot convert value to a u32") - .into() - }), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a u32") - .into()), - } - } -} - -impl TryFromJs for i64 { - fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Integer(i) => Ok((*i).into()), - JsValue::Rational(f) => from_f64(*f).ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot convert value to a i64") - .into() - }), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a i64") - .into()), - } - } -} - -impl TryFromJs for u64 { - fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Integer(i) => (*i).try_into().map_err(|e| { - JsNativeError::typ() - .with_message(format!("cannot convert value to a u64: {e}")) - .into() - }), - JsValue::Rational(f) => from_f64(*f).ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot convert value to a u64") - .into() - }), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a u64") - .into()), - } - } -} - -impl TryFromJs for usize { - fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Integer(i) => (*i).try_into().map_err(|e| { - JsNativeError::typ() - .with_message(format!("cannot convert value to a usize: {e}")) - .into() - }), - JsValue::Rational(f) => from_f64(*f).ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot convert value to a usize") - .into() - }), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a usize") - .into()), - } - } -} - -impl TryFromJs for i128 { - fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Integer(i) => Ok((*i).into()), - JsValue::Rational(f) => from_f64(*f).ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot convert value to a i128") - .into() - }), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a i128") - .into()), - } +macro_rules! impl_try_from_js_integer { + ( $( $type: ty ),* ) => { + $( + 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| { + JsNativeError::typ() + .with_message(format!( + concat!("cannot convert value to a ", stringify!($type), ": {}"), + e) + ) + .into() + }), + InnerValue::Float64(f) => from_f64(*f).ok_or_else(|| { + JsNativeError::typ() + .with_message(concat!("cannot convert value to a ", stringify!($type))) + .into() + }), + _ => Err(JsNativeError::typ() + .with_message(concat!("cannot convert value to a ", stringify!($type))) + .into()), + } + } + } + )* } } -impl TryFromJs for u128 { - fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Integer(i) => (*i).try_into().map_err(|e| { - JsNativeError::typ() - .with_message(format!("cannot convert value to a u128: {e}")) - .into() - }), - JsValue::Rational(f) => from_f64(*f).ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot convert value to a u128") - .into() - }), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a u128") - .into()), - } - } -} +impl_try_from_js_integer!(i8, u8, i16, u16, i32, u32, i64, u64, usize, i128, u128); #[test] fn integer_floating_js_value_to_integer() { diff --git a/core/engine/src/value/conversions/try_from_js/collections.rs b/core/engine/src/value/conversions/try_from_js/collections.rs index 4934de30352..825b72514c5 100644 --- a/core/engine/src/value/conversions/try_from_js/collections.rs +++ b/core/engine/src/value/conversions/try_from_js/collections.rs @@ -13,7 +13,7 @@ where V: TryFromJs, { fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { - let JsValue::Object(object) = value else { + let Some(object) = value.as_object() else { return Err(JsNativeError::typ() .with_message("cannot convert value to a BTreeMap") .into()); @@ -56,7 +56,7 @@ where S: std::hash::BuildHasher + Default, { fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { - let JsValue::Object(object) = value else { + let Some(object) = value.as_object() else { return Err(JsNativeError::typ() .with_message("cannot convert value to a BTreeMap") .into()); diff --git a/core/engine/src/value/conversions/try_into_js.rs b/core/engine/src/value/conversions/try_into_js.rs index a4b31ea1564..3e12d8be58e 100644 --- a/core/engine/src/value/conversions/try_into_js.rs +++ b/core/engine/src/value/conversions/try_into_js.rs @@ -8,18 +8,18 @@ pub trait TryIntoJs: Sized { impl TryIntoJs for bool { fn try_into_js(&self, _context: &mut Context) -> JsResult { - Ok(JsValue::Boolean(*self)) + Ok(JsValue::from(*self)) } } impl TryIntoJs for &str { fn try_into_js(&self, _context: &mut Context) -> JsResult { - Ok(JsValue::String(JsString::from(*self))) + Ok(JsValue::from(JsString::from(*self))) } } impl TryIntoJs for String { fn try_into_js(&self, _context: &mut Context) -> JsResult { - Ok(JsValue::String(JsString::from(self.as_str()))) + Ok(JsValue::from(JsString::from(self.as_str()))) } } @@ -72,7 +72,7 @@ fn err_outside_safe_range() -> crate::JsError { .into() } fn convert_safe_i64(value: i64) -> JsValue { - i32::try_from(value).map_or(JsValue::Rational(value as f64), JsValue::Integer) + i32::try_from(value).map_or(JsValue::from(value as f64), JsValue::new) } impl TryIntoJs for i64 { @@ -124,7 +124,7 @@ where fn try_into_js(&self, context: &mut Context) -> JsResult { match self { Some(x) => x.try_into_js(context), - None => Ok(JsValue::Undefined), + None => Ok(JsValue::undefined()), } } } @@ -170,7 +170,7 @@ impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: impl TryIntoJs for () { fn try_into_js(&self, _context: &mut Context) -> JsResult { - Ok(JsValue::Null) + Ok(JsValue::null()) } } diff --git a/core/engine/src/value/display.rs b/core/engine/src/value/display.rs index 9320bb5dd67..82d48a4d53c 100644 --- a/core/engine/src/value/display.rs +++ b/core/engine/src/value/display.rs @@ -1,4 +1,4 @@ -use super::{fmt, Display, HashSet, JsValue}; +use super::{fmt, Display, HashSet, JsValue, JsVariant}; use crate::{ builtins::{ error::ErrorObject, map::ordered_map::OrderedMap, promise::PromiseState, @@ -63,7 +63,7 @@ macro_rules! print_obj_value { vec![format!( "{:>width$}: {}", "__proto__", - JsValue::Null.display(), + JsValue::null().display(), width = $indent, )] } @@ -98,9 +98,9 @@ macro_rules! print_obj_value { } pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children: bool) -> String { - match x { + match x.variant() { // We don't want to print private (compiler) or prototype properties - JsValue::Object(ref v) => { + JsVariant::Object(v) => { // Can use the private "type" field of an Object to match on // which type of Object it represents for special printing let v_bor = v.borrow(); @@ -263,7 +263,7 @@ impl JsValue { indent: usize, print_internals: bool, ) -> String { - if let JsValue::Object(ref v) = *data { + if let Some(v) = data.as_object() { // The in-memory address of the current object let addr = address_of(v.as_ref()); @@ -307,20 +307,20 @@ impl JsValue { impl Display for ValueDisplay<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.value { - JsValue::Null => write!(f, "null"), - JsValue::Undefined => write!(f, "undefined"), - JsValue::Boolean(v) => write!(f, "{v}"), - JsValue::Symbol(ref symbol) => { + match self.value.variant() { + JsVariant::Null => write!(f, "null"), + JsVariant::Undefined => write!(f, "undefined"), + JsVariant::Boolean(v) => write!(f, "{v}"), + JsVariant::Symbol(symbol) => { write!(f, "{}", symbol.descriptive_string().to_std_string_escaped()) } - JsValue::String(ref v) => write!(f, "\"{}\"", v.to_std_string_escaped()), - JsValue::Rational(v) => format_rational(*v, f), - JsValue::Object(_) => { + JsVariant::String(v) => write!(f, "\"{}\"", v.to_std_string_escaped()), + JsVariant::Float64(v) => format_rational(v, f), + JsVariant::Object(_) => { write!(f, "{}", log_string_from(self.value, self.internals, true)) } - JsValue::Integer(v) => write!(f, "{v}"), - JsValue::BigInt(ref num) => write!(f, "{num}n"), + JsVariant::Integer32(v) => write!(f, "{v}"), + JsVariant::BigInt(num) => write!(f, "{num}n"), } } } diff --git a/core/engine/src/value/equality.rs b/core/engine/src/value/equality.rs index d14a95d1af2..58f664ac8bc 100644 --- a/core/engine/src/value/equality.rs +++ b/core/engine/src/value/equality.rs @@ -1,4 +1,4 @@ -use super::{JsBigInt, JsObject, JsResult, JsValue, PreferredType}; +use super::{InnerValue, JsBigInt, JsObject, JsResult, JsValue, PreferredType}; use crate::{builtins::Number, Context}; impl JsValue { @@ -13,20 +13,20 @@ impl JsValue { return false; } - match (self, other) { + match (&self.inner, &other.inner) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::equal(x, y). - (Self::BigInt(x), Self::BigInt(y)) => JsBigInt::equal(x, y), - (Self::Rational(x), Self::Rational(y)) => Number::equal(*x, *y), - (Self::Rational(x), Self::Integer(y)) => Number::equal(*x, f64::from(*y)), - (Self::Integer(x), Self::Rational(y)) => Number::equal(f64::from(*x), *y), - (Self::Integer(x), Self::Integer(y)) => x == y, + (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, //Null has to be handled specially because "typeof null" returns object and if we managed //this without a special case we would compare self and other as if they were actually //objects which unfortunately fails //Specification Link: https://tc39.es/ecma262/#sec-typeof-operator - (Self::Null, Self::Null) => true, + (InnerValue::Null, InnerValue::Null) => true, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(self, other), @@ -45,17 +45,21 @@ impl JsValue { return Ok(self.strict_equals(other)); } - Ok(match (self, other) { + Ok(match (&self.inner, &other.inner) { // 2. If x is null and y is undefined, return true. // 3. If x is undefined and y is null, return true. - (Self::Null, Self::Undefined) | (Self::Undefined, Self::Null) => true, + (InnerValue::Null, InnerValue::Undefined) + | (InnerValue::Undefined, InnerValue::Null) => true, // 3. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y). // 4. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y. // // https://github.com/rust-lang/rust/issues/54883 - (Self::Integer(_) | Self::Rational(_), Self::String(_) | Self::Boolean(_)) - | (Self::String(_), Self::Integer(_) | Self::Rational(_)) => { + ( + InnerValue::Integer32(_) | InnerValue::Float64(_), + InnerValue::String(_) | InnerValue::Boolean(_), + ) + | (InnerValue::String(_), InnerValue::Integer32(_) | InnerValue::Float64(_)) => { let x = self.to_number(context)?; let y = other.to_number(context)?; Number::equal(x, y) @@ -65,30 +69,34 @@ impl JsValue { // a. Let n be ! StringToBigInt(y). // b. If n is NaN, return false. // c. Return the result of the comparison x == n. - (Self::BigInt(ref a), Self::String(ref b)) => JsBigInt::from_js_string(b) + (InnerValue::BigInt(ref a), InnerValue::String(ref 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. - (Self::String(ref a), Self::BigInt(ref b)) => JsBigInt::from_js_string(a) + (InnerValue::String(ref a), InnerValue::BigInt(ref 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. - (Self::Boolean(x), _) => return other.equals(&Self::new(i32::from(*x)), context), + (InnerValue::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). - (_, Self::Boolean(y)) => return self.equals(&Self::new(i32::from(*y)), context), + (_, InnerValue::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). ( - Self::Object(_), - Self::String(_) - | Self::Rational(_) - | Self::Integer(_) - | Self::BigInt(_) - | Self::Symbol(_), + InnerValue::Object(_), + InnerValue::String(_) + | InnerValue::Float64(_) + | InnerValue::Integer32(_) + | InnerValue::BigInt(_) + | InnerValue::Symbol(_), ) => { let primitive = self.to_primitive(context, PreferredType::Default)?; return Ok(primitive @@ -99,12 +107,12 @@ impl JsValue { // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result // of the comparison ? ToPrimitive(x) == y. ( - Self::String(_) - | Self::Rational(_) - | Self::Integer(_) - | Self::BigInt(_) - | Self::Symbol(_), - Self::Object(_), + InnerValue::String(_) + | InnerValue::Float64(_) + | InnerValue::Integer32(_) + | InnerValue::BigInt(_) + | InnerValue::Symbol(_), + InnerValue::Object(_), ) => { let primitive = other.to_primitive(context, PreferredType::Default)?; return Ok(primitive @@ -115,10 +123,10 @@ impl JsValue { // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then // a. If x or y are any of NaN, +∞, or -∞, return false. // b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false. - (Self::BigInt(ref a), Self::Rational(ref b)) => a == b, - (Self::Rational(ref a), Self::BigInt(ref b)) => a == b, - (Self::BigInt(ref a), Self::Integer(ref b)) => a == b, - (Self::Integer(ref a), Self::BigInt(ref b)) => a == b, + (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, // 13. Return false. _ => false, @@ -139,14 +147,18 @@ impl JsValue { return false; } - match (x, y) { + match (&x.inner, &y.inner) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::SameValue(x, y). - (Self::BigInt(x), Self::BigInt(y)) => JsBigInt::same_value(x, y), - (Self::Rational(x), Self::Rational(y)) => Number::same_value(*x, *y), - (Self::Rational(x), Self::Integer(y)) => Number::same_value(*x, f64::from(*y)), - (Self::Integer(x), Self::Rational(y)) => Number::same_value(f64::from(*x), *y), - (Self::Integer(x), Self::Integer(y)) => x == y, + (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, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(x, y), @@ -168,15 +180,19 @@ impl JsValue { return false; } - match (x, y) { + match (&x.inner, &y.inner) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::SameValueZero(x, y). - (Self::BigInt(x), Self::BigInt(y)) => JsBigInt::same_value_zero(x, y), + (InnerValue::BigInt(x), InnerValue::BigInt(y)) => JsBigInt::same_value_zero(x, y), - (Self::Rational(x), Self::Rational(y)) => Number::same_value_zero(*x, *y), - (Self::Rational(x), Self::Integer(y)) => Number::same_value_zero(*x, f64::from(*y)), - (Self::Integer(x), Self::Rational(y)) => Number::same_value_zero(f64::from(*x), *y), - (Self::Integer(x), Self::Integer(y)) => 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)) + } + (InnerValue::Integer32(x), InnerValue::Float64(y)) => { + Number::same_value_zero(f64::from(*x), *y) + } + (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x == y, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(x, y), @@ -185,12 +201,13 @@ impl JsValue { fn same_value_non_numeric(x: &Self, y: &Self) -> bool { debug_assert!(x.get_type() == y.get_type()); - match (x, y) { - (Self::Null, Self::Null) | (Self::Undefined, Self::Undefined) => true, - (Self::String(ref x), Self::String(ref y)) => x == y, - (Self::Boolean(x), Self::Boolean(y)) => x == y, - (Self::Object(ref x), Self::Object(ref y)) => JsObject::equals(x, y), - (Self::Symbol(ref x), Self::Symbol(ref y)) => x == y, + match (&x.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, _ => false, } } diff --git a/core/engine/src/value/hash.rs b/core/engine/src/value/hash.rs index c657037b1cd..3becb2fcdab 100644 --- a/core/engine/src/value/hash.rs +++ b/core/engine/src/value/hash.rs @@ -1,4 +1,4 @@ -use super::JsValue; +use super::{InnerValue, JsValue}; use crate::builtins::Number; use std::hash::{Hash, Hasher}; @@ -36,16 +36,16 @@ impl Hash for RationalHashable { impl Hash for JsValue { fn hash(&self, state: &mut H) { - match self { - Self::Undefined => UndefinedHashable.hash(state), - Self::Null => NullHashable.hash(state), - Self::String(ref string) => string.hash(state), - Self::Boolean(boolean) => boolean.hash(state), - Self::Integer(integer) => RationalHashable(f64::from(*integer)).hash(state), - Self::BigInt(ref bigint) => bigint.hash(state), - Self::Rational(rational) => RationalHashable(*rational).hash(state), - Self::Symbol(ref symbol) => Hash::hash(symbol, state), - Self::Object(ref object) => object.hash(state), + 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), } } } diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index c767c1b8463..0d55e3fc781 100644 --- a/core/engine/src/value/mod.rs +++ b/core/engine/src/value/mod.rs @@ -26,6 +26,7 @@ pub(crate) use self::conversions::IntoOrUndefined; pub use self::{ conversions::try_from_js::TryFromJs, conversions::try_into_js::TryIntoJs, display::ValueDisplay, integer::IntegerOrInfinity, operations::*, r#type::Type, + variant::JsVariant, }; use crate::builtins::RegExp; use crate::object::{JsFunction, JsPromise, JsRegExp}; @@ -49,6 +50,7 @@ mod hash; mod integer; mod operations; mod r#type; +mod variant; #[cfg(test)] mod tests; @@ -63,30 +65,25 @@ static TWO_E_63: Lazy = Lazy::new(|| { BigInt::from(TWO_E_63) }); -/// A Javascript value -#[derive(Finalize, Debug, Clone)] -pub enum JsValue { - /// `null` - A null value, for when a value doesn't exist. +/// 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` - An undefined value, for when a field or index doesn't exist. Undefined, - /// `boolean` - A `true` / `false` value, for if a certain criteria is met. Boolean(bool), - /// `String` - A UTF-16 string, such as `"Hello, world"`. - String(JsString), - /// `Number` - A 64-bit floating point number, such as `3.1415` - Rational(f64), - /// `Number` - A 32-bit integer, such as `42`. - Integer(i32), - /// `BigInt` - holds any arbitrary large signed integer. + Float64(f64), + Integer32(i32), BigInt(JsBigInt), - /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values. - Object(JsObject), - /// `Symbol` - A Symbol Primitive type. + String(JsString), Symbol(JsSymbol), + Object(JsObject), } -unsafe impl Trace for JsValue { +#[allow(unsafe_op_in_unsafe_fn)] +unsafe impl Trace for InnerValue { custom_trace! {this, mark, { if let Self::Object(o) = this { mark(o); @@ -94,7 +91,28 @@ unsafe impl Trace for JsValue { }} } +/// 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. +/// This allows for internal changes to the value without affecting the public API. +/// +/// ``` +/// # use boa_engine::{js_string, Context, JsValue}; +/// let mut context = Context::default(); +/// let value = JsValue::new(3); +/// assert_eq!(value.to_string(&mut context), Ok(js_string!("3"))); +/// ``` +#[derive(Finalize, Debug, Clone, Trace)] +pub struct JsValue { + inner: InnerValue, +} + impl JsValue { + /// Create a new [`JsValue`] from an inner value. + const fn from_inner(inner: InnerValue) -> Self { + Self { inner } + } + /// Create a new [`JsValue`]. pub fn new(value: T) -> Self where @@ -103,55 +121,81 @@ impl JsValue { value.into() } + /// Return the variant of this value. + #[inline] + #[must_use] + pub fn variant(&self) -> JsVariant<'_> { + (&self.inner).into() + } + /// Creates a new `undefined` value. #[inline] #[must_use] pub const fn undefined() -> Self { - Self::Undefined + Self::from_inner(InnerValue::Undefined) } /// Creates a new `null` value. #[inline] #[must_use] pub const fn null() -> Self { - Self::Null + Self::from_inner(InnerValue::Null) } /// Creates a new number with `NaN` value. #[inline] #[must_use] pub const fn nan() -> Self { - Self::Rational(f64::NAN) + Self::from_inner(InnerValue::Float64(f64::NAN)) } /// Creates a new number with `Infinity` value. #[inline] #[must_use] pub const fn positive_infinity() -> Self { - Self::Rational(f64::INFINITY) + Self::from_inner(InnerValue::Float64(f64::INFINITY)) } /// Creates a new number with `-Infinity` value. #[inline] #[must_use] pub const fn negative_infinity() -> Self { - Self::Rational(f64::NEG_INFINITY) + Self::from_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)) } /// Returns true if the value is an object. #[inline] #[must_use] pub const fn is_object(&self) -> bool { - matches!(self, Self::Object(_)) + matches!(self.inner, InnerValue::Object(_)) } /// Returns the object if the value is object, otherwise `None`. #[inline] #[must_use] pub const fn as_object(&self) -> Option<&JsObject> { - match *self { - Self::Object(ref o) => Some(o), - _ => None, + if let InnerValue::Object(obj) = &self.inner { + Some(obj) + } else { + None + } + } + + /// 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 } } @@ -164,14 +208,22 @@ impl JsValue { #[inline] #[must_use] pub fn is_callable(&self) -> bool { - matches!(self, Self::Object(obj) if obj.is_callable()) + if let InnerValue::Object(obj) = &self.inner { + obj.is_callable() + } else { + false + } } /// Returns the callable value if the value is callable, otherwise `None`. #[inline] #[must_use] pub fn as_callable(&self) -> Option<&JsObject> { - self.as_object().filter(|obj| obj.is_callable()) + if let InnerValue::Object(obj) = &self.inner { + obj.is_callable().then_some(obj) + } else { + None + } } /// Returns a [`JsFunction`] if the value is callable, otherwise `None`. @@ -188,7 +240,7 @@ impl JsValue { #[inline] #[must_use] pub fn is_constructor(&self) -> bool { - matches!(self, Self::Object(obj) if obj.is_constructor()) + matches!(&self.inner, InnerValue::Object(obj) if obj.is_constructor()) } /// Returns the constructor if the value is a constructor, otherwise `None`. @@ -202,14 +254,22 @@ impl JsValue { #[inline] #[must_use] pub fn is_promise(&self) -> bool { - matches!(self, Self::Object(obj) if obj.is::()) + if let InnerValue::Object(obj) = &self.inner { + obj.is::() + } else { + false + } } /// 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> { - self.as_object().filter(|obj| obj.is::()) + if let InnerValue::Object(obj) = &self.inner { + obj.is::().then_some(obj) + } else { + None + } } /// Returns the value as a promise if the value is a promise, otherwise `None`. @@ -225,7 +285,11 @@ impl JsValue { #[inline] #[must_use] pub fn is_regexp(&self) -> bool { - matches!(self, Self::Object(obj) if obj.is::()) + if let InnerValue::Object(obj) = &self.inner { + obj.is::() + } else { + false + } } /// Returns the value as a regular expression if the value is a regexp, otherwise `None`. @@ -242,16 +306,17 @@ impl JsValue { #[inline] #[must_use] pub const fn is_symbol(&self) -> bool { - matches!(self, Self::Symbol(_)) + matches!(self.inner, InnerValue::Symbol(_)) } /// Returns the symbol if the value is a symbol, otherwise `None`. #[inline] #[must_use] pub fn as_symbol(&self) -> Option { - match self { - Self::Symbol(symbol) => Some(symbol.clone()), - _ => None, + if let InnerValue::Symbol(symbol) = &self.inner { + Some(symbol.clone()) + } else { + None } } @@ -259,64 +324,41 @@ impl JsValue { #[inline] #[must_use] pub const fn is_undefined(&self) -> bool { - matches!(self, Self::Undefined) + matches!(&self.inner, InnerValue::Undefined) } /// Returns true if the value is null. #[inline] #[must_use] pub const fn is_null(&self) -> bool { - matches!(self, Self::Null) + matches!(self.inner, InnerValue::Null) } /// Returns true if the value is null or undefined. #[inline] #[must_use] pub const fn is_null_or_undefined(&self) -> bool { - matches!(self, Self::Null | Self::Undefined) - } - - /// Returns true if the value is a 64-bit floating-point number. - #[inline] - #[must_use] - pub const fn is_double(&self) -> bool { - matches!(self, Self::Rational(_)) + self.is_undefined() || self.is_null() } - /// Determines if argument is a finite integral Number value. + /// Returns the number if the value is a finite integral Number value, otherwise `None`. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-isintegralnumber + #[inline] #[must_use] #[allow(clippy::float_cmp)] - pub fn is_integral_number(&self) -> bool { - // If it can fit in a i32 and the truncated version is - // equal to the original then it is an integer. - let is_rational_integer = |n: f64| n == f64::from(n as i32); - - match *self { - Self::Integer(_) => true, - Self::Rational(n) if is_rational_integer(n) => true, - _ => false, - } - } - - /// Returns true if the value can be reprented as an integer. - /// - /// Similar to [`JsValue::is_integral_number()`] except that it returns `false` for `-0`. - #[must_use] - #[allow(clippy::float_cmp)] - pub fn is_integer(&self) -> bool { - // If it can fit in a i32 and the truncated version is - // equal to the original then it is an integer. - let is_rational_integer = |n: f64| n.to_bits() == f64::from(n as i32).to_bits(); - - match *self { - Self::Integer(_) => true, - Self::Rational(n) if is_rational_integer(n) => true, - _ => false, + 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) => { + Some(rational as i32) + } + _ => None, } } @@ -324,16 +366,19 @@ impl JsValue { #[inline] #[must_use] pub const fn is_number(&self) -> bool { - matches!(self, Self::Rational(_) | Self::Integer(_)) + matches!( + self.inner, + InnerValue::Float64(_) | InnerValue::Integer32(_) + ) } /// Returns the number if the value is a number, otherwise `None`. #[inline] #[must_use] pub fn as_number(&self) -> Option { - match *self { - Self::Integer(integer) => Some(integer.into()), - Self::Rational(rational) => Some(rational), + match self.inner { + InnerValue::Integer32(integer) => Some(integer.into()), + InnerValue::Float64(rational) => Some(rational), _ => None, } } @@ -342,16 +387,17 @@ impl JsValue { #[inline] #[must_use] pub const fn is_string(&self) -> bool { - matches!(self, Self::String(_)) + matches!(self.inner, InnerValue::String(_)) } /// Returns the string if the value is a string, otherwise `None`. #[inline] #[must_use] pub const fn as_string(&self) -> Option<&JsString> { - match self { - Self::String(ref string) => Some(string), - _ => None, + if let InnerValue::String(string) = &self.inner { + Some(string) + } else { + None } } @@ -359,15 +405,15 @@ impl JsValue { #[inline] #[must_use] pub const fn is_boolean(&self) -> bool { - matches!(self, Self::Boolean(_)) + matches!(self.inner, InnerValue::Boolean(_)) } /// Returns the boolean if the value is a boolean, otherwise `None`. #[inline] #[must_use] pub const fn as_boolean(&self) -> Option { - match self { - Self::Boolean(boolean) => Some(*boolean), + match &self.inner { + InnerValue::Boolean(boolean) => Some(*boolean), _ => None, } } @@ -376,15 +422,15 @@ impl JsValue { #[inline] #[must_use] pub const fn is_bigint(&self) -> bool { - matches!(self, Self::BigInt(_)) + matches!(self.inner, InnerValue::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 { - Self::BigInt(bigint) => Some(bigint), + match &self.inner { + InnerValue::BigInt(bigint) => Some(bigint), _ => None, } } @@ -397,13 +443,13 @@ impl JsValue { /// [spec]: https://tc39.es/ecma262/#sec-toboolean #[must_use] pub fn to_boolean(&self) -> bool { - match *self { - Self::Symbol(_) | Self::Object(_) => true, - Self::String(ref s) if !s.is_empty() => true, - Self::Rational(n) if n != 0.0 && !n.is_nan() => true, - Self::Integer(n) if n != 0 => true, - Self::BigInt(ref n) if !n.is_zero() => true, - Self::Boolean(v) => v, + match self.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, _ => false, } } @@ -471,14 +517,14 @@ impl JsValue { /// /// [spec]: https://tc39.es/ecma262/#sec-tobigint pub fn to_bigint(&self, context: &mut Context) -> JsResult { - match self { - Self::Null => Err(JsNativeError::typ() + match &self.inner { + InnerValue::Null => Err(JsNativeError::typ() .with_message("cannot convert null to a BigInt") .into()), - Self::Undefined => Err(JsNativeError::typ() + InnerValue::Undefined => Err(JsNativeError::typ() .with_message("cannot convert undefined to a BigInt") .into()), - Self::String(ref string) => JsBigInt::from_js_string(string).map_or_else( + InnerValue::String(ref string) => JsBigInt::from_js_string(string).map_or_else( || { Err(JsNativeError::syntax() .with_message(format!( @@ -489,17 +535,17 @@ impl JsValue { }, Ok, ), - Self::Boolean(true) => Ok(JsBigInt::one()), - Self::Boolean(false) => Ok(JsBigInt::zero()), - Self::Integer(_) | Self::Rational(_) => Err(JsNativeError::typ() + InnerValue::Boolean(true) => Ok(JsBigInt::one()), + InnerValue::Boolean(false) => Ok(JsBigInt::zero()), + InnerValue::Integer32(_) | InnerValue::Float64(_) => Err(JsNativeError::typ() .with_message("cannot convert Number to a BigInt") .into()), - Self::BigInt(b) => Ok(b.clone()), - Self::Object(_) => { + InnerValue::BigInt(b) => Ok(b.clone()), + InnerValue::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_bigint(context) } - Self::Symbol(_) => Err(JsNativeError::typ() + InnerValue::Symbol(_) => Err(JsNativeError::typ() .with_message("cannot convert Symbol to a BigInt") .into()), } @@ -507,7 +553,7 @@ impl JsValue { /// Returns an object that implements `Display`. /// - /// By default the internals are not shown, but they can be toggled + /// By default, the internals are not shown, but they can be toggled /// with [`ValueDisplay::internals`] method. /// /// # Examples @@ -532,22 +578,19 @@ impl JsValue { /// /// This function is equivalent to `String(value)` in JavaScript. pub fn to_string(&self, context: &mut Context) -> JsResult { - match self { - Self::Null => Ok(js_string!("null")), - Self::Undefined => Ok(js_string!("undefined")), - Self::Boolean(boolean) => Ok(if *boolean { - js_string!("true") - } else { - js_string!("false") - }), - Self::Rational(rational) => Ok(JsString::from(*rational)), - Self::Integer(integer) => Ok(JsString::from(*integer)), - Self::String(string) => Ok(string.clone()), - Self::Symbol(_) => Err(JsNativeError::typ() + 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() .with_message("can't convert symbol to string") .into()), - Self::BigInt(ref bigint) => Ok(bigint.to_string().into()), - Self::Object(_) => { + InnerValue::BigInt(ref bigint) => Ok(bigint.to_string().into()), + InnerValue::Object(_) => { let primitive = self.to_primitive(context, PreferredType::String)?; primitive.to_string(context) } @@ -560,41 +603,41 @@ impl JsValue { /// /// See: pub fn to_object(&self, context: &mut Context) -> JsResult { - match self { - Self::Undefined | Self::Null => Err(JsNativeError::typ() + match &self.inner { + InnerValue::Undefined | InnerValue::Null => Err(JsNativeError::typ() .with_message("cannot convert 'null' or 'undefined' to object") .into()), - Self::Boolean(boolean) => Ok(context + InnerValue::Boolean(boolean) => Ok(context .intrinsics() .templates() .boolean() .create(*boolean, Vec::default())), - Self::Integer(integer) => Ok(context + InnerValue::Integer32(integer) => Ok(context .intrinsics() .templates() .number() .create(f64::from(*integer), Vec::default())), - Self::Rational(rational) => Ok(context + InnerValue::Float64(rational) => Ok(context .intrinsics() .templates() .number() .create(*rational, Vec::default())), - Self::String(ref string) => Ok(context + InnerValue::String(ref string) => Ok(context .intrinsics() .templates() .string() .create(string.clone(), vec![string.len().into()])), - Self::Symbol(ref symbol) => Ok(context + InnerValue::Symbol(ref symbol) => Ok(context .intrinsics() .templates() .symbol() .create(symbol.clone(), Vec::default())), - Self::BigInt(ref bigint) => Ok(context + InnerValue::BigInt(ref bigint) => Ok(context .intrinsics() .templates() .bigint() .create(bigint.clone(), Vec::default())), - Self::Object(jsobject) => Ok(jsobject.clone()), + InnerValue::Object(jsobject) => Ok(jsobject.clone()), } } @@ -602,19 +645,22 @@ impl JsValue { /// /// See pub fn to_property_key(&self, context: &mut Context) -> JsResult { - Ok(match self { + Ok(match &self.inner { // Fast path: - Self::String(string) => string.clone().into(), - Self::Symbol(symbol) => symbol.clone().into(), - Self::Integer(integer) => (*integer).into(), + InnerValue::String(string) => string.clone().into(), + InnerValue::Symbol(symbol) => symbol.clone().into(), + InnerValue::Integer32(integer) => (*integer).into(), // Slow path: - Self::Object(_) => match self.to_primitive(context, PreferredType::String)? { - Self::String(ref string) => string.clone().into(), - Self::Symbol(ref symbol) => symbol.clone().into(), - Self::Integer(integer) => integer.into(), - primitive => primitive.to_string(context)?.into(), - }, - primitive => primitive.to_string(context)?.into(), + InnerValue::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(), + _ => primitive.to_string(context)?.into(), + } + } + _ => self.to_string(context)?.into(), }) } @@ -634,14 +680,14 @@ impl JsValue { Ok(primitive.to_number(context)?.into()) } - /// Converts a value to an integral 32 bit unsigned integer. + /// Converts a value to an integral 32-bit unsigned integer. /// /// This function is equivalent to `value | 0` in JavaScript /// /// 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 Self::Integer(number) = *self { + if let InnerValue::Integer32(number) = self.inner { if let Ok(number) = u32::try_from(number) { return Ok(number); } @@ -651,12 +697,12 @@ impl JsValue { Ok(f64_to_uint32(number)) } - /// Converts a value to an integral 32 bit signed integer. + /// Converts a value to an integral 32-bit signed integer. /// /// 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 Self::Integer(number) = *self { + if let InnerValue::Integer32(number) = self.inner { return Ok(number); } let number = self.to_number(context)?; @@ -928,20 +974,20 @@ impl JsValue { /// /// See: pub fn to_number(&self, context: &mut Context) -> JsResult { - match *self { - Self::Null => Ok(0.0), - Self::Undefined => Ok(f64::NAN), - Self::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), - Self::String(ref string) => Ok(string.to_number()), - Self::Rational(number) => Ok(number), - Self::Integer(integer) => Ok(f64::from(integer)), - Self::Symbol(_) => Err(JsNativeError::typ() + 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() .with_message("argument must not be a symbol") .into()), - Self::BigInt(_) => Err(JsNativeError::typ() + InnerValue::BigInt(_) => Err(JsNativeError::typ() .with_message("argument must not be a bigint") .into()), - Self::Object(_) => { + InnerValue::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_number(context) } @@ -1015,43 +1061,13 @@ impl JsValue { /// [spec]: https://tc39.es/ecma262/#sec-typeof-operator #[must_use] pub fn type_of(&self) -> &'static str { - match *self { - Self::Rational(_) | Self::Integer(_) => "number", - Self::String(_) => "string", - Self::Boolean(_) => "boolean", - Self::Symbol(_) => "symbol", - Self::Null => "object", - Self::Undefined => "undefined", - Self::BigInt(_) => "bigint", - Self::Object(ref object) => { - if object.is_callable() { - "function" - } else { - "object" - } - } - } + self.variant().type_of() } /// Same as [`JsValue::type_of`], but returning a [`JsString`] instead. #[must_use] pub fn js_type_of(&self) -> JsString { - match *self { - Self::Rational(_) | Self::Integer(_) => js_string!("number"), - Self::String(_) => js_string!("string"), - Self::Boolean(_) => js_string!("boolean"), - Self::Symbol(_) => js_string!("symbol"), - Self::Null => js_string!("object"), - Self::Undefined => js_string!("undefined"), - Self::BigInt(_) => js_string!("bigint"), - Self::Object(ref object) => { - if object.is_callable() { - js_string!("function") - } else { - js_string!("object") - } - } - } + self.variant().js_type_of() } /// Maps a `JsValue` into `Option` where T is the result of an @@ -1071,7 +1087,7 @@ impl JsValue { /// let defined_result = defined_value.map(|v| v.add(&JsValue::from(5), &mut context)).transpose().unwrap(); /// let undefined_result = undefined.map(|v| v.add(&JsValue::from(5), &mut context)).transpose().unwrap(); /// - /// assert_eq!(defined_result, Some(JsValue::Integer(10))); + /// assert_eq!(defined_result, Some(JsValue::from(10u8))); /// assert_eq!(undefined_result, None); /// /// ``` @@ -1102,14 +1118,14 @@ impl JsValue { /// let undefined = JsValue::undefined(); /// /// let defined_result = defined_value - /// .map_or(Ok(JsValue::Boolean(true)), |v| v.add(&JsValue::from(5), &mut context)) + /// .map_or(Ok(JsValue::new(true)), |v| v.add(&JsValue::from(5), &mut context)) /// .unwrap(); /// let undefined_result = undefined - /// .map_or(Ok(JsValue::Boolean(true)), |v| v.add(&JsValue::from(5), &mut context)) + /// .map_or(Ok(JsValue::new(true)), |v| v.add(&JsValue::from(5), &mut context)) /// .unwrap(); /// - /// assert_eq!(defined_result, JsValue::Integer(10)); - /// assert_eq!(undefined_result, JsValue::Boolean(true)); + /// assert_eq!(defined_result, JsValue::new(10)); + /// assert_eq!(undefined_result, JsValue::new(true)); /// /// ``` #[inline] @@ -1144,7 +1160,7 @@ impl JsValue { impl Default for JsValue { fn default() -> Self { - Self::Undefined + Self::undefined() } } diff --git a/core/engine/src/value/operations.rs b/core/engine/src/value/operations.rs index d243315beaa..2b0d659c4d2 100644 --- a/core/engine/src/value/operations.rs +++ b/core/engine/src/value/operations.rs @@ -1,3 +1,4 @@ +use crate::value::InnerValue; use crate::{ builtins::{ number::{f64_to_int32, f64_to_uint32}, @@ -12,56 +13,63 @@ use crate::{ 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, other) { + Ok(match (&self.inner, &other.inner) { // Fast path: // Numeric add - (Self::Integer(x), Self::Integer(y)) => x + (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x .checked_add(*y) .map_or_else(|| Self::new(f64::from(*x) + f64::from(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x + y), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) + y), - (Self::Rational(x), Self::Integer(y)) => Self::new(x + f64::from(*y)), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::add(x, y)), + (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)) + } // String concat - (Self::String(ref x), Self::String(ref y)) => Self::from(js_string!(x, y)), + (InnerValue::String(ref x), InnerValue::String(ref y)) => Self::from(js_string!(x, y)), // Slow path: - (_, _) => match ( - self.to_primitive(context, PreferredType::Default)?, - other.to_primitive(context, PreferredType::Default)?, - ) { - (Self::String(ref x), ref y) => Self::from(js_string!(x, &y.to_string(context)?)), - (ref x, Self::String(ref y)) => Self::from(js_string!(&x.to_string(context)?, y)), - (x, y) => match (x.to_numeric(context)?, y.to_numeric(context)?) { - (Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y), - (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => { - Self::new(JsBigInt::add(x, y)) + (_, _) => { + let x = self.to_primitive(context, PreferredType::Default)?; + let y = other.to_primitive(context, PreferredType::Default)?; + match (&x.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)), (_, _) => { - return Err(JsNativeError::typ() - .with_message( - "cannot mix BigInt and other types, use explicit conversions", - ) - .into()) + match (x.to_numeric(context)?, y.to_numeric(context)?) { + (Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y), + (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => { + Self::new(JsBigInt::add(x, y)) + } + (_, _) => return Err(JsNativeError::typ() + .with_message( + "cannot mix BigInt and other types, use explicit conversions", + ) + .into()), + } } - }, - }, + } + } }) } /// Perform the binary `-` operator on the value and return the result. pub fn sub(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (&self.inner, &other.inner) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => x + (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x .checked_sub(*y) .map_or_else(|| Self::new(f64::from(*x) - f64::from(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x - y), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) - y), - (Self::Rational(x), Self::Integer(y)) => Self::new(x - f64::from(*y)), + (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)), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)), + (InnerValue::BigInt(ref x), InnerValue::BigInt(ref y)) => { + Self::new(JsBigInt::sub(x, y)) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -78,16 +86,18 @@ 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, other) { + Ok(match (&self.inner, &other.inner) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => x + (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x .checked_mul(*y) .map_or_else(|| Self::new(f64::from(*x) * f64::from(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x * y), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) * y), - (Self::Rational(x), Self::Integer(y)) => Self::new(x * f64::from(*y)), + (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)), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)), + (InnerValue::BigInt(ref x), InnerValue::BigInt(ref y)) => { + Self::new(JsBigInt::mul(x, y)) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -104,17 +114,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, other) { + Ok(match (&self.inner, &other.inner) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => x + (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), - (Self::Rational(x), Self::Rational(y)) => Self::new(x / y), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) / y), - (Self::Rational(x), Self::Integer(y)) => Self::new(x / f64::from(*y)), + (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)), - (Self::BigInt(ref x), Self::BigInt(ref y)) => { + (InnerValue::BigInt(ref x), InnerValue::BigInt(ref y)) => { if y.is_zero() { return Err(JsNativeError::range() .with_message("BigInt division by zero") @@ -145,9 +155,9 @@ 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, other) { + Ok(match (&self.inner, &other.inner) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => { + (InnerValue::Integer32(x), InnerValue::Integer32(y)) => { if *y == 0 { Self::nan() } else { @@ -157,15 +167,17 @@ impl JsValue { } } } - (Self::Rational(x), Self::Rational(y)) => Self::new((x % y).copysign(*x)), - (Self::Integer(x), Self::Rational(y)) => { + (InnerValue::Float64(x), InnerValue::Float64(y)) => Self::new((x % y).copysign(*x)), + (InnerValue::Integer32(x), InnerValue::Float64(y)) => { let x = f64::from(*x); Self::new((x % y).copysign(x)) } - (Self::Rational(x), Self::Integer(y)) => Self::new((x % f64::from(*y)).copysign(*x)), + (InnerValue::Float64(x), InnerValue::Integer32(y)) => { + Self::new((x % f64::from(*y)).copysign(*x)) + } - (Self::BigInt(ref x), Self::BigInt(ref y)) => { + (InnerValue::BigInt(x), InnerValue::BigInt(y)) => { if y.is_zero() { return Err(JsNativeError::range() .with_message("BigInt division by zero") @@ -198,28 +210,30 @@ 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, other) { + Ok(match (&self.inner, &other.inner) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => u32::try_from(*y) + (InnerValue::Integer32(x), InnerValue::Integer32(y)) => u32::try_from(*y) .ok() .and_then(|y| x.checked_pow(y)) .map_or_else(|| Self::new(f64::from(*x).powi(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => { + (InnerValue::Float64(x), InnerValue::Float64(y)) => { if x.abs() == 1.0 && y.is_infinite() { Self::nan() } else { Self::new(x.powf(*y)) } } - (Self::Integer(x), Self::Rational(y)) => { + (InnerValue::Integer32(x), InnerValue::Float64(y)) => { if x.wrapping_abs() == 1 && y.is_infinite() { Self::nan() } else { Self::new(f64::from(*x).powf(*y)) } } - (Self::Rational(x), Self::Integer(y)) => Self::new(x.powi(*y)), - (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::pow(a, b)?), + (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)?) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -242,16 +256,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, other) { + Ok(match (&self.inner, &other.inner) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x & y), - (Self::Rational(x), Self::Rational(y)) => { + (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)) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x & f64_to_int32(*y)), - (Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) & y), + (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), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitand(x, y)), + (InnerValue::BigInt(x), InnerValue::BigInt(y)) => Self::new(JsBigInt::bitand(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -272,16 +286,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, other) { + Ok(match (&self.inner, &other.inner) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x | y), - (Self::Rational(x), Self::Rational(y)) => { + (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)) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x | f64_to_int32(*y)), - (Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) | y), + (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), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitor(x, y)), + (InnerValue::BigInt(x), InnerValue::BigInt(y)) => Self::new(JsBigInt::bitor(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -302,16 +316,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, other) { + Ok(match (&self.inner, &other.inner) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x ^ y), - (Self::Rational(x), Self::Rational(y)) => { + (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)) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x ^ f64_to_int32(*y)), - (Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) ^ y), + (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), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitxor(x, y)), + (InnerValue::BigInt(x), InnerValue::BigInt(y)) => Self::new(JsBigInt::bitxor(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -332,18 +346,24 @@ 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, other) { + Ok(match (&self.inner, &other.inner) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shl(*y as u32)), - (Self::Rational(x), Self::Rational(y)) => { + (InnerValue::Integer32(x), InnerValue::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))) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shl(f64_to_uint32(*y))), - (Self::Rational(x), Self::Integer(y)) => { + (InnerValue::Integer32(x), InnerValue::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)) } - (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::shift_left(a, b)?), + (InnerValue::BigInt(a), InnerValue::BigInt(b)) => { + Self::new(JsBigInt::shift_left(a, b)?) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -364,18 +384,24 @@ 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, other) { + Ok(match (&self.inner, &other.inner) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shr(*y as u32)), - (Self::Rational(x), Self::Rational(y)) => { + (InnerValue::Integer32(x), InnerValue::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))) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shr(f64_to_uint32(*y))), - (Self::Rational(x), Self::Integer(y)) => { + (InnerValue::Integer32(x), InnerValue::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)) } - (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::shift_right(a, b)?), + (InnerValue::BigInt(a), InnerValue::BigInt(b)) => { + Self::new(JsBigInt::shift_right(a, b)?) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -396,16 +422,18 @@ 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, other) { + Ok(match (&self.inner, &other.inner) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new((*x as u32).wrapping_shr(*y as u32)), - (Self::Rational(x), Self::Rational(y)) => { + (InnerValue::Integer32(x), InnerValue::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))) } - (Self::Integer(x), Self::Rational(y)) => { + (InnerValue::Integer32(x), InnerValue::Float64(y)) => { Self::new((*x as u32).wrapping_shr(f64_to_uint32(*y))) } - (Self::Rational(x), Self::Integer(y)) => { + (InnerValue::Float64(x), InnerValue::Integer32(y)) => { Self::new(f64_to_uint32(*x).wrapping_shr(*y as u32)) } @@ -469,18 +497,20 @@ impl JsValue { /// Returns the negated value. pub fn neg(&self, context: &mut Context) -> JsResult { - Ok(match *self { - Self::Symbol(_) | Self::Undefined => Self::new(f64::NAN), - Self::Object(_) => Self::new( + Ok(match self.inner { + InnerValue::Symbol(_) | InnerValue::Undefined => Self::new(f64::NAN), + InnerValue::Object(_) => Self::new( self.to_numeric_number(context) .map_or(f64::NAN, std::ops::Neg::neg), ), - Self::String(ref str) => Self::new(-str.to_number()), - Self::Rational(num) => Self::new(-num), - Self::Integer(0) | Self::Boolean(false) | Self::Null => Self::new(-f64::from(0)), - Self::Integer(num) => Self::new(-num), - Self::Boolean(true) => Self::new(-f64::from(1)), - Self::BigInt(ref x) => Self::new(JsBigInt::neg(x)), + 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)), }) } @@ -513,13 +543,17 @@ impl JsValue { left_first: bool, context: &mut Context, ) -> JsResult { - Ok(match (self, other) { + Ok(match (&self.inner, &other.inner) { // Fast path (for some common operations): - (Self::Integer(x), Self::Integer(y)) => (x < y).into(), - (Self::Integer(x), Self::Rational(y)) => Number::less_than(f64::from(*x), *y), - (Self::Rational(x), Self::Integer(y)) => Number::less_than(*x, f64::from(*y)), - (Self::Rational(x), Self::Rational(y)) => Number::less_than(*x, *y), - (Self::BigInt(ref x), Self::BigInt(ref y)) => (x < y).into(), + (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(), // Slow path: (_, _) => { @@ -534,13 +568,17 @@ impl JsValue { (px, py) }; - match (px, py) { - (Self::String(ref x), Self::String(ref y)) => (x < y).into(), - (Self::BigInt(ref x), Self::String(ref y)) => JsBigInt::from_js_string(y) - .map_or(AbstractRelation::Undefined, |y| (*x < y).into()), - (Self::String(ref x), Self::BigInt(ref y)) => JsBigInt::from_js_string(x) - .map_or(AbstractRelation::Undefined, |x| (x < *y).into()), - (px, py) => match (px.to_numeric(context)?, py.to_numeric(context)?) { + 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.to_numeric(context)?, py.to_numeric(context)?) { (Numeric::Number(x), Numeric::Number(y)) => Number::less_than(x, y), (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => (x < y).into(), (Numeric::BigInt(ref x), Numeric::Number(y)) => { diff --git a/core/engine/src/value/tests.rs b/core/engine/src/value/tests.rs index 7f00f68708b..6a898377717 100644 --- a/core/engine/src/value/tests.rs +++ b/core/engine/src/value/tests.rs @@ -708,6 +708,18 @@ fn to_bigint() { })]); } +#[test] +fn pad_end() { + run_test_actions([ + TestAction::assert_eq("'abc'.padEnd(10, false)", js_string!("abcfalsefa")), + TestAction::assert_eq("'abc'.padEnd(10, true)", js_string!("abctruetru")), + TestAction::assert_eq("'abc'.padEnd(10, null)", js_string!("abcnullnul")), + TestAction::assert_eq("'abc'.padEnd(10, 0)", js_string!("abc0000000")), + TestAction::assert_eq("'abc'.padEnd(10, -0)", js_string!("abc0000000")), + TestAction::assert_eq("'abc'.padEnd(10, NaN)", js_string!("abcNaNNaNN")), + ]); +} + /// Test cyclic conversions that previously caused stack overflows /// Relevant mitigation for these are in `JsObject::ordinary_to_primitive` and /// `JsObject::to_json` diff --git a/core/engine/src/value/type.rs b/core/engine/src/value/type.rs index 74ca4d4141e..6d4b6d9c184 100644 --- a/core/engine/src/value/type.rs +++ b/core/engine/src/value/type.rs @@ -1,4 +1,4 @@ -use super::JsValue; +use super::{InnerValue, JsValue}; /// Possible types of values as defined at . #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -37,15 +37,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 { - Self::Rational(_) | Self::Integer(_) => Type::Number, - Self::String(_) => Type::String, - Self::Boolean(_) => Type::Boolean, - Self::Symbol(_) => Type::Symbol, - Self::Null => Type::Null, - Self::Undefined => Type::Undefined, - Self::BigInt(_) => Type::BigInt, - Self::Object(_) => Type::Object, + match self.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, } } } diff --git a/core/engine/src/value/variant.rs b/core/engine/src/value/variant.rs new file mode 100644 index 00000000000..dec2827e264 --- /dev/null +++ b/core/engine/src/value/variant.rs @@ -0,0 +1,120 @@ +use super::InnerValue; +use crate::{JsBigInt, JsObject, JsSymbol, JsValue}; +use boa_engine::js_string; +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)] +pub enum JsVariant<'a> { + /// `null` - A null value, for when a value doesn't exist. + Null, + /// `undefined` - An undefined value, for when a field or index doesn't exist. + Undefined, + /// `boolean` - A `true` / `false` value, for if a certain criteria is met. + Boolean(bool), + /// `String` - A UTF-16 string, such as `"Hello, world"`. + String(&'a JsString), + /// `Number` - A 64-bit floating point number, such as `3.1415` or `Infinity`. + /// This is the default representation of a number. If a number can be represented + /// as an integer, it will be stored as an `Integer` variant instead. + Float64(f64), + /// `Number` - A 32-bit integer, such as `42`. + Integer32(i32), + /// `BigInt` - holds any arbitrary large signed integer. + BigInt(&'a JsBigInt), + /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values. + Object(&'a JsObject), + /// `Symbol` - A Symbol Primitive type. + 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 { + JsVariant::Null => JsValue::null(), + JsVariant::Undefined => JsValue::undefined(), + JsVariant::Boolean(b) => JsValue::new(b), + JsVariant::String(s) => JsValue::new(s.clone()), + JsVariant::Float64(f) => JsValue::new(f), + JsVariant::Integer32(i) => JsValue::new(i), + JsVariant::BigInt(b) => JsValue::new(b.clone()), + JsVariant::Object(o) => JsValue::new(o.clone()), + JsVariant::Symbol(s) => JsValue::new(s.clone()), + } + } +} + +impl JsVariant<'_> { + /// Check if the variant is an `undefined` value. + #[inline] + #[must_use] + pub fn is_undefined(&self) -> bool { + matches!(self, JsVariant::Undefined) + } + + /// `typeof` operator. Returns a string representing the type of the + /// given ECMA Value. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-typeof-operator + #[must_use] + pub fn type_of(&self) -> &'static str { + match self { + JsVariant::Float64(_) | JsVariant::Integer32(_) => "number", + JsVariant::String(_) => "string", + JsVariant::Boolean(_) => "boolean", + JsVariant::Symbol(_) => "symbol", + JsVariant::Null => "object", + JsVariant::Undefined => "undefined", + JsVariant::BigInt(_) => "bigint", + JsVariant::Object(object) => { + if object.is_callable() { + "function" + } else { + "object" + } + } + } + } + + /// Same as [`JsVariant::type_of`], but returning a [`JsString`] instead. + #[must_use] + pub fn js_type_of(&self) -> JsString { + match self { + JsVariant::Float64(_) | JsVariant::Integer32(_) => js_string!("number"), + JsVariant::String(_) => js_string!("string"), + JsVariant::Boolean(_) => js_string!("boolean"), + JsVariant::Symbol(_) => js_string!("symbol"), + JsVariant::Null => js_string!("object"), + JsVariant::Undefined => js_string!("undefined"), + JsVariant::BigInt(_) => js_string!("bigint"), + JsVariant::Object(object) => { + if object.is_callable() { + js_string!("function") + } else { + js_string!("object") + } + } + } + } +} diff --git a/core/engine/src/vm/call_frame/mod.rs b/core/engine/src/vm/call_frame/mod.rs index 3aca4f33a28..f541a0d296e 100644 --- a/core/engine/src/vm/call_frame/mod.rs +++ b/core/engine/src/vm/call_frame/mod.rs @@ -377,8 +377,8 @@ impl JsValue { /// If not a integer type or not in the range `1..=2`. #[track_caller] pub(crate) fn to_generator_resume_kind(&self) -> GeneratorResumeKind { - if let Self::Integer(value) = self { - match *value { + if let Some(value) = self.as_i32() { + match value { 0 => return GeneratorResumeKind::Normal, 1 => return GeneratorResumeKind::Throw, 2 => return GeneratorResumeKind::Return, diff --git a/core/engine/src/vm/opcode/call/mod.rs b/core/engine/src/vm/opcode/call/mod.rs index 7db445a0cd9..c164742c56d 100644 --- a/core/engine/src/vm/opcode/call/mod.rs +++ b/core/engine/src/vm/opcode/call/mod.rs @@ -60,7 +60,7 @@ impl CallEval { context.vm.push(result); } else { // NOTE: This is a deviation from the spec, to optimize the case when we dont pass anything to `eval`. - context.vm.push(JsValue::Undefined); + context.vm.push(JsValue::undefined()); } return Ok(CompletionType::Normal); @@ -154,7 +154,7 @@ impl CallEvalSpread { context.vm.push(result); } else { // NOTE: This is a deviation from the spec, to optimize the case when we dont pass anything to `eval`. - context.vm.push(JsValue::Undefined); + context.vm.push(JsValue::undefined()); } return Ok(CompletionType::Normal); diff --git a/core/engine/src/vm/opcode/control_flow/jump.rs b/core/engine/src/vm/opcode/control_flow/jump.rs index 5ed13643b5d..387a2de2e0d 100644 --- a/core/engine/src/vm/opcode/control_flow/jump.rs +++ b/core/engine/src/vm/opcode/control_flow/jump.rs @@ -1,6 +1,6 @@ use crate::{ vm::{opcode::Operation, CompletionType}, - Context, JsResult, JsValue, + Context, JsResult, }; /// `Jump` implements the Opcode Operation for `Opcode::Jump` @@ -128,8 +128,8 @@ impl Operation for JumpTable { let count = context.vm.read::(); let value = context.vm.pop(); - if let JsValue::Integer(value) = &value { - let value = *value as u32; + if let Some(value) = value.as_i32() { + let value = value as u32; let mut target = None; for i in 0..count { let address = context.vm.read::(); diff --git a/core/engine/src/vm/opcode/set/class_prototype.rs b/core/engine/src/vm/opcode/set/class_prototype.rs index be0d89c2054..8cd7737b96d 100644 --- a/core/engine/src/vm/opcode/set/class_prototype.rs +++ b/core/engine/src/vm/opcode/set/class_prototype.rs @@ -1,9 +1,10 @@ +use crate::value::JsVariant; use crate::{ builtins::{function::OrdinaryFunction, OrdinaryObject}, object::{internal_methods::InternalMethodContext, JsObject, CONSTRUCTOR, PROTOTYPE}, property::PropertyDescriptorBuilder, vm::{opcode::Operation, CompletionType}, - Context, JsResult, JsValue, + Context, JsResult, }; /// `SetClassProtoType` implements the Opcode Operation for `Opcode::SetClassPrototype` @@ -20,10 +21,10 @@ impl Operation for SetClassPrototype { fn execute(context: &mut Context) -> JsResult { let prototype_value = context.vm.pop(); - let prototype = match &prototype_value { - JsValue::Object(proto) => Some(proto.clone()), - JsValue::Null => None, - JsValue::Undefined => Some(context.intrinsics().constructors().object().prototype()), + let prototype = match prototype_value.variant() { + JsVariant::Object(proto) => Some(proto.clone()), + JsVariant::Null => None, + JsVariant::Undefined => Some(context.intrinsics().constructors().object().prototype()), _ => unreachable!(), }; diff --git a/core/engine/src/vm/opcode/set/property.rs b/core/engine/src/vm/opcode/set/property.rs index 07256681061..870f8dce8ac 100644 --- a/core/engine/src/vm/opcode/set/property.rs +++ b/core/engine/src/vm/opcode/set/property.rs @@ -1,11 +1,12 @@ use boa_macros::js_str; +use crate::value::JsVariant; use crate::{ builtins::function::set_function_name, object::{internal_methods::InternalMethodContext, shape::slot::SlotAttributes}, property::{PropertyDescriptor, PropertyKey}, vm::{opcode::Operation, CompletionType}, - Context, JsNativeError, JsResult, JsValue, + Context, JsNativeError, JsResult, }; /// `SetPropertyByName` implements the Opcode Operation for `Opcode::SetPropertyByName` @@ -379,9 +380,9 @@ impl Operation for SetFunctionName { let function = context.vm.pop(); let name = context.vm.pop(); - let name = match name { - JsValue::String(name) => name.into(), - JsValue::Symbol(name) => name.into(), + let name = match name.variant() { + JsVariant::String(name) => PropertyKey::from(name.clone()), + JsVariant::Symbol(name) => PropertyKey::from(name.clone()), _ => unreachable!(), }; diff --git a/core/engine/src/vm/opcode/unary_ops/decrement.rs b/core/engine/src/vm/opcode/unary_ops/decrement.rs index 978b10607fc..074704dd334 100644 --- a/core/engine/src/vm/opcode/unary_ops/decrement.rs +++ b/core/engine/src/vm/opcode/unary_ops/decrement.rs @@ -1,5 +1,6 @@ +use crate::value::JsVariant; use crate::{ - value::{JsValue, Numeric}, + value::Numeric, vm::{opcode::Operation, CompletionType}, Context, JsBigInt, JsResult, }; @@ -18,8 +19,8 @@ impl Operation for Dec { fn execute(context: &mut Context) -> JsResult { let value = context.vm.pop(); - match value { - JsValue::Integer(number) if number > i32::MIN => { + match value.variant() { + JsVariant::Integer32(number) if number > i32::MIN => { context.vm.push(number - 1); } _ => match value.to_numeric(context)? { @@ -47,8 +48,8 @@ impl Operation for DecPost { fn execute(context: &mut Context) -> JsResult { let value = context.vm.pop(); - match value { - JsValue::Integer(number) if number > i32::MIN => { + match value.variant() { + JsVariant::Integer32(number) if number > i32::MIN => { context.vm.push(number - 1); context.vm.push(value); } diff --git a/core/engine/src/vm/opcode/unary_ops/increment.rs b/core/engine/src/vm/opcode/unary_ops/increment.rs index 87fefb21934..45f9d8de0cf 100644 --- a/core/engine/src/vm/opcode/unary_ops/increment.rs +++ b/core/engine/src/vm/opcode/unary_ops/increment.rs @@ -1,5 +1,6 @@ +use crate::value::JsVariant; use crate::{ - value::{JsValue, Numeric}, + value::Numeric, vm::{opcode::Operation, CompletionType}, Context, JsBigInt, JsResult, }; @@ -18,8 +19,8 @@ impl Operation for Inc { fn execute(context: &mut Context) -> JsResult { let value = context.vm.pop(); - match value { - JsValue::Integer(number) if number < i32::MAX => { + match value.variant() { + JsVariant::Integer32(number) if number < i32::MAX => { context.vm.push(number + 1); } _ => match value.to_numeric(context)? { @@ -47,8 +48,8 @@ impl Operation for IncPost { fn execute(context: &mut Context) -> JsResult { let value = context.vm.pop(); - match value { - JsValue::Integer(number) if number < i32::MAX => { + match value.variant() { + JsVariant::Integer32(number) if number < i32::MAX => { context.vm.push(number + 1); context.vm.push(value); } diff --git a/core/engine/tests/imports.rs b/core/engine/tests/imports.rs index d3f38e2c68b..36def275947 100644 --- a/core/engine/tests/imports.rs +++ b/core/engine/tests/imports.rs @@ -38,10 +38,7 @@ fn subdirectories() { .call(&JsValue::undefined(), &[], &mut context) .unwrap(); - assert_eq!( - foo_value, - JsValue::String(js_string!("file1..file1_1.file1_2")) - ); + assert_eq!(foo_value, js_string!("file1..file1_1.file1_2").into()); } PromiseState::Rejected(reason) => { panic!("Module failed to load: {}", reason.display()); diff --git a/core/interop/src/lib.rs b/core/interop/src/lib.rs index 722f3e99b87..436680d29db 100644 --- a/core/interop/src/lib.rs +++ b/core/interop/src/lib.rs @@ -270,7 +270,7 @@ impl<'a> IntoIterator for JsRest<'a> { /// JsValue::from(1), /// JsValue::from(2), /// JsValue::from(3), -/// JsValue::Boolean(true), +/// JsValue::from(true), /// JsValue::from(4), /// ], /// &mut context, diff --git a/core/macros/src/lib.rs b/core/macros/src/lib.rs index 5913ef43ecb..b5247c4b59b 100644 --- a/core/macros/src/lib.rs +++ b/core/macros/src/lib.rs @@ -402,13 +402,11 @@ pub fn derive_try_from_js(input: TokenStream) -> TokenStream { impl ::boa_engine::value::TryFromJs for #type_name { fn try_from_js(value: &boa_engine::JsValue, context: &mut boa_engine::Context) -> boa_engine::JsResult { - match value { - boa_engine::JsValue::Object(o) => {#conv}, - _ => Err(boa_engine::JsError::from( - boa_engine::JsNativeError::typ() - .with_message("cannot convert value to a #type_name") - )), - } + let o = value.as_object().ok_or_else(|| ::boa_engine::JsError::from( + ::boa_engine::JsNativeError::typ() + .with_message("value is not an object") + ))?; + #conv } } }; diff --git a/core/runtime/src/console/mod.rs b/core/runtime/src/console/mod.rs index 020c9fcdace..4789f29730b 100644 --- a/core/runtime/src/console/mod.rs +++ b/core/runtime/src/console/mod.rs @@ -836,7 +836,7 @@ impl Console { logger: &impl Logger, context: &mut Context, ) -> JsResult { - Console::group(&JsValue::Undefined, args, console, logger, context) + Console::group(&JsValue::undefined(), args, console, logger, context) } /// `console.groupEnd(label)` diff --git a/examples/src/bin/derive.rs b/examples/src/bin/derive.rs index 81deb460574..fb9590e6999 100644 --- a/examples/src/bin/derive.rs +++ b/examples/src/bin/derive.rs @@ -1,3 +1,4 @@ +use boa_engine::value::JsVariant; use boa_engine::{value::TryFromJs, Context, JsNativeError, JsResult, JsValue, Source}; /// You can easily derive `TryFromJs` for structures with base Rust types. @@ -37,9 +38,9 @@ fn main() { /// Converts the value lossly fn lossy_conversion(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Rational(r) => Ok(r.round() as i16), - JsValue::Integer(i) => Ok(*i as i16), + match value.variant() { + JsVariant::Float64(r) => Ok(r.round() as i16), + JsVariant::Integer32(i) => Ok(i as i16), _ => Err(JsNativeError::typ() .with_message("cannot convert value to an i16") .into()), diff --git a/examples/src/bin/jstypedarray.rs b/examples/src/bin/jstypedarray.rs index 7ffec384740..19abcc96831 100644 --- a/examples/src/bin/jstypedarray.rs +++ b/examples/src/bin/jstypedarray.rs @@ -45,7 +45,7 @@ fn main() -> JsResult<()> { JsValue::new(sum) ); - let greter_than_10_predicate = FunctionObjectBuilder::new( + let greater_than_10_predicate = FunctionObjectBuilder::new( context.realm(), NativeFunction::from_fn_ptr(|_this, args, _context| { let element = args @@ -54,13 +54,13 @@ fn main() -> JsResult<()> { .unwrap_or_default() .as_number() .expect("error at number conversion"); - Ok(JsValue::Boolean(element > 10.0)) + Ok(JsValue::from(element > 10.0)) }), ) .build(); assert_eq!( - array.find_index(greter_than_10_predicate, None, context), + array.find_index(greater_than_10_predicate, None, context), Ok(Some(11)) ); @@ -73,14 +73,14 @@ fn main() -> JsResult<()> { .unwrap_or_default() .as_number() .expect("error at number conversion"); - Ok(JsValue::Boolean(element < 200.0)) + Ok(JsValue::from(element < 200.0)) }), ) .build(); assert_eq!( array.find_last(lower_than_200_predicate.clone(), None, context), - Ok(JsValue::Integer(199)) + Ok(JsValue::from(199u8)) ); let data: Vec = vec![90, 120, 150, 180, 210, 240]; @@ -107,7 +107,7 @@ fn main() -> JsResult<()> { .expect("error at number conversion"); *captures.borrow_mut() += element; - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) }, Gc::clone(&num_to_modify), ), @@ -143,7 +143,7 @@ fn main() -> JsResult<()> { assert_eq!(initialized8_array.get(5, context)?, JsValue::new(0)); assert_eq!(initialized8_array.get(6, context)?, JsValue::new(0)); assert_eq!(initialized8_array.get(7, context)?, JsValue::new(0)); - assert_eq!(initialized8_array.get(8, context)?, JsValue::Undefined); + assert_eq!(initialized8_array.get(8, context)?, JsValue::undefined()); // subarray let array = JsUint8Array::from_iter(vec![1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8], context)?; diff --git a/examples/src/bin/modulehandler.rs b/examples/src/bin/modulehandler.rs index c36aaa2600d..cca53b5efa8 100644 --- a/examples/src/bin/modulehandler.rs +++ b/examples/src/bin/modulehandler.rs @@ -32,12 +32,7 @@ fn main() -> Result<(), Box> { // Adding custom object that mimics 'module.exports' let moduleobj = JsObject::default(); - moduleobj.set( - js_string!("exports"), - JsValue::from(js_string!(" ")), - false, - &mut ctx, - )?; + moduleobj.set(js_string!("exports"), js_string!(" "), false, &mut ctx)?; ctx.register_global_property( js_string!("module"), diff --git a/examples/src/bin/try_into_js_derive.rs b/examples/src/bin/try_into_js_derive.rs index 55de96e87a3..52ad5527d6d 100644 --- a/examples/src/bin/try_into_js_derive.rs +++ b/examples/src/bin/try_into_js_derive.rs @@ -69,7 +69,7 @@ fn main() -> JsResult<()> { }; let result = point_shift.call( - &JsValue::Undefined, + &JsValue::undefined(), &[a.try_into_js(context)?, b.try_into_js(context)?], context, )?; @@ -82,7 +82,7 @@ fn main() -> JsResult<()> { assert_eq!(verifier, expect); let result = point_shift.call( - &JsValue::Undefined, + &JsValue::undefined(), &[a.try_into_js(context)?, c.try_into_js(context)?], context, )?; @@ -92,5 +92,5 @@ fn main() -> JsResult<()> { } fn readable_into_js(value: &i8, _context: &mut Context) -> JsResult { - Ok(JsValue::Boolean(*value != 0)) + Ok(JsValue::new(*value != 0)) } diff --git a/tests/macros/tests/derive/from_js_with.rs b/tests/macros/tests/derive/from_js_with.rs index e7137e7e92e..d5d94026e79 100644 --- a/tests/macros/tests/derive/from_js_with.rs +++ b/tests/macros/tests/derive/from_js_with.rs @@ -1,6 +1,6 @@ #![allow(unused)] -use boa_engine::{value::TryFromJs, Context, JsNativeError, JsResult, JsValue}; +use boa_engine::{value::TryFromJs, Context, JsNativeError, JsResult, JsValue, JsVariant}; #[derive(TryFromJs)] struct TestStruct { @@ -12,9 +12,9 @@ struct TestStruct { fn main() {} fn lossy_float(value: &JsValue, _context: &mut Context) -> JsResult { - match value { - JsValue::Rational(r) => Ok(r.round() as i16), - JsValue::Integer(i) => Ok(*i as i16), + match value.variant() { + JsVariant::Float64(r) => Ok(r.round() as i16), + JsVariant::Integer32(i) => Ok(i as i16), _ => Err(JsNativeError::typ() .with_message("cannot convert value to an i16") .into()),