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