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