Skip to main content

boa_engine/value/
mod.rs

1//! Boa's ECMAScript Value implementation.
2//!
3//! Javascript values, utility methods and conversion between Javascript values and Rust values.
4
5use num_bigint::BigInt;
6use num_integer::Integer;
7use num_traits::{ToPrimitive, Zero};
8use std::{ops::Sub, sync::LazyLock};
9
10use boa_gc::{Finalize, Trace};
11#[doc(inline)]
12pub use boa_macros::TryFromJs;
13pub use boa_macros::TryIntoJs;
14#[doc(inline)]
15pub use conversions::convert::Convert;
16#[doc(inline)]
17pub use conversions::nullable::Nullable;
18
19pub(crate) use self::conversions::IntoOrUndefined;
20#[doc(inline)]
21pub use self::{
22    conversions::try_from_js::TryFromJs, conversions::try_into_js::TryIntoJs,
23    display::ValueDisplay, integer::IntegerOrInfinity, operations::*, r#type::Type,
24    variant::JsVariant,
25};
26use crate::builtins::RegExp;
27use crate::object::{JsFunction, JsPromise, JsRegExp};
28use crate::{
29    Context, JsBigInt, JsResult, JsString,
30    builtins::{
31        Number, Promise,
32        number::{f64_to_int32, f64_to_uint32},
33    },
34    error::JsNativeError,
35    js_string,
36    object::JsObject,
37    property::{PropertyDescriptor, PropertyKey},
38    symbol::JsSymbol,
39};
40
41mod conversions;
42pub(crate) mod display;
43mod equality;
44mod hash;
45mod inner;
46mod integer;
47mod operations;
48mod r#type;
49mod variant;
50
51#[cfg(test)]
52mod tests;
53
54static TWO_E_64: LazyLock<BigInt> = LazyLock::new(|| {
55    const TWO_E_64: u128 = 2u128.pow(64);
56    BigInt::from(TWO_E_64)
57});
58
59static TWO_E_63: LazyLock<BigInt> = LazyLock::new(|| {
60    const TWO_E_63: u128 = 2u128.pow(63);
61    BigInt::from(TWO_E_63)
62});
63
64/// The `js_value!` macro creates a `JsValue` instance based on a JSON-like DSL.
65///
66/// ```
67/// # use boa_engine::{js_string, js_value, Context, JsValue};
68/// # let context = &mut Context::default();
69/// assert_eq!(js_value!( 1 ), JsValue::from(1));
70/// assert_eq!(js_value!( false ), JsValue::from(false));
71/// // Objects and arrays cannot be compared with simple equality.
72/// // To create arrays and objects, the context needs to be passed in.
73/// assert_eq!(js_value!([ 1, 2, 3 ], context).display().to_string(), "[ 1, 2, 3 ]");
74/// assert_eq!(
75///   js_value!({
76///     // Comments are allowed inside.
77///     "key": (js_string!("value"))
78///   }, context).display().to_string(),
79///   "{\n    key: \"value\"\n}",
80/// );
81/// ```
82pub use boa_macros::js_object;
83
84/// Create a `JsObject` object from a simpler DSL that resembles JSON.
85///
86/// ```
87/// # use boa_engine::{js_string, js_object, Context, JsValue};
88/// # let context = &mut Context::default();
89/// let value = js_object!({
90///   // Comments are allowed inside. String literals will always be transformed to `JsString`.
91///   "key": "value",
92///   // Identifiers will be used as keys, like in JavaScript.
93///   alsoKey: 1,
94///   // Expressions surrounded by brackets will be expressed, like in JavaScript.
95///   // Note that in this case, the unit value is represented by `null`.
96///   [1 + 2]: (),
97/// }, context);
98///
99/// assert_eq!(
100///     JsValue::from(value).display().to_string(),
101///     "{\n    3: null,\n    key: \"value\",\n    alsoKey: 1\n}"
102/// );
103/// ```
104pub use boa_macros::js_value;
105
106/// A generic JavaScript value. This can be any ECMAScript language valid value.
107///
108/// This is a wrapper around the actual value, which is stored in an opaque type.
109/// This allows for internal changes to the value without affecting the public API.
110///
111/// ```
112/// # use boa_engine::{js_string, Context, JsValue};
113/// let mut context = Context::default();
114/// let value = JsValue::new(3);
115/// assert_eq!(value.to_string(&mut context), Ok(js_string!("3")));
116/// ```
117#[derive(Finalize, Debug, Clone, Trace)]
118pub struct JsValue(inner::InnerValue);
119
120impl JsValue {
121    /// Drops the value without the per-object collection check (see
122    /// [`boa_gc::Gc::release_outside_collection`]).
123    ///
124    /// # Safety
125    ///
126    /// The caller is not running inside a garbage collection sweep on this thread.
127    #[inline]
128    pub(crate) unsafe fn release_outside_collection(self) {
129        // SAFETY: forwarded from the caller.
130        let this = std::mem::ManuallyDrop::new(self);
131        // SAFETY: `this` is never dropped, so the inner value is read out exactly once.
132        let inner = unsafe { std::ptr::read(&raw const this.0) };
133        // SAFETY: forwarded from the caller.
134        unsafe { inner.release_outside_collection() };
135    }
136}
137
138/// A [`JsObject`] borrowed from a [`JsValue`], sharing its reference instead of taking a new one.
139pub(crate) struct JsObjectRef<'a>(
140    std::mem::ManuallyDrop<JsObject>,
141    std::marker::PhantomData<&'a JsValue>,
142);
143
144impl std::ops::Deref for JsObjectRef<'_> {
145    type Target = JsObject;
146
147    #[inline]
148    fn deref(&self) -> &JsObject {
149        &self.0
150    }
151}
152
153impl JsValue {
154    /// Create a new [`JsValue`] from an inner value.
155    #[inline]
156    /// The NaN-boxed bits of this value.
157    #[cfg(all(
158        feature = "jit",
159        not(feature = "jsvalue-enum"),
160        target_pointer_width = "64"
161    ))]
162    #[must_use]
163    pub(crate) fn jit_bits(&self) -> u64 {
164        self.0.bits()
165    }
166
167    const fn from_inner(inner: inner::InnerValue) -> Self {
168        Self(inner)
169    }
170
171    /// Create a new [`JsValue`].
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// use boa_engine::JsValue;
177    ///
178    /// let integer = JsValue::new(42);
179    /// assert_eq!(integer.as_number(), Some(42.0));
180    ///
181    /// let float = JsValue::new(3.14);
182    /// assert_eq!(float.as_number(), Some(3.14));
183    ///
184    /// let boolean = JsValue::new(true);
185    /// assert_eq!(boolean.as_boolean(), Some(true));
186    /// ```
187    #[inline]
188    #[must_use]
189    pub fn new<T>(value: T) -> Self
190    where
191        T: Into<Self>,
192    {
193        value.into()
194    }
195
196    /// Return the variant of this value.
197    ///
198    /// This can be used to match on the underlying type of the value.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use boa_engine::{JsValue, value::JsVariant};
204    ///
205    /// let value = JsValue::new(42);
206    /// match value.variant() {
207    ///     JsVariant::Integer32(n) => assert_eq!(n, 42),
208    ///     _ => unreachable!(),
209    /// }
210    ///
211    /// let value = JsValue::undefined();
212    /// assert!(matches!(value.variant(), JsVariant::Undefined));
213    /// ```
214    #[inline]
215    #[must_use]
216    pub fn variant(&self) -> JsVariant {
217        self.0.as_variant()
218    }
219
220    /// Creates a new `undefined` value.
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// use boa_engine::JsValue;
226    ///
227    /// let value = JsValue::undefined();
228    /// assert!(value.is_undefined());
229    /// assert!(value.is_null_or_undefined());
230    /// assert_eq!(value.display().to_string(), "undefined");
231    /// ```
232    #[inline]
233    #[must_use]
234    pub const fn undefined() -> Self {
235        Self::from_inner(inner::InnerValue::undefined())
236    }
237
238    /// Creates a new `null` value.
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// use boa_engine::JsValue;
244    ///
245    /// let value = JsValue::null();
246    /// assert!(value.is_null());
247    /// assert!(value.is_null_or_undefined());
248    /// assert_eq!(value.display().to_string(), "null");
249    /// ```
250    #[inline]
251    #[must_use]
252    pub const fn null() -> Self {
253        Self::from_inner(inner::InnerValue::null())
254    }
255
256    /// Creates a new number with `NaN` value.
257    ///
258    /// # Examples
259    ///
260    /// ```
261    /// use boa_engine::JsValue;
262    ///
263    /// let value = JsValue::nan();
264    /// assert!(value.is_number());
265    /// // NaN is not equal to itself.
266    /// assert!(value.as_number().unwrap().is_nan());
267    /// assert_eq!(value.display().to_string(), "NaN");
268    /// ```
269    #[inline]
270    #[must_use]
271    pub const fn nan() -> Self {
272        Self::from_inner(inner::InnerValue::float64(f64::NAN))
273    }
274
275    /// Creates a new number with `Infinity` value.
276    ///
277    /// # Examples
278    ///
279    /// ```
280    /// use boa_engine::JsValue;
281    ///
282    /// let value = JsValue::positive_infinity();
283    /// assert!(value.is_number());
284    /// assert_eq!(value.as_number(), Some(f64::INFINITY));
285    /// assert_eq!(value.display().to_string(), "Infinity");
286    /// ```
287    #[inline]
288    #[must_use]
289    pub const fn positive_infinity() -> Self {
290        Self::from_inner(inner::InnerValue::float64(f64::INFINITY))
291    }
292
293    /// Creates a new number with `-Infinity` value.
294    ///
295    /// # Examples
296    ///
297    /// ```
298    /// use boa_engine::JsValue;
299    ///
300    /// let value = JsValue::negative_infinity();
301    /// assert!(value.is_number());
302    /// assert_eq!(value.as_number(), Some(f64::NEG_INFINITY));
303    /// assert_eq!(value.display().to_string(), "-Infinity");
304    /// ```
305    #[inline]
306    #[must_use]
307    pub const fn negative_infinity() -> Self {
308        Self::from_inner(inner::InnerValue::float64(f64::NEG_INFINITY))
309    }
310
311    /// Creates a new number from a float.
312    ///
313    /// # Examples
314    ///
315    /// ```
316    /// use boa_engine::JsValue;
317    ///
318    /// let value = JsValue::rational(3.14);
319    /// assert!(value.is_number());
320    /// assert_eq!(value.as_number(), Some(3.14));
321    ///
322    /// // Can also represent special float values.
323    /// let neg_zero = JsValue::rational(-0.0);
324    /// assert!(neg_zero.is_number());
325    /// ```
326    // #[inline]
327    #[must_use]
328    pub fn rational(rational: f64) -> Self {
329        Self::from_inner(inner::InnerValue::float64(rational))
330    }
331
332    /// Returns true if the value is an object.
333    ///
334    /// # Examples
335    ///
336    /// ```
337    /// use boa_engine::{JsValue, object::JsObject};
338    ///
339    /// let obj = JsValue::new(JsObject::with_null_proto());
340    /// assert!(obj.is_object());
341    ///
342    /// let number = JsValue::new(42);
343    /// assert!(!number.is_object());
344    /// ```
345    #[inline]
346    #[must_use]
347    pub fn is_object(&self) -> bool {
348        self.0.is_object()
349    }
350
351    /// Returns the object if the value is object, otherwise `None`.
352    ///
353    /// # Examples
354    ///
355    /// ```
356    /// use boa_engine::{JsValue, object::JsObject};
357    ///
358    /// let obj = JsValue::new(JsObject::with_null_proto());
359    /// assert!(obj.as_object().is_some());
360    ///
361    /// let number = JsValue::new(42);
362    /// assert!(number.as_object().is_none());
363    /// ```
364    #[inline]
365    #[must_use]
366    pub fn as_object(&self) -> Option<JsObject> {
367        self.0.as_object()
368    }
369
370    /// Borrows the inner object if the value is an object, without touching its reference count.
371    #[inline]
372    #[must_use]
373    pub(crate) fn as_object_ref(&self) -> Option<JsObjectRef<'_>> {
374        self.0
375            .as_object_ref()
376            .map(|object| JsObjectRef(object, std::marker::PhantomData))
377    }
378
379    /// Consumes the value and return the inner object if it was an object.
380    ///
381    /// # Examples
382    ///
383    /// ```
384    /// use boa_engine::{JsValue, object::JsObject};
385    ///
386    /// let obj = JsValue::new(JsObject::with_null_proto());
387    /// let inner = obj.into_object();
388    /// assert!(inner.is_some());
389    ///
390    /// let number = JsValue::new(42);
391    /// assert!(number.into_object().is_none());
392    /// ```
393    #[inline]
394    #[must_use]
395    pub fn into_object(self) -> Option<JsObject> {
396        self.0.as_object()
397    }
398
399    /// It determines if the value is a callable function with a `[[Call]]` internal method.
400    ///
401    /// More information:
402    /// - [ECMAScript reference][spec]
403    ///
404    /// [spec]: https://tc39.es/ecma262/#sec-iscallable
405    ///
406    /// # Examples
407    ///
408    /// ```
409    /// use boa_engine::{Context, JsValue, NativeFunction};
410    ///
411    /// let context = &mut Context::default();
412    /// let native_fn = NativeFunction::from_copy_closure(|_, _, _| Ok(JsValue::undefined()));
413    /// let js_value = JsValue::from(native_fn.to_js_function(context.realm()));
414    /// assert!(js_value.is_callable());
415    ///
416    /// let number = JsValue::new(42);
417    /// assert!(!number.is_callable());
418    /// ```
419    #[inline]
420    #[must_use]
421    pub fn is_callable(&self) -> bool {
422        self.as_object().as_ref().is_some_and(JsObject::is_callable)
423    }
424
425    /// Returns the callable value if the value is callable, otherwise `None`.
426    ///
427    /// # Examples
428    ///
429    /// ```
430    /// use boa_engine::{Context, JsValue, NativeFunction};
431    ///
432    /// let context = &mut Context::default();
433    /// let native_fn = NativeFunction::from_copy_closure(|_, _, _| Ok(JsValue::undefined()));
434    /// let js_value = JsValue::from(native_fn.to_js_function(context.realm()));
435    /// assert!(js_value.as_callable().is_some());
436    ///
437    /// let number = JsValue::new(42);
438    /// assert!(number.as_callable().is_none());
439    /// ```
440    #[inline]
441    #[must_use]
442    pub fn as_callable(&self) -> Option<JsObject> {
443        self.as_object().filter(JsObject::is_callable)
444    }
445
446    /// Returns a [`JsFunction`] if the value is callable, otherwise `None`.
447    /// This is equivalent to `JsFunction::from_object(value.as_callable()?)`.
448    ///
449    /// # Examples
450    ///
451    /// ```
452    /// use boa_engine::{Context, JsValue, NativeFunction};
453    ///
454    /// let context = &mut Context::default();
455    /// let native_fn = NativeFunction::from_copy_closure(|_, _, _| Ok(JsValue::undefined()));
456    /// let js_value = JsValue::from(native_fn.to_js_function(context.realm()));
457    /// assert!(js_value.as_function().is_some());
458    ///
459    /// let number = JsValue::new(42);
460    /// assert!(number.as_function().is_none());
461    /// ```
462    #[inline]
463    #[must_use]
464    pub fn as_function(&self) -> Option<JsFunction> {
465        self.as_callable().and_then(JsFunction::from_object)
466    }
467
468    /// Returns true if the value is a constructor object.
469    ///
470    /// # Examples
471    ///
472    /// ```
473    /// use boa_engine::{Context, JsValue, Source};
474    ///
475    /// let mut context = Context::default();
476    /// // Classes and regular functions are constructors.
477    /// let class = context.eval(Source::from_bytes(b"(class {})")).unwrap();
478    /// assert!(class.is_constructor());
479    ///
480    /// // Arrow functions are not constructors.
481    /// let arrow = context.eval(Source::from_bytes(b"(() => {})")).unwrap();
482    /// assert!(!arrow.is_constructor());
483    /// ```
484    #[inline]
485    #[must_use]
486    pub fn is_constructor(&self) -> bool {
487        self.as_object()
488            .as_ref()
489            .is_some_and(JsObject::is_constructor)
490    }
491
492    /// Returns the constructor if the value is a constructor, otherwise `None`.
493    ///
494    /// # Examples
495    ///
496    /// ```
497    /// use boa_engine::{Context, JsValue, Source};
498    ///
499    /// let mut context = Context::default();
500    /// let class = context.eval(Source::from_bytes(b"(class {})")).unwrap();
501    /// assert!(class.as_constructor().is_some());
502    ///
503    /// let number = JsValue::new(42);
504    /// assert!(number.as_constructor().is_none());
505    /// ```
506    #[inline]
507    #[must_use]
508    pub fn as_constructor(&self) -> Option<JsObject> {
509        self.as_object().filter(JsObject::is_constructor)
510    }
511
512    /// Returns true if the value is a promise object.
513    ///
514    /// # Examples
515    ///
516    /// ```
517    /// use boa_engine::{Context, JsValue, object::builtins::JsPromise};
518    ///
519    /// let context = &mut Context::default();
520    /// let (promise, _) = JsPromise::new_pending(context);
521    /// let js_value = JsValue::from(promise);
522    /// assert!(js_value.is_promise());
523    ///
524    /// let number = JsValue::new(42);
525    /// assert!(!number.is_promise());
526    /// ```
527    #[inline]
528    #[must_use]
529    pub fn is_promise(&self) -> bool {
530        self.as_object().is_some_and(|obj| obj.is::<Promise>())
531    }
532
533    /// Returns the value as an object if the value is a promise, otherwise `None`.
534    #[inline]
535    #[must_use]
536    pub(crate) fn as_promise_object(&self) -> Option<JsObject<Promise>> {
537        self.as_object()
538            .and_then(|obj| obj.downcast::<Promise>().ok())
539    }
540
541    /// Returns the value as a promise if the value is a promise, otherwise `None`.
542    ///
543    /// # Examples
544    ///
545    /// ```
546    /// use boa_engine::{Context, JsValue, object::builtins::JsPromise};
547    ///
548    /// let context = &mut Context::default();
549    /// let (promise, _) = JsPromise::new_pending(context);
550    /// let js_value = JsValue::from(promise);
551    /// assert!(js_value.as_promise().is_some());
552    ///
553    /// let number = JsValue::new(42);
554    /// assert!(number.as_promise().is_none());
555    /// ```
556    #[inline]
557    #[must_use]
558    pub fn as_promise(&self) -> Option<JsPromise> {
559        self.as_promise_object().map(JsPromise::from)
560    }
561
562    /// Returns true if the value is a regular expression object.
563    ///
564    /// # Examples
565    ///
566    /// ```
567    /// use boa_engine::{Context, JsValue, js_string, object::builtins::JsRegExp};
568    ///
569    /// let context = &mut Context::default();
570    /// let regexp = JsRegExp::new(js_string!("abc"), js_string!("g"), context).unwrap();
571    /// let js_value = JsValue::from(regexp);
572    /// assert!(js_value.is_regexp());
573    ///
574    /// let string = JsValue::new(js_string!("abc"));
575    /// assert!(!string.is_regexp());
576    /// ```
577    #[inline]
578    #[must_use]
579    pub fn is_regexp(&self) -> bool {
580        self.as_object().is_some_and(|obj| obj.is::<RegExp>())
581    }
582
583    /// Returns the value as a regular expression if the value is a regexp, otherwise `None`.
584    ///
585    /// # Examples
586    ///
587    /// ```
588    /// use boa_engine::{Context, JsValue, js_string, object::builtins::JsRegExp};
589    ///
590    /// let context = &mut Context::default();
591    /// let regexp = JsRegExp::new(js_string!("abc"), js_string!("g"), context).unwrap();
592    /// let js_value = JsValue::from(regexp);
593    /// assert!(js_value.as_regexp().is_some());
594    ///
595    /// let number = JsValue::new(42);
596    /// assert!(number.as_regexp().is_none());
597    /// ```
598    #[inline]
599    #[must_use]
600    pub fn as_regexp(&self) -> Option<JsRegExp> {
601        self.as_object()
602            .filter(|obj| obj.is::<RegExp>())
603            .and_then(|o| JsRegExp::from_object(o).ok())
604    }
605
606    /// Returns true if the value is a symbol.
607    ///
608    /// # Examples
609    ///
610    /// ```
611    /// use boa_engine::{JsValue, JsSymbol};
612    ///
613    /// let sym = JsValue::new(JsSymbol::new(None).unwrap());
614    /// assert!(sym.is_symbol());
615    ///
616    /// let string = JsValue::new(boa_engine::js_string!("hello"));
617    /// assert!(!string.is_symbol());
618    /// ```
619    #[inline]
620    #[must_use]
621    pub fn is_symbol(&self) -> bool {
622        self.0.is_symbol()
623    }
624
625    /// Returns the symbol if the value is a symbol, otherwise `None`.
626    ///
627    /// # Examples
628    ///
629    /// ```
630    /// use boa_engine::{JsValue, JsSymbol};
631    ///
632    /// let sym = JsValue::new(JsSymbol::new(None).unwrap());
633    /// assert!(sym.as_symbol().is_some());
634    ///
635    /// let number = JsValue::new(42);
636    /// assert!(number.as_symbol().is_none());
637    /// ```
638    #[inline]
639    #[must_use]
640    pub fn as_symbol(&self) -> Option<JsSymbol> {
641        self.0.as_symbol()
642    }
643
644    /// Returns true if the value is undefined.
645    ///
646    /// # Examples
647    ///
648    /// ```
649    /// use boa_engine::JsValue;
650    ///
651    /// assert!(JsValue::undefined().is_undefined());
652    /// assert!(!JsValue::null().is_undefined());
653    /// assert!(!JsValue::new(0).is_undefined());
654    /// ```
655    #[inline]
656    #[must_use]
657    pub fn is_undefined(&self) -> bool {
658        self.0.is_undefined()
659    }
660
661    /// Returns true if the value is null.
662    ///
663    /// # Examples
664    ///
665    /// ```
666    /// use boa_engine::JsValue;
667    ///
668    /// assert!(JsValue::null().is_null());
669    /// assert!(!JsValue::undefined().is_null());
670    /// assert!(!JsValue::new(0).is_null());
671    /// ```
672    #[inline]
673    #[must_use]
674    pub fn is_null(&self) -> bool {
675        self.0.is_null()
676    }
677
678    /// Returns true if the value is null or undefined.
679    ///
680    /// # Examples
681    ///
682    /// ```
683    /// use boa_engine::JsValue;
684    ///
685    /// assert!(JsValue::null().is_null_or_undefined());
686    /// assert!(JsValue::undefined().is_null_or_undefined());
687    /// assert!(!JsValue::new(0).is_null_or_undefined());
688    /// assert!(!JsValue::new(false).is_null_or_undefined());
689    /// ```
690    #[inline]
691    #[must_use]
692    pub fn is_null_or_undefined(&self) -> bool {
693        self.0.is_null_or_undefined()
694    }
695
696    /// Returns the number if the value is a finite integral Number value, otherwise `None`.
697    ///
698    /// More information:
699    /// - [ECMAScript reference][spec]
700    ///
701    /// [spec]: https://tc39.es/ecma262/#sec-isintegralnumber
702    ///
703    /// # Examples
704    ///
705    /// ```
706    /// use boa_engine::JsValue;
707    ///
708    /// // Integers are returned directly.
709    /// assert_eq!(JsValue::new(42).as_i32(), Some(42));
710    ///
711    /// // Floats that are whole numbers also succeed.
712    /// assert_eq!(JsValue::new(5.0).as_i32(), Some(5));
713    ///
714    /// // Non-integral floats return None.
715    /// assert_eq!(JsValue::new(3.14).as_i32(), None);
716    ///
717    /// // Non-number types return None.
718    /// assert_eq!(JsValue::new(true).as_i32(), None);
719    /// ```
720    #[inline]
721    #[must_use]
722    #[allow(clippy::float_cmp)]
723    pub fn as_i32(&self) -> Option<i32> {
724        if let Some(integer) = self.0.as_integer32() {
725            return Some(integer);
726        }
727
728        if let Some(rational) = self.0.as_float64() {
729            let int_val = rational as i32;
730            // Use bitwise comparison to handle -0.0 correctly
731            if rational.to_bits() == f64::from(int_val).to_bits() {
732                return Some(int_val);
733            }
734        }
735        None
736    }
737
738    /// Returns true if the value is a number.
739    ///
740    /// # Examples
741    ///
742    /// ```
743    /// use boa_engine::JsValue;
744    ///
745    /// assert!(JsValue::new(42).is_number());
746    /// assert!(JsValue::new(3.14).is_number());
747    /// assert!(JsValue::nan().is_number());
748    ///
749    /// assert!(!JsValue::new(true).is_number());
750    /// assert!(!JsValue::undefined().is_number());
751    /// ```
752    #[inline]
753    #[must_use]
754    pub fn is_number(&self) -> bool {
755        self.0.is_integer32() || self.0.is_float64()
756    }
757
758    /// Returns true if the value is a negative zero (`-0`).
759    #[inline]
760    #[must_use]
761    pub(crate) fn is_negative_zero(&self) -> bool {
762        self.0.is_negative_zero()
763    }
764
765    /// Returns the number if the value is a number, otherwise `None`.
766    ///
767    /// # Examples
768    ///
769    /// ```
770    /// use boa_engine::JsValue;
771    ///
772    /// assert_eq!(JsValue::new(42).as_number(), Some(42.0));
773    /// assert_eq!(JsValue::new(3.14).as_number(), Some(3.14));
774    ///
775    /// // Non-number types return None.
776    /// assert_eq!(JsValue::null().as_number(), None);
777    /// assert_eq!(JsValue::new(true).as_number(), None);
778    /// ```
779    #[inline]
780    #[must_use]
781    pub fn as_number(&self) -> Option<f64> {
782        match self.variant() {
783            JsVariant::Integer32(i) => Some(f64::from(i)),
784            JsVariant::Float64(f) => Some(f),
785            _ => None,
786        }
787    }
788
789    /// Returns true if the value is a string.
790    ///
791    /// # Examples
792    ///
793    /// ```
794    /// use boa_engine::{JsValue, js_string};
795    ///
796    /// let s = JsValue::new(js_string!("hello"));
797    /// assert!(s.is_string());
798    ///
799    /// let number = JsValue::new(42);
800    /// assert!(!number.is_string());
801    /// ```
802    #[inline]
803    #[must_use]
804    pub fn is_string(&self) -> bool {
805        self.0.is_string()
806    }
807
808    /// Returns the string if the value is a string, otherwise `None`.
809    ///
810    /// # Examples
811    ///
812    /// ```
813    /// use boa_engine::{JsValue, js_string};
814    ///
815    /// let s = JsValue::new(js_string!("hello"));
816    /// assert_eq!(s.as_string().map(|s| s == js_string!("hello")), Some(true));
817    ///
818    /// let number = JsValue::new(42);
819    /// assert!(number.as_string().is_none());
820    /// ```
821    #[inline]
822    #[must_use]
823    pub fn as_string(&self) -> Option<JsString> {
824        self.0.as_string()
825    }
826
827    /// Returns true if the value is a boolean.
828    ///
829    /// # Examples
830    ///
831    /// ```
832    /// use boa_engine::JsValue;
833    ///
834    /// assert!(JsValue::new(true).is_boolean());
835    /// assert!(JsValue::new(false).is_boolean());
836    ///
837    /// assert!(!JsValue::new(1).is_boolean());
838    /// assert!(!JsValue::null().is_boolean());
839    /// ```
840    #[inline]
841    #[must_use]
842    pub fn is_boolean(&self) -> bool {
843        self.0.is_bool()
844    }
845
846    /// Returns the boolean if the value is a boolean, otherwise `None`.
847    ///
848    /// # Examples
849    ///
850    /// ```
851    /// use boa_engine::JsValue;
852    ///
853    /// assert_eq!(JsValue::new(true).as_boolean(), Some(true));
854    /// assert_eq!(JsValue::new(false).as_boolean(), Some(false));
855    ///
856    /// // Non-boolean types return None, even "truthy" or "falsy" ones.
857    /// assert_eq!(JsValue::new(1).as_boolean(), None);
858    /// assert_eq!(JsValue::new(0).as_boolean(), None);
859    /// ```
860    #[inline]
861    #[must_use]
862    pub fn as_boolean(&self) -> Option<bool> {
863        self.0.as_bool()
864    }
865
866    /// Returns true if the value is a bigint.
867    ///
868    /// # Examples
869    ///
870    /// ```
871    /// use boa_engine::{JsValue, JsBigInt};
872    ///
873    /// let big = JsValue::new(JsBigInt::from(42));
874    /// assert!(big.is_bigint());
875    ///
876    /// // Regular numbers are not bigints.
877    /// let number = JsValue::new(42);
878    /// assert!(!number.is_bigint());
879    /// ```
880    #[inline]
881    #[must_use]
882    pub fn is_bigint(&self) -> bool {
883        self.0.is_bigint()
884    }
885
886    /// Returns a `BigInt` if the value is a `BigInt` primitive.
887    ///
888    /// # Examples
889    ///
890    /// ```
891    /// use boa_engine::{JsValue, JsBigInt};
892    ///
893    /// let big = JsValue::new(JsBigInt::from(100));
894    /// assert!(big.as_bigint().is_some());
895    ///
896    /// let number = JsValue::new(100);
897    /// assert!(number.as_bigint().is_none());
898    /// ```
899    #[inline]
900    #[must_use]
901    pub fn as_bigint(&self) -> Option<JsBigInt> {
902        self.0.as_bigint()
903    }
904
905    /// Converts the value to a `bool` type.
906    ///
907    /// More information:
908    ///  - [ECMAScript][spec]
909    ///
910    /// [spec]: https://tc39.es/ecma262/#sec-toboolean
911    ///
912    /// # Examples
913    ///
914    /// ```
915    /// use boa_engine::{JsValue, js_string};
916    ///
917    /// // Numbers: 0 and NaN are false, everything else is true.
918    /// assert!(!JsValue::new(0).to_boolean());
919    /// assert!(!JsValue::nan().to_boolean());
920    /// assert!(JsValue::new(1).to_boolean());
921    /// assert!(JsValue::new(-1).to_boolean());
922    ///
923    /// // Strings: empty string is false, non-empty is true.
924    /// assert!(!JsValue::new(js_string!("")).to_boolean());
925    /// assert!(JsValue::new(js_string!("hello")).to_boolean());
926    ///
927    /// // null and undefined are always false.
928    /// assert!(!JsValue::null().to_boolean());
929    /// assert!(!JsValue::undefined().to_boolean());
930    ///
931    /// // Booleans pass through.
932    /// assert!(JsValue::new(true).to_boolean());
933    /// assert!(!JsValue::new(false).to_boolean());
934    /// ```
935    #[must_use]
936    #[inline]
937    pub fn to_boolean(&self) -> bool {
938        self.0.to_boolean()
939    }
940
941    /// The abstract operation `ToPrimitive` takes an input argument and an optional argument
942    /// `PreferredType`.
943    ///
944    /// <https://tc39.es/ecma262/#sec-toprimitive>
945    #[inline]
946    pub fn to_primitive(
947        &self,
948        context: &mut Context,
949        preferred_type: PreferredType,
950    ) -> JsResult<Self> {
951        // 1. Assert: input is an ECMAScript language value. (always a value not need to check)
952        // 2. If Type(input) is Object, then
953        if let Some(o) = self.as_object() {
954            return o.to_primitive(context, preferred_type);
955        }
956
957        // 3. Return input.
958        Ok(self.clone())
959    }
960
961    /// `7.1.13 ToBigInt ( argument )`
962    ///
963    /// More information:
964    ///  - [ECMAScript reference][spec]
965    ///
966    /// [spec]: https://tc39.es/ecma262/#sec-tobigint
967    pub fn to_bigint(&self, context: &mut Context) -> JsResult<JsBigInt> {
968        match self.variant() {
969            JsVariant::Null => Err(JsNativeError::typ()
970                .with_message("cannot convert null to a BigInt")
971                .into()),
972            JsVariant::Undefined => Err(JsNativeError::typ()
973                .with_message("cannot convert undefined to a BigInt")
974                .into()),
975            JsVariant::String(string) => JsBigInt::from_js_string(&string).map_or_else(
976                || {
977                    Err(JsNativeError::syntax()
978                        .with_message(format!(
979                            "cannot convert string '{}' to bigint primitive",
980                            string.to_std_string_escaped()
981                        ))
982                        .into())
983                },
984                Ok,
985            ),
986            JsVariant::Boolean(true) => Ok(JsBigInt::one()),
987            JsVariant::Boolean(false) => Ok(JsBigInt::zero()),
988            JsVariant::Integer32(_) | JsVariant::Float64(_) => Err(JsNativeError::typ()
989                .with_message("cannot convert Number to a BigInt")
990                .into()),
991            JsVariant::BigInt(b) => Ok(b),
992            JsVariant::Object(o) => o
993                .to_primitive(context, PreferredType::Number)?
994                .to_bigint(context),
995            JsVariant::Symbol(_) => Err(JsNativeError::typ()
996                .with_message("cannot convert Symbol to a BigInt")
997                .into()),
998        }
999    }
1000
1001    /// Returns an object that implements `Display`.
1002    ///
1003    /// By default, the internals are not shown, but they can be toggled
1004    /// with [`ValueDisplay::internals`] method.
1005    ///
1006    /// # Examples
1007    ///
1008    /// ```
1009    /// use boa_engine::JsValue;
1010    ///
1011    /// let value = JsValue::new(3);
1012    ///
1013    /// println!("{}", value.display());
1014    /// ```
1015    #[must_use]
1016    #[inline]
1017    pub const fn display(&self) -> ValueDisplay<'_> {
1018        ValueDisplay {
1019            value: self,
1020            internals: false,
1021        }
1022    }
1023
1024    /// Converts the value to a string.
1025    ///
1026    /// This function is equivalent to `String(value)` in JavaScript.
1027    pub fn to_string(&self, context: &mut Context) -> JsResult<JsString> {
1028        match self.variant() {
1029            JsVariant::Null => Ok(js_string!("null")),
1030            JsVariant::Undefined => Ok(js_string!("undefined")),
1031            JsVariant::Boolean(true) => Ok(js_string!("true")),
1032            JsVariant::Boolean(false) => Ok(js_string!("false")),
1033            JsVariant::Float64(rational) => Ok(JsString::from(rational)),
1034            JsVariant::Integer32(integer) => Ok(JsString::from(integer)),
1035            JsVariant::String(string) => Ok(string),
1036            JsVariant::Symbol(_) => Err(JsNativeError::typ()
1037                .with_message("can't convert symbol to string")
1038                .into()),
1039            JsVariant::BigInt(bigint) => Ok(bigint.to_string().into()),
1040            JsVariant::Object(o) => o
1041                .to_primitive(context, PreferredType::String)?
1042                .to_string(context),
1043        }
1044    }
1045
1046    /// Converts the value to an Object.
1047    ///
1048    /// This function is equivalent to `Object(value)` in JavaScript.
1049    ///
1050    /// See: <https://tc39.es/ecma262/#sec-toobject>
1051    pub fn to_object(&self, context: &mut Context) -> JsResult<JsObject> {
1052        match self.variant() {
1053            JsVariant::Undefined | JsVariant::Null => Err(JsNativeError::typ()
1054                .with_message("cannot convert 'null' or 'undefined' to object")
1055                .into()),
1056            JsVariant::Boolean(boolean) => Ok(context
1057                .intrinsics()
1058                .templates()
1059                .boolean()
1060                .create(boolean, Vec::default())),
1061            JsVariant::Integer32(integer) => Ok(context
1062                .intrinsics()
1063                .templates()
1064                .number()
1065                .create(f64::from(integer), Vec::default())),
1066            JsVariant::Float64(rational) => Ok(context
1067                .intrinsics()
1068                .templates()
1069                .number()
1070                .create(rational, Vec::default())),
1071            JsVariant::String(string) => {
1072                let len = string.len();
1073                Ok(context
1074                    .intrinsics()
1075                    .templates()
1076                    .string()
1077                    .create(string, vec![len.into()]))
1078            }
1079            JsVariant::Symbol(symbol) => Ok(context
1080                .intrinsics()
1081                .templates()
1082                .symbol()
1083                .create(symbol, Vec::default())),
1084            JsVariant::BigInt(bigint) => Ok(context
1085                .intrinsics()
1086                .templates()
1087                .bigint()
1088                .create(bigint, Vec::default())),
1089            JsVariant::Object(jsobject) => Ok(jsobject),
1090        }
1091    }
1092
1093    pub(crate) fn base_class(&self, context: &Context) -> JsResult<JsObject> {
1094        let constructors = context.intrinsics().constructors();
1095        match self.variant() {
1096            JsVariant::Undefined | JsVariant::Null => Err(JsNativeError::typ()
1097                .with_message("cannot convert 'null' or 'undefined' to object")
1098                .into()),
1099            JsVariant::Boolean(_) => Ok(constructors.boolean().prototype()),
1100            JsVariant::Integer32(_) | JsVariant::Float64(_) => {
1101                Ok(constructors.number().prototype())
1102            }
1103            JsVariant::String(_) => Ok(constructors.string().prototype()),
1104            JsVariant::Symbol(_) => Ok(constructors.symbol().prototype()),
1105            JsVariant::BigInt(_) => Ok(constructors.bigint().prototype()),
1106            JsVariant::Object(object) => Ok(object.clone()),
1107        }
1108    }
1109
1110    /// Converts the value to a `PropertyKey`, that can be used as a key for properties.
1111    ///
1112    /// See <https://tc39.es/ecma262/#sec-topropertykey>
1113    pub fn to_property_key(&self, context: &mut Context) -> JsResult<PropertyKey> {
1114        match self.variant() {
1115            // fast path
1116            //
1117            // The compiler will surely make this a jump table, but in case it
1118            // doesn't, we put the "expected" property key types first
1119            // (integer, string, symbol), then the rest of the variants.
1120            JsVariant::Integer32(integer) => Ok(integer.into()),
1121            JsVariant::String(string) => Ok(string.into()),
1122            JsVariant::Symbol(symbol) => Ok(symbol.into()),
1123
1124            // We also inline the call to `to_string`, removing the
1125            // double match against `self.variant()`.
1126            // An integral float in index range stringifies to its digits (and
1127            // -0 to "0"), so it is that index; skip formatting and re-parsing.
1128            JsVariant::Float64(float)
1129                if float >= 0.0 && float < f64::from(u32::MAX) && float.fract() == 0.0 =>
1130            {
1131                Ok((float as u32).into())
1132            }
1133            JsVariant::Float64(float) => Ok(JsString::from(float).into()),
1134            JsVariant::Undefined => Ok(js_string!("undefined").into()),
1135            JsVariant::Null => Ok(js_string!("null").into()),
1136            JsVariant::Boolean(true) => Ok(js_string!("true").into()),
1137            JsVariant::Boolean(false) => Ok(js_string!("false").into()),
1138            JsVariant::BigInt(bigint) => Ok(JsString::from(bigint.to_string()).into()),
1139
1140            // slow path
1141            // Cannot infinitely recurse since it is guaranteed that `to_primitive` returns a non-object
1142            // value or errors.
1143            JsVariant::Object(o) => o
1144                .to_primitive(context, PreferredType::String)?
1145                .to_property_key(context),
1146        }
1147    }
1148
1149    /// It returns value converted to a numeric value of type `Number` or `BigInt`.
1150    ///
1151    /// See: <https://tc39.es/ecma262/#sec-tonumeric>
1152    pub fn to_numeric(&self, context: &mut Context) -> JsResult<Numeric> {
1153        // 1. Let primValue be ? ToPrimitive(value, number).
1154        let primitive = self.to_primitive(context, PreferredType::Number)?;
1155
1156        // 2. If primValue is a BigInt, return primValue.
1157        if let Some(bigint) = primitive.as_bigint() {
1158            return Ok(bigint.into());
1159        }
1160
1161        // 3. Return ? ToNumber(primValue).
1162        Ok(primitive.to_number(context)?.into())
1163    }
1164
1165    /// Converts a value to an integral 32-bit unsigned integer.
1166    ///
1167    /// This function is equivalent to `value | 0` in JavaScript
1168    ///
1169    /// See: <https://tc39.es/ecma262/#sec-touint32>
1170    pub fn to_u32(&self, context: &mut Context) -> JsResult<u32> {
1171        // This is the fast path, if the value is Integer we can just return it.
1172        if let Some(number) = self.0.as_integer32()
1173            && let Ok(number) = u32::try_from(number)
1174        {
1175            return Ok(number);
1176        }
1177        let number = self.to_number(context)?;
1178
1179        Ok(f64_to_uint32(number))
1180    }
1181
1182    /// Converts a value to an integral 32-bit signed integer.
1183    ///
1184    /// See: <https://tc39.es/ecma262/#sec-toint32>
1185    pub fn to_i32(&self, context: &mut Context) -> JsResult<i32> {
1186        // This is the fast path, if the value is Integer we can just return it.
1187        if let Some(number) = self.0.as_integer32() {
1188            return Ok(number);
1189        }
1190        let number = self.to_number(context)?;
1191
1192        Ok(f64_to_int32(number))
1193    }
1194
1195    /// `7.1.10 ToInt8 ( argument )`
1196    ///
1197    /// More information:
1198    ///  - [ECMAScript reference][spec]
1199    ///
1200    /// [spec]: https://tc39.es/ecma262/#sec-toint8
1201    pub fn to_int8(&self, context: &mut Context) -> JsResult<i8> {
1202        // 1. Let number be ? ToNumber(argument).
1203        let number = self.to_number(context)?;
1204
1205        // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
1206        if number.is_nan() || number.is_zero() || number.is_infinite() {
1207            return Ok(0);
1208        }
1209
1210        // 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
1211        let int = number.abs().floor().copysign(number) as i64;
1212
1213        // 4. Let int8bit be int modulo 2^8.
1214        let int_8_bit = int % 2i64.pow(8);
1215
1216        // 5. If int8bit ≥ 2^7, return 𝔽(int8bit - 2^8); otherwise return 𝔽(int8bit).
1217        if int_8_bit >= 2i64.pow(7) {
1218            Ok((int_8_bit - 2i64.pow(8)) as i8)
1219        } else {
1220            Ok(int_8_bit as i8)
1221        }
1222    }
1223
1224    /// `7.1.11 ToUint8 ( argument )`
1225    ///
1226    /// More information:
1227    ///  - [ECMAScript reference][spec]
1228    ///
1229    /// [spec]: https://tc39.es/ecma262/#sec-touint8
1230    pub fn to_uint8(&self, context: &mut Context) -> JsResult<u8> {
1231        // 1. Let number be ? ToNumber(argument).
1232        let number = self.to_number(context)?;
1233
1234        // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
1235        if number.is_nan() || number.is_zero() || number.is_infinite() {
1236            return Ok(0);
1237        }
1238
1239        // 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
1240        let int = number.abs().floor().copysign(number) as i64;
1241
1242        // 4. Let int8bit be int modulo 2^8.
1243        let int_8_bit = int % 2i64.pow(8);
1244
1245        // 5. Return 𝔽(int8bit).
1246        Ok(int_8_bit as u8)
1247    }
1248
1249    /// `7.1.12 ToUint8Clamp ( argument )`
1250    ///
1251    /// More information:
1252    ///  - [ECMAScript reference][spec]
1253    ///
1254    /// [spec]: https://tc39.es/ecma262/#sec-touint8clamp
1255    pub fn to_uint8_clamp(&self, context: &mut Context) -> JsResult<u8> {
1256        // 1. Let number be ? ToNumber(argument).
1257        let number = self.to_number(context)?;
1258
1259        // 2. If number is NaN, return +0𝔽.
1260        if number.is_nan() {
1261            return Ok(0);
1262        }
1263
1264        // 3. If ℝ(number) ≤ 0, return +0𝔽.
1265        if number <= 0.0 {
1266            return Ok(0);
1267        }
1268
1269        // 4. If ℝ(number) ≥ 255, return 255𝔽.
1270        if number >= 255.0 {
1271            return Ok(255);
1272        }
1273
1274        // 5. Let f be floor(ℝ(number)).
1275        let f = number.floor();
1276
1277        // 6. If f + 0.5 < ℝ(number), return 𝔽(f + 1).
1278        if f + 0.5 < number {
1279            return Ok(f as u8 + 1);
1280        }
1281
1282        // 7. If ℝ(number) < f + 0.5, return 𝔽(f).
1283        if number < f + 0.5 {
1284            return Ok(f as u8);
1285        }
1286
1287        // 8. If f is odd, return 𝔽(f + 1).
1288        if !(f as u8).is_multiple_of(2) {
1289            return Ok(f as u8 + 1);
1290        }
1291
1292        // 9. Return 𝔽(f).
1293        Ok(f as u8)
1294    }
1295
1296    /// `7.1.8 ToInt16 ( argument )`
1297    ///
1298    /// More information:
1299    ///  - [ECMAScript reference][spec]
1300    ///
1301    /// [spec]: https://tc39.es/ecma262/#sec-toint16
1302    pub fn to_int16(&self, context: &mut Context) -> JsResult<i16> {
1303        // 1. Let number be ? ToNumber(argument).
1304        let number = self.to_number(context)?;
1305
1306        // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
1307        if number.is_nan() || number.is_zero() || number.is_infinite() {
1308            return Ok(0);
1309        }
1310
1311        // 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
1312        let int = number.abs().floor().copysign(number) as i64;
1313
1314        // 4. Let int16bit be int modulo 2^16.
1315        let int_16_bit = int % 2i64.pow(16);
1316
1317        // 5. If int16bit ≥ 2^15, return 𝔽(int16bit - 2^16); otherwise return 𝔽(int16bit).
1318        if int_16_bit >= 2i64.pow(15) {
1319            Ok((int_16_bit - 2i64.pow(16)) as i16)
1320        } else {
1321            Ok(int_16_bit as i16)
1322        }
1323    }
1324
1325    /// `7.1.9 ToUint16 ( argument )`
1326    ///
1327    /// More information:
1328    ///  - [ECMAScript reference][spec]
1329    ///
1330    /// [spec]: https://tc39.es/ecma262/#sec-touint16
1331    pub fn to_uint16(&self, context: &mut Context) -> JsResult<u16> {
1332        // 1. Let number be ? ToNumber(argument).
1333        let number = self.to_number(context)?;
1334
1335        // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
1336        if number.is_nan() || number.is_zero() || number.is_infinite() {
1337            return Ok(0);
1338        }
1339
1340        // 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
1341        let int = number.abs().floor().copysign(number) as i64;
1342
1343        // 4. Let int16bit be int modulo 2^16.
1344        let int_16_bit = int % 2i64.pow(16);
1345
1346        // 5. Return 𝔽(int16bit).
1347        Ok(int_16_bit as u16)
1348    }
1349
1350    /// `7.1.15 ToBigInt64 ( argument )`
1351    ///
1352    /// More information:
1353    ///  - [ECMAScript reference][spec]
1354    ///
1355    /// [spec]: https://tc39.es/ecma262/#sec-tobigint64
1356    pub fn to_big_int64(&self, context: &mut Context) -> JsResult<i64> {
1357        // 1. Let n be ? ToBigInt(argument).
1358        let n = self.to_bigint(context)?;
1359
1360        // 2. Let int64bit be ℝ(n) modulo 2^64.
1361        let int64_bit = n.as_inner().mod_floor(&TWO_E_64);
1362
1363        // 3. If int64bit ≥ 2^63, return ℤ(int64bit - 2^64); otherwise return ℤ(int64bit).
1364        let value = if int64_bit >= *TWO_E_63 {
1365            int64_bit.sub(&*TWO_E_64)
1366        } else {
1367            int64_bit
1368        };
1369
1370        Ok(value
1371            .to_i64()
1372            .expect("should be within the range of `i64` by the mod operation"))
1373    }
1374
1375    /// `7.1.16 ToBigUint64 ( argument )`
1376    ///
1377    /// More information:
1378    ///  - [ECMAScript reference][spec]
1379    ///
1380    /// [spec]: https://tc39.es/ecma262/#sec-tobiguint64
1381    pub fn to_big_uint64(&self, context: &mut Context) -> JsResult<u64> {
1382        // 1. Let n be ? ToBigInt(argument).
1383        let n = self.to_bigint(context)?;
1384
1385        // 2. Let int64bit be ℝ(n) modulo 2^64.
1386        // 3. Return ℤ(int64bit).
1387        Ok(n.as_inner()
1388            .mod_floor(&TWO_E_64)
1389            .to_u64()
1390            .expect("should be within the range of `u64` by the mod operation"))
1391    }
1392
1393    /// Converts a value to a non-negative integer if it is a valid integer index value.
1394    ///
1395    /// See: <https://tc39.es/ecma262/#sec-toindex>
1396    pub fn to_index(&self, context: &mut Context) -> JsResult<u64> {
1397        // 1. If value is undefined, then
1398        if self.is_undefined() {
1399            // a. Return 0.
1400            return Ok(0);
1401        }
1402
1403        // 2. Else,
1404        // a. Let integer be ? ToIntegerOrInfinity(value).
1405        let integer = self.to_integer_or_infinity(context)?;
1406
1407        // b. Let clamped be ! ToLength(𝔽(integer)).
1408        let clamped = integer.clamp_finite(0, Number::MAX_SAFE_INTEGER as i64);
1409
1410        // c. If ! SameValue(𝔽(integer), clamped) is false, throw a RangeError exception.
1411        if integer != clamped {
1412            return Err(JsNativeError::range()
1413                .with_message("Index must be between 0 and  2^53 - 1")
1414                .into());
1415        }
1416
1417        // d. Assert: 0 ≤ integer ≤ 2^53 - 1.
1418        debug_assert!(0 <= clamped && clamped <= Number::MAX_SAFE_INTEGER as i64);
1419
1420        // e. Return integer.
1421        Ok(clamped as u64)
1422    }
1423
1424    /// Converts argument to an integer suitable for use as the length of an array-like object.
1425    ///
1426    /// See: <https://tc39.es/ecma262/#sec-tolength>
1427    pub fn to_length(&self, context: &mut Context) -> JsResult<u64> {
1428        // 1. Let len be ? ToInteger(argument).
1429        // 2. If len ≤ +0, return +0.
1430        // 3. Return min(len, 2^53 - 1).
1431        Ok(self
1432            .to_integer_or_infinity(context)?
1433            .clamp_finite(0, Number::MAX_SAFE_INTEGER as i64) as u64)
1434    }
1435
1436    /// Abstract operation `ToIntegerOrInfinity ( argument )`
1437    ///
1438    /// This method converts a `Value` to an integer representing its `Number` value with
1439    /// fractional part truncated, or to +∞ or -∞ when that `Number` value is infinite.
1440    ///
1441    /// More information:
1442    /// - [ECMAScript reference][spec]
1443    ///
1444    /// [spec]: https://tc39.es/ecma262/#sec-tointegerorinfinity
1445    pub fn to_integer_or_infinity(&self, context: &mut Context) -> JsResult<IntegerOrInfinity> {
1446        // 1. Let number be ? ToNumber(argument).
1447        let number = self.to_number(context)?;
1448
1449        // Continues on `IntegerOrInfinity::from::<f64>`
1450        Ok(IntegerOrInfinity::from(number))
1451    }
1452
1453    /// Converts a value to a double precision floating point.
1454    ///
1455    /// This function is equivalent to the unary `+` operator (`+value`) in JavaScript
1456    ///
1457    /// See: <https://tc39.es/ecma262/#sec-tonumber>
1458    pub fn to_number(&self, context: &mut Context) -> JsResult<f64> {
1459        match self.variant() {
1460            JsVariant::Null => Ok(0.0),
1461            JsVariant::Undefined => Ok(f64::NAN),
1462            JsVariant::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
1463            JsVariant::String(string) => Ok(string.to_number()),
1464            JsVariant::Float64(number) => Ok(number),
1465            JsVariant::Integer32(integer) => Ok(f64::from(integer)),
1466            JsVariant::Symbol(_) => Err(JsNativeError::typ()
1467                .with_message("argument must not be a symbol")
1468                .into()),
1469            JsVariant::BigInt(_) => Err(JsNativeError::typ()
1470                .with_message("argument must not be a bigint")
1471                .into()),
1472            JsVariant::Object(_) => {
1473                let primitive = self.to_primitive(context, PreferredType::Number)?;
1474                primitive.to_number(context)
1475            }
1476        }
1477    }
1478
1479    /// Converts a value to a 16-bit floating point.
1480    #[cfg(feature = "float16")]
1481    pub fn to_f16(&self, context: &mut Context) -> JsResult<float16::f16> {
1482        self.to_number(context).map(float16::f16::from_f64)
1483    }
1484
1485    /// Converts a value to a 32 bit floating point.
1486    pub fn to_f32(&self, context: &mut Context) -> JsResult<f32> {
1487        self.to_number(context).map(|n| n as f32)
1488    }
1489
1490    /// This is a more specialized version of `to_numeric`, including `BigInt`.
1491    ///
1492    /// This function is equivalent to `Number(value)` in JavaScript
1493    ///
1494    /// See: <https://tc39.es/ecma262/#sec-tonumeric>
1495    pub fn to_numeric_number(&self, context: &mut Context) -> JsResult<f64> {
1496        let primitive = self.to_primitive(context, PreferredType::Number)?;
1497        if let Some(bigint) = primitive.as_bigint() {
1498            return Ok(bigint.to_f64());
1499        }
1500        primitive.to_number(context)
1501    }
1502
1503    /// Check if the `Value` can be converted to an `Object`
1504    ///
1505    /// The abstract operation `RequireObjectCoercible` takes argument argument.
1506    /// It throws an error if argument is a value that cannot be converted to an Object using `ToObject`.
1507    /// It is defined by [Table 15][table]
1508    ///
1509    /// More information:
1510    ///  - [ECMAScript reference][spec]
1511    ///
1512    /// [table]: https://tc39.es/ecma262/#table-14
1513    /// [spec]: https://tc39.es/ecma262/#sec-requireobjectcoercible
1514    ///
1515    /// # Examples
1516    ///
1517    /// ```
1518    /// use boa_engine::JsValue;
1519    ///
1520    /// // Most values are object-coercible.
1521    /// assert!(JsValue::new(42).require_object_coercible().is_ok());
1522    /// assert!(JsValue::new(true).require_object_coercible().is_ok());
1523    ///
1524    /// // null and undefined are not.
1525    /// assert!(JsValue::null().require_object_coercible().is_err());
1526    /// assert!(JsValue::undefined().require_object_coercible().is_err());
1527    /// ```
1528    #[inline]
1529    pub fn require_object_coercible(&self) -> JsResult<&Self> {
1530        if self.is_null_or_undefined() {
1531            Err(JsNativeError::typ()
1532                .with_message("cannot convert null or undefined to Object")
1533                .into())
1534        } else {
1535            Ok(self)
1536        }
1537    }
1538
1539    /// The abstract operation `ToPropertyDescriptor`.
1540    ///
1541    /// More information:
1542    /// - [ECMAScript reference][spec]
1543    ///
1544    /// [spec]: https://tc39.es/ecma262/#sec-topropertydescriptor
1545    #[inline]
1546    pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult<PropertyDescriptor> {
1547        // 1. If Type(Obj) is not Object, throw a TypeError exception.
1548        self.as_object()
1549            .ok_or_else(|| {
1550                JsNativeError::typ()
1551                    .with_message("Cannot construct a property descriptor from a non-object")
1552                    .into()
1553            })
1554            .and_then(|obj| obj.to_property_descriptor(context))
1555    }
1556
1557    /// `typeof` operator. Returns a string representing the type of the
1558    /// given ECMA Value.
1559    ///
1560    /// More information:
1561    /// - [ECMAScript reference][spec]
1562    ///
1563    /// [spec]: https://tc39.es/ecma262/#sec-typeof-operator
1564    ///
1565    /// # Examples
1566    ///
1567    /// ```
1568    /// use boa_engine::{JsValue, js_string, JsSymbol};
1569    ///
1570    /// assert_eq!(JsValue::undefined().type_of(), "undefined");
1571    /// assert_eq!(JsValue::null().type_of(), "object");
1572    /// assert_eq!(JsValue::new(true).type_of(), "boolean");
1573    /// assert_eq!(JsValue::new(42).type_of(), "number");
1574    /// assert_eq!(JsValue::new(js_string!("hi")).type_of(), "string");
1575    /// assert_eq!(JsValue::new(JsSymbol::new(None).unwrap()).type_of(), "symbol");
1576    /// ```
1577    #[must_use]
1578    pub fn type_of(&self) -> &'static str {
1579        self.variant().type_of()
1580    }
1581
1582    /// Same as [`JsValue::type_of`], but returning a [`JsString`] instead.
1583    ///
1584    /// # Examples
1585    ///
1586    /// ```
1587    /// use boa_engine::{JsValue, js_string};
1588    ///
1589    /// assert_eq!(JsValue::new(42).js_type_of(), js_string!("number"));
1590    /// assert_eq!(JsValue::new(true).js_type_of(), js_string!("boolean"));
1591    /// assert_eq!(JsValue::undefined().js_type_of(), js_string!("undefined"));
1592    /// ```
1593    #[must_use]
1594    pub fn js_type_of(&self) -> JsString {
1595        self.variant().js_type_of()
1596    }
1597
1598    /// Maps a `JsValue` into `Option<T>` where T is the result of an
1599    /// operation on a defined value. If the value is `JsValue::undefined`,
1600    /// then `JsValue::map` will return None.
1601    ///
1602    /// # Example
1603    ///
1604    /// ```
1605    /// use boa_engine::{Context, JsValue};
1606    ///
1607    /// let mut context = Context::default();
1608    ///
1609    /// let defined_value = JsValue::from(5);
1610    /// let undefined = JsValue::undefined();
1611    ///
1612    /// let defined_result = defined_value
1613    ///     .map(|v| v.add(&JsValue::from(5), &mut context))
1614    ///     .transpose()
1615    ///     .unwrap();
1616    /// let undefined_result = undefined
1617    ///     .map(|v| v.add(&JsValue::from(5), &mut context))
1618    ///     .transpose()
1619    ///     .unwrap();
1620    ///
1621    /// assert_eq!(defined_result, Some(JsValue::from(10u8)));
1622    /// assert_eq!(undefined_result, None);
1623    /// ```
1624    #[inline]
1625    #[must_use]
1626    pub fn map<T, F>(&self, f: F) -> Option<T>
1627    where
1628        F: FnOnce(&JsValue) -> T,
1629    {
1630        if self.is_undefined() {
1631            return None;
1632        }
1633        Some(f(self))
1634    }
1635
1636    /// Maps a `JsValue` into `T` where T is the result of an
1637    /// operation on a defined value. If the value is `JsValue::undefined`,
1638    /// then `JsValue::map` will return the provided default value.
1639    ///
1640    /// # Example
1641    ///
1642    /// ```
1643    /// use boa_engine::{Context, JsValue};
1644    ///
1645    /// let mut context = Context::default();
1646    ///
1647    /// let defined_value = JsValue::from(5);
1648    /// let undefined = JsValue::undefined();
1649    ///
1650    /// let defined_result = defined_value
1651    ///     .map_or(Ok(JsValue::new(true)), |v| {
1652    ///         v.add(&JsValue::from(5), &mut context)
1653    ///     })
1654    ///     .unwrap();
1655    /// let undefined_result = undefined
1656    ///     .map_or(Ok(JsValue::new(true)), |v| {
1657    ///         v.add(&JsValue::from(5), &mut context)
1658    ///     })
1659    ///     .unwrap();
1660    ///
1661    /// assert_eq!(defined_result, JsValue::new(10));
1662    /// assert_eq!(undefined_result, JsValue::new(true));
1663    /// ```
1664    #[inline]
1665    #[must_use]
1666    pub fn map_or<T, F>(&self, default: T, f: F) -> T
1667    where
1668        F: FnOnce(&JsValue) -> T,
1669    {
1670        if self.is_undefined() {
1671            return default;
1672        }
1673        f(self)
1674    }
1675
1676    /// Abstract operation `IsArray ( argument )`
1677    ///
1678    /// Check if a value is an array.
1679    ///
1680    /// More information:
1681    ///  - [ECMAScript reference][spec]
1682    ///
1683    /// [spec]: https://tc39.es/ecma262/#sec-isarray
1684    pub(crate) fn is_array(&self) -> JsResult<bool> {
1685        // Note: The spec specifies this function for JsValue.
1686        // The main part of the function is implemented for JsObject.
1687
1688        // 1. If Type(argument) is not Object, return false.
1689        self.as_object()
1690            .as_ref()
1691            .map_or(Ok(false), JsObject::is_array_abstract)
1692    }
1693}
1694
1695impl Default for JsValue {
1696    fn default() -> Self {
1697        Self::undefined()
1698    }
1699}
1700
1701/// The preferred type to convert an object to a primitive `Value`.
1702#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1703pub enum PreferredType {
1704    /// Prefer to convert to a `String` primitive.
1705    String,
1706
1707    /// Prefer to convert to a `Number` primitive.
1708    Number,
1709
1710    /// Do not prefer a type to convert to.
1711    Default,
1712}
1713
1714/// Numeric value which can be of two types `Number`, `BigInt`.
1715#[derive(Debug, Clone, PartialEq, PartialOrd)]
1716pub enum Numeric {
1717    /// Double precision floating point number.
1718    Number(f64),
1719    /// `BigInt` an integer of arbitrary size.
1720    BigInt(JsBigInt),
1721}
1722
1723impl From<f64> for Numeric {
1724    #[inline]
1725    fn from(value: f64) -> Self {
1726        Self::Number(value)
1727    }
1728}
1729
1730impl From<f32> for Numeric {
1731    #[inline]
1732    fn from(value: f32) -> Self {
1733        Self::Number(value.into())
1734    }
1735}
1736
1737impl From<i64> for Numeric {
1738    #[inline]
1739    fn from(value: i64) -> Self {
1740        Self::BigInt(value.into())
1741    }
1742}
1743
1744impl From<i32> for Numeric {
1745    #[inline]
1746    fn from(value: i32) -> Self {
1747        Self::Number(value.into())
1748    }
1749}
1750
1751impl From<i16> for Numeric {
1752    #[inline]
1753    fn from(value: i16) -> Self {
1754        Self::Number(value.into())
1755    }
1756}
1757
1758impl From<i8> for Numeric {
1759    #[inline]
1760    fn from(value: i8) -> Self {
1761        Self::Number(value.into())
1762    }
1763}
1764
1765impl From<u64> for Numeric {
1766    #[inline]
1767    fn from(value: u64) -> Self {
1768        Self::BigInt(value.into())
1769    }
1770}
1771
1772impl From<u32> for Numeric {
1773    #[inline]
1774    fn from(value: u32) -> Self {
1775        Self::Number(value.into())
1776    }
1777}
1778
1779impl From<u16> for Numeric {
1780    #[inline]
1781    fn from(value: u16) -> Self {
1782        Self::Number(value.into())
1783    }
1784}
1785
1786impl From<u8> for Numeric {
1787    #[inline]
1788    fn from(value: u8) -> Self {
1789        Self::Number(value.into())
1790    }
1791}
1792
1793impl From<JsBigInt> for Numeric {
1794    #[inline]
1795    fn from(value: JsBigInt) -> Self {
1796        Self::BigInt(value)
1797    }
1798}
1799
1800impl From<Numeric> for JsValue {
1801    fn from(value: Numeric) -> Self {
1802        match value {
1803            Numeric::Number(number) => Self::new(number),
1804            Numeric::BigInt(bigint) => Self::new(bigint),
1805        }
1806    }
1807}