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 std::{
6    collections::HashSet,
7    fmt::{self, Display},
8    ops::Sub,
9};
10
11use num_bigint::BigInt;
12use num_integer::Integer;
13use num_traits::{ToPrimitive, Zero};
14use once_cell::sync::Lazy;
15
16use boa_gc::{custom_trace, Finalize, Trace};
17#[doc(inline)]
18pub use boa_macros::TryFromJs;
19pub use boa_macros::TryIntoJs;
20use boa_profiler::Profiler;
21#[doc(inline)]
22pub use conversions::convert::Convert;
23
24pub(crate) use self::conversions::IntoOrUndefined;
25#[doc(inline)]
26pub use self::{
27    conversions::try_from_js::TryFromJs, conversions::try_into_js::TryIntoJs,
28    display::ValueDisplay, integer::IntegerOrInfinity, operations::*, r#type::Type,
29};
30use crate::builtins::RegExp;
31use crate::object::{JsFunction, JsPromise, JsRegExp};
32use crate::{
33    builtins::{
34        number::{f64_to_int32, f64_to_uint32},
35        Number, Promise,
36    },
37    error::JsNativeError,
38    js_string,
39    object::JsObject,
40    property::{PropertyDescriptor, PropertyKey},
41    symbol::JsSymbol,
42    Context, JsBigInt, JsResult, JsString,
43};
44
45mod conversions;
46pub(crate) mod display;
47mod equality;
48mod hash;
49mod integer;
50mod operations;
51mod r#type;
52
53#[cfg(test)]
54mod tests;
55
56static TWO_E_64: Lazy<BigInt> = Lazy::new(|| {
57    const TWO_E_64: u128 = 2u128.pow(64);
58    BigInt::from(TWO_E_64)
59});
60
61static TWO_E_63: Lazy<BigInt> = Lazy::new(|| {
62    const TWO_E_63: u128 = 2u128.pow(63);
63    BigInt::from(TWO_E_63)
64});
65
66/// A Javascript value
67#[derive(Finalize, Debug, Clone)]
68pub enum JsValue {
69    /// `null` - A null value, for when a value doesn't exist.
70    Null,
71    /// `undefined` - An undefined value, for when a field or index doesn't exist.
72    Undefined,
73    /// `boolean` - A `true` / `false` value, for if a certain criteria is met.
74    Boolean(bool),
75    /// `String` - A UTF-16 string, such as `"Hello, world"`.
76    String(JsString),
77    /// `Number` - A 64-bit floating point number, such as `3.1415`
78    Rational(f64),
79    /// `Number` - A 32-bit integer, such as `42`.
80    Integer(i32),
81    /// `BigInt` - holds any arbitrary large signed integer.
82    BigInt(JsBigInt),
83    /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values.
84    Object(JsObject),
85    /// `Symbol` - A Symbol Primitive type.
86    Symbol(JsSymbol),
87}
88
89unsafe impl Trace for JsValue {
90    custom_trace! {this, mark, {
91        if let Self::Object(o) = this {
92            mark(o);
93        }
94    }}
95}
96
97impl JsValue {
98    /// Create a new [`JsValue`].
99    pub fn new<T>(value: T) -> Self
100    where
101        T: Into<Self>,
102    {
103        value.into()
104    }
105
106    /// Creates a new `undefined` value.
107    #[inline]
108    #[must_use]
109    pub const fn undefined() -> Self {
110        Self::Undefined
111    }
112
113    /// Creates a new `null` value.
114    #[inline]
115    #[must_use]
116    pub const fn null() -> Self {
117        Self::Null
118    }
119
120    /// Creates a new number with `NaN` value.
121    #[inline]
122    #[must_use]
123    pub const fn nan() -> Self {
124        Self::Rational(f64::NAN)
125    }
126
127    /// Creates a new number with `Infinity` value.
128    #[inline]
129    #[must_use]
130    pub const fn positive_infinity() -> Self {
131        Self::Rational(f64::INFINITY)
132    }
133
134    /// Creates a new number with `-Infinity` value.
135    #[inline]
136    #[must_use]
137    pub const fn negative_infinity() -> Self {
138        Self::Rational(f64::NEG_INFINITY)
139    }
140
141    /// Returns true if the value is an object.
142    #[inline]
143    #[must_use]
144    pub const fn is_object(&self) -> bool {
145        matches!(self, Self::Object(_))
146    }
147
148    /// Returns the object if the value is object, otherwise `None`.
149    #[inline]
150    #[must_use]
151    pub const fn as_object(&self) -> Option<&JsObject> {
152        match *self {
153            Self::Object(ref o) => Some(o),
154            _ => None,
155        }
156    }
157
158    /// It determines if the value is a callable function with a `[[Call]]` internal method.
159    ///
160    /// More information:
161    /// - [ECMAScript reference][spec]
162    ///
163    /// [spec]: https://tc39.es/ecma262/#sec-iscallable
164    #[inline]
165    #[must_use]
166    pub fn is_callable(&self) -> bool {
167        matches!(self, Self::Object(obj) if obj.is_callable())
168    }
169
170    /// Returns the callable value if the value is callable, otherwise `None`.
171    #[inline]
172    #[must_use]
173    pub fn as_callable(&self) -> Option<&JsObject> {
174        self.as_object().filter(|obj| obj.is_callable())
175    }
176
177    /// Returns a [`JsFunction`] if the value is callable, otherwise `None`.
178    /// This is equivalent to `JsFunction::from_object(value.as_callable()?)`.
179    #[inline]
180    #[must_use]
181    pub fn as_function(&self) -> Option<JsFunction> {
182        self.as_callable()
183            .cloned()
184            .and_then(JsFunction::from_object)
185    }
186
187    /// Returns true if the value is a constructor object.
188    #[inline]
189    #[must_use]
190    pub fn is_constructor(&self) -> bool {
191        matches!(self, Self::Object(obj) if obj.is_constructor())
192    }
193
194    /// Returns the constructor if the value is a constructor, otherwise `None`.
195    #[inline]
196    #[must_use]
197    pub fn as_constructor(&self) -> Option<&JsObject> {
198        self.as_object().filter(|obj| obj.is_constructor())
199    }
200
201    /// Returns true if the value is a promise object.
202    #[inline]
203    #[must_use]
204    pub fn is_promise(&self) -> bool {
205        matches!(self, Self::Object(obj) if obj.is::<Promise>())
206    }
207
208    /// Returns the value as an object if the value is a promise, otherwise `None`.
209    #[inline]
210    #[must_use]
211    pub(crate) fn as_promise_object(&self) -> Option<&JsObject> {
212        self.as_object().filter(|obj| obj.is::<Promise>())
213    }
214
215    /// Returns the value as a promise if the value is a promise, otherwise `None`.
216    #[inline]
217    #[must_use]
218    pub fn as_promise(&self) -> Option<JsPromise> {
219        self.as_promise_object()
220            .cloned()
221            .and_then(|o| JsPromise::from_object(o).ok())
222    }
223
224    /// Returns true if the value is a regular expression object.
225    #[inline]
226    #[must_use]
227    pub fn is_regexp(&self) -> bool {
228        matches!(self, Self::Object(obj) if obj.is::<RegExp>())
229    }
230
231    /// Returns the value as a regular expression if the value is a regexp, otherwise `None`.
232    #[inline]
233    #[must_use]
234    pub fn as_regexp(&self) -> Option<JsRegExp> {
235        self.as_object()
236            .filter(|obj| obj.is::<RegExp>())
237            .cloned()
238            .and_then(|o| JsRegExp::from_object(o).ok())
239    }
240
241    /// Returns true if the value is a symbol.
242    #[inline]
243    #[must_use]
244    pub const fn is_symbol(&self) -> bool {
245        matches!(self, Self::Symbol(_))
246    }
247
248    /// Returns the symbol if the value is a symbol, otherwise `None`.
249    #[inline]
250    #[must_use]
251    pub fn as_symbol(&self) -> Option<JsSymbol> {
252        match self {
253            Self::Symbol(symbol) => Some(symbol.clone()),
254            _ => None,
255        }
256    }
257
258    /// Returns true if the value is undefined.
259    #[inline]
260    #[must_use]
261    pub const fn is_undefined(&self) -> bool {
262        matches!(self, Self::Undefined)
263    }
264
265    /// Returns true if the value is null.
266    #[inline]
267    #[must_use]
268    pub const fn is_null(&self) -> bool {
269        matches!(self, Self::Null)
270    }
271
272    /// Returns true if the value is null or undefined.
273    #[inline]
274    #[must_use]
275    pub const fn is_null_or_undefined(&self) -> bool {
276        matches!(self, Self::Null | Self::Undefined)
277    }
278
279    /// Returns true if the value is a 64-bit floating-point number.
280    #[inline]
281    #[must_use]
282    pub const fn is_double(&self) -> bool {
283        matches!(self, Self::Rational(_))
284    }
285
286    /// Determines if argument is a finite integral Number value.
287    ///
288    /// More information:
289    /// - [ECMAScript reference][spec]
290    ///
291    /// [spec]: https://tc39.es/ecma262/#sec-isintegralnumber
292    #[must_use]
293    #[allow(clippy::float_cmp)]
294    pub fn is_integral_number(&self) -> bool {
295        // If it can fit in a i32 and the truncated version is
296        // equal to the original then it is an integer.
297        let is_rational_integer = |n: f64| n == f64::from(n as i32);
298
299        match *self {
300            Self::Integer(_) => true,
301            Self::Rational(n) if is_rational_integer(n) => true,
302            _ => false,
303        }
304    }
305
306    /// Returns true if the value can be reprented as an integer.
307    ///
308    /// Similar to [`JsValue::is_integral_number()`] except that it returns `false` for `-0`.
309    #[must_use]
310    #[allow(clippy::float_cmp)]
311    pub fn is_integer(&self) -> bool {
312        // If it can fit in a i32 and the truncated version is
313        // equal to the original then it is an integer.
314        let is_rational_integer = |n: f64| n.to_bits() == f64::from(n as i32).to_bits();
315
316        match *self {
317            Self::Integer(_) => true,
318            Self::Rational(n) if is_rational_integer(n) => true,
319            _ => false,
320        }
321    }
322
323    /// Returns true if the value is a number.
324    #[inline]
325    #[must_use]
326    pub const fn is_number(&self) -> bool {
327        matches!(self, Self::Rational(_) | Self::Integer(_))
328    }
329
330    /// Returns the number if the value is a number, otherwise `None`.
331    #[inline]
332    #[must_use]
333    pub fn as_number(&self) -> Option<f64> {
334        match *self {
335            Self::Integer(integer) => Some(integer.into()),
336            Self::Rational(rational) => Some(rational),
337            _ => None,
338        }
339    }
340
341    /// Returns true if the value is a string.
342    #[inline]
343    #[must_use]
344    pub const fn is_string(&self) -> bool {
345        matches!(self, Self::String(_))
346    }
347
348    /// Returns the string if the value is a string, otherwise `None`.
349    #[inline]
350    #[must_use]
351    pub const fn as_string(&self) -> Option<&JsString> {
352        match self {
353            Self::String(ref string) => Some(string),
354            _ => None,
355        }
356    }
357
358    /// Returns true if the value is a boolean.
359    #[inline]
360    #[must_use]
361    pub const fn is_boolean(&self) -> bool {
362        matches!(self, Self::Boolean(_))
363    }
364
365    /// Returns the boolean if the value is a boolean, otherwise `None`.
366    #[inline]
367    #[must_use]
368    pub const fn as_boolean(&self) -> Option<bool> {
369        match self {
370            Self::Boolean(boolean) => Some(*boolean),
371            _ => None,
372        }
373    }
374
375    /// Returns true if the value is a bigint.
376    #[inline]
377    #[must_use]
378    pub const fn is_bigint(&self) -> bool {
379        matches!(self, Self::BigInt(_))
380    }
381
382    /// Returns an optional reference to a `BigInt` if the value is a `BigInt` primitive.
383    #[inline]
384    #[must_use]
385    pub const fn as_bigint(&self) -> Option<&JsBigInt> {
386        match self {
387            Self::BigInt(bigint) => Some(bigint),
388            _ => None,
389        }
390    }
391
392    /// Converts the value to a `bool` type.
393    ///
394    /// More information:
395    ///  - [ECMAScript][spec]
396    ///
397    /// [spec]: https://tc39.es/ecma262/#sec-toboolean
398    #[must_use]
399    pub fn to_boolean(&self) -> bool {
400        match *self {
401            Self::Symbol(_) | Self::Object(_) => true,
402            Self::String(ref s) if !s.is_empty() => true,
403            Self::Rational(n) if n != 0.0 && !n.is_nan() => true,
404            Self::Integer(n) if n != 0 => true,
405            Self::BigInt(ref n) if !n.is_zero() => true,
406            Self::Boolean(v) => v,
407            _ => false,
408        }
409    }
410
411    /// The abstract operation `ToPrimitive` takes an input argument and an optional argument
412    /// `PreferredType`.
413    ///
414    /// <https://tc39.es/ecma262/#sec-toprimitive>
415    pub fn to_primitive(
416        &self,
417        context: &mut Context,
418        preferred_type: PreferredType,
419    ) -> JsResult<Self> {
420        // 1. Assert: input is an ECMAScript language value. (always a value not need to check)
421        // 2. If Type(input) is Object, then
422        if let Some(input) = self.as_object() {
423            // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
424            let exotic_to_prim = input.get_method(JsSymbol::to_primitive(), context)?;
425
426            // b. If exoticToPrim is not undefined, then
427            if let Some(exotic_to_prim) = exotic_to_prim {
428                // i. If preferredType is not present, let hint be "default".
429                // ii. Else if preferredType is string, let hint be "string".
430                // iii. Else,
431                //     1. Assert: preferredType is number.
432                //     2. Let hint be "number".
433                let hint = match preferred_type {
434                    PreferredType::Default => js_string!("default"),
435                    PreferredType::String => js_string!("string"),
436                    PreferredType::Number => js_string!("number"),
437                }
438                .into();
439
440                // iv. Let result be ? Call(exoticToPrim, input, « hint »).
441                let result = exotic_to_prim.call(self, &[hint], context)?;
442                // v. If Type(result) is not Object, return result.
443                // vi. Throw a TypeError exception.
444                return if result.is_object() {
445                    Err(JsNativeError::typ()
446                        .with_message("Symbol.toPrimitive cannot return an object")
447                        .into())
448                } else {
449                    Ok(result)
450                };
451            }
452
453            // c. If preferredType is not present, let preferredType be number.
454            let preferred_type = match preferred_type {
455                PreferredType::Default | PreferredType::Number => PreferredType::Number,
456                PreferredType::String => PreferredType::String,
457            };
458
459            // d. Return ? OrdinaryToPrimitive(input, preferredType).
460            return input.ordinary_to_primitive(context, preferred_type);
461        }
462
463        // 3. Return input.
464        Ok(self.clone())
465    }
466
467    /// `7.1.13 ToBigInt ( argument )`
468    ///
469    /// More information:
470    ///  - [ECMAScript reference][spec]
471    ///
472    /// [spec]: https://tc39.es/ecma262/#sec-tobigint
473    pub fn to_bigint(&self, context: &mut Context) -> JsResult<JsBigInt> {
474        match self {
475            Self::Null => Err(JsNativeError::typ()
476                .with_message("cannot convert null to a BigInt")
477                .into()),
478            Self::Undefined => Err(JsNativeError::typ()
479                .with_message("cannot convert undefined to a BigInt")
480                .into()),
481            Self::String(ref string) => JsBigInt::from_js_string(string).map_or_else(
482                || {
483                    Err(JsNativeError::syntax()
484                        .with_message(format!(
485                            "cannot convert string '{}' to bigint primitive",
486                            string.to_std_string_escaped()
487                        ))
488                        .into())
489                },
490                Ok,
491            ),
492            Self::Boolean(true) => Ok(JsBigInt::one()),
493            Self::Boolean(false) => Ok(JsBigInt::zero()),
494            Self::Integer(_) | Self::Rational(_) => Err(JsNativeError::typ()
495                .with_message("cannot convert Number to a BigInt")
496                .into()),
497            Self::BigInt(b) => Ok(b.clone()),
498            Self::Object(_) => {
499                let primitive = self.to_primitive(context, PreferredType::Number)?;
500                primitive.to_bigint(context)
501            }
502            Self::Symbol(_) => Err(JsNativeError::typ()
503                .with_message("cannot convert Symbol to a BigInt")
504                .into()),
505        }
506    }
507
508    /// Returns an object that implements `Display`.
509    ///
510    /// By default the internals are not shown, but they can be toggled
511    /// with [`ValueDisplay::internals`] method.
512    ///
513    /// # Examples
514    ///
515    /// ```
516    /// use boa_engine::JsValue;
517    ///
518    /// let value = JsValue::new(3);
519    ///
520    /// println!("{}", value.display());
521    /// ```
522    #[must_use]
523    #[inline]
524    pub const fn display(&self) -> ValueDisplay<'_> {
525        ValueDisplay {
526            value: self,
527            internals: false,
528        }
529    }
530
531    /// Converts the value to a string.
532    ///
533    /// This function is equivalent to `String(value)` in JavaScript.
534    pub fn to_string(&self, context: &mut Context) -> JsResult<JsString> {
535        match self {
536            Self::Null => Ok(js_string!("null")),
537            Self::Undefined => Ok(js_string!("undefined")),
538            Self::Boolean(boolean) => Ok(if *boolean {
539                js_string!("true")
540            } else {
541                js_string!("false")
542            }),
543            Self::Rational(rational) => Ok(Number::to_js_string(*rational)),
544            Self::Integer(integer) => Ok(integer.to_string().into()),
545            Self::String(string) => Ok(string.clone()),
546            Self::Symbol(_) => Err(JsNativeError::typ()
547                .with_message("can't convert symbol to string")
548                .into()),
549            Self::BigInt(ref bigint) => Ok(bigint.to_string().into()),
550            Self::Object(_) => {
551                let primitive = self.to_primitive(context, PreferredType::String)?;
552                primitive.to_string(context)
553            }
554        }
555    }
556
557    /// Converts the value to an Object.
558    ///
559    /// This function is equivalent to `Object(value)` in JavaScript.
560    ///
561    /// See: <https://tc39.es/ecma262/#sec-toobject>
562    pub fn to_object(&self, context: &mut Context) -> JsResult<JsObject> {
563        match self {
564            Self::Undefined | Self::Null => Err(JsNativeError::typ()
565                .with_message("cannot convert 'null' or 'undefined' to object")
566                .into()),
567            Self::Boolean(boolean) => Ok(context
568                .intrinsics()
569                .templates()
570                .boolean()
571                .create(*boolean, Vec::default())),
572            Self::Integer(integer) => Ok(context
573                .intrinsics()
574                .templates()
575                .number()
576                .create(f64::from(*integer), Vec::default())),
577            Self::Rational(rational) => Ok(context
578                .intrinsics()
579                .templates()
580                .number()
581                .create(*rational, Vec::default())),
582            Self::String(ref string) => Ok(context
583                .intrinsics()
584                .templates()
585                .string()
586                .create(string.clone(), vec![string.len().into()])),
587            Self::Symbol(ref symbol) => Ok(context
588                .intrinsics()
589                .templates()
590                .symbol()
591                .create(symbol.clone(), Vec::default())),
592            Self::BigInt(ref bigint) => Ok(context
593                .intrinsics()
594                .templates()
595                .bigint()
596                .create(bigint.clone(), Vec::default())),
597            Self::Object(jsobject) => Ok(jsobject.clone()),
598        }
599    }
600
601    /// Converts the value to a `PropertyKey`, that can be used as a key for properties.
602    ///
603    /// See <https://tc39.es/ecma262/#sec-topropertykey>
604    pub fn to_property_key(&self, context: &mut Context) -> JsResult<PropertyKey> {
605        Ok(match self {
606            // Fast path:
607            Self::String(string) => string.clone().into(),
608            Self::Symbol(symbol) => symbol.clone().into(),
609            Self::Integer(integer) => (*integer).into(),
610            // Slow path:
611            Self::Object(_) => match self.to_primitive(context, PreferredType::String)? {
612                Self::String(ref string) => string.clone().into(),
613                Self::Symbol(ref symbol) => symbol.clone().into(),
614                Self::Integer(integer) => integer.into(),
615                primitive => primitive.to_string(context)?.into(),
616            },
617            primitive => primitive.to_string(context)?.into(),
618        })
619    }
620
621    /// It returns value converted to a numeric value of type `Number` or `BigInt`.
622    ///
623    /// See: <https://tc39.es/ecma262/#sec-tonumeric>
624    pub fn to_numeric(&self, context: &mut Context) -> JsResult<Numeric> {
625        // 1. Let primValue be ? ToPrimitive(value, number).
626        let primitive = self.to_primitive(context, PreferredType::Number)?;
627
628        // 2. If primValue is a BigInt, return primValue.
629        if let Some(bigint) = primitive.as_bigint() {
630            return Ok(bigint.clone().into());
631        }
632
633        // 3. Return ? ToNumber(primValue).
634        Ok(primitive.to_number(context)?.into())
635    }
636
637    /// Converts a value to an integral 32 bit unsigned integer.
638    ///
639    /// This function is equivalent to `value | 0` in JavaScript
640    ///
641    /// See: <https://tc39.es/ecma262/#sec-touint32>
642    pub fn to_u32(&self, context: &mut Context) -> JsResult<u32> {
643        // This is the fast path, if the value is Integer we can just return it.
644        if let Self::Integer(number) = *self {
645            if let Ok(number) = u32::try_from(number) {
646                return Ok(number);
647            }
648        }
649        let number = self.to_number(context)?;
650
651        Ok(f64_to_uint32(number))
652    }
653
654    /// Converts a value to an integral 32 bit signed integer.
655    ///
656    /// See: <https://tc39.es/ecma262/#sec-toint32>
657    pub fn to_i32(&self, context: &mut Context) -> JsResult<i32> {
658        // This is the fast path, if the value is Integer we can just return it.
659        if let Self::Integer(number) = *self {
660            return Ok(number);
661        }
662        let number = self.to_number(context)?;
663
664        Ok(f64_to_int32(number))
665    }
666
667    /// `7.1.10 ToInt8 ( argument )`
668    ///
669    /// More information:
670    ///  - [ECMAScript reference][spec]
671    ///
672    /// [spec]: https://tc39.es/ecma262/#sec-toint8
673    pub fn to_int8(&self, context: &mut Context) -> JsResult<i8> {
674        // 1. Let number be ? ToNumber(argument).
675        let number = self.to_number(context)?;
676
677        // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
678        if number.is_nan() || number.is_zero() || number.is_infinite() {
679            return Ok(0);
680        }
681
682        // 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
683        let int = number.abs().floor().copysign(number) as i64;
684
685        // 4. Let int8bit be int modulo 2^8.
686        let int_8_bit = int % 2i64.pow(8);
687
688        // 5. If int8bit ≥ 2^7, return 𝔽(int8bit - 2^8); otherwise return 𝔽(int8bit).
689        if int_8_bit >= 2i64.pow(7) {
690            Ok((int_8_bit - 2i64.pow(8)) as i8)
691        } else {
692            Ok(int_8_bit as i8)
693        }
694    }
695
696    /// `7.1.11 ToUint8 ( argument )`
697    ///
698    /// More information:
699    ///  - [ECMAScript reference][spec]
700    ///
701    /// [spec]: https://tc39.es/ecma262/#sec-touint8
702    pub fn to_uint8(&self, context: &mut Context) -> JsResult<u8> {
703        // 1. Let number be ? ToNumber(argument).
704        let number = self.to_number(context)?;
705
706        // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
707        if number.is_nan() || number.is_zero() || number.is_infinite() {
708            return Ok(0);
709        }
710
711        // 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
712        let int = number.abs().floor().copysign(number) as i64;
713
714        // 4. Let int8bit be int modulo 2^8.
715        let int_8_bit = int % 2i64.pow(8);
716
717        // 5. Return 𝔽(int8bit).
718        Ok(int_8_bit as u8)
719    }
720
721    /// `7.1.12 ToUint8Clamp ( argument )`
722    ///
723    /// More information:
724    ///  - [ECMAScript reference][spec]
725    ///
726    /// [spec]: https://tc39.es/ecma262/#sec-touint8clamp
727    pub fn to_uint8_clamp(&self, context: &mut Context) -> JsResult<u8> {
728        // 1. Let number be ? ToNumber(argument).
729        let number = self.to_number(context)?;
730
731        // 2. If number is NaN, return +0𝔽.
732        if number.is_nan() {
733            return Ok(0);
734        }
735
736        // 3. If ℝ(number) ≤ 0, return +0𝔽.
737        if number <= 0.0 {
738            return Ok(0);
739        }
740
741        // 4. If ℝ(number) ≥ 255, return 255𝔽.
742        if number >= 255.0 {
743            return Ok(255);
744        }
745
746        // 5. Let f be floor(ℝ(number)).
747        let f = number.floor();
748
749        // 6. If f + 0.5 < ℝ(number), return 𝔽(f + 1).
750        if f + 0.5 < number {
751            return Ok(f as u8 + 1);
752        }
753
754        // 7. If ℝ(number) < f + 0.5, return 𝔽(f).
755        if number < f + 0.5 {
756            return Ok(f as u8);
757        }
758
759        // 8. If f is odd, return 𝔽(f + 1).
760        if f as u8 % 2 != 0 {
761            return Ok(f as u8 + 1);
762        }
763
764        // 9. Return 𝔽(f).
765        Ok(f as u8)
766    }
767
768    /// `7.1.8 ToInt16 ( argument )`
769    ///
770    /// More information:
771    ///  - [ECMAScript reference][spec]
772    ///
773    /// [spec]: https://tc39.es/ecma262/#sec-toint16
774    pub fn to_int16(&self, context: &mut Context) -> JsResult<i16> {
775        // 1. Let number be ? ToNumber(argument).
776        let number = self.to_number(context)?;
777
778        // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
779        if number.is_nan() || number.is_zero() || number.is_infinite() {
780            return Ok(0);
781        }
782
783        // 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
784        let int = number.abs().floor().copysign(number) as i64;
785
786        // 4. Let int16bit be int modulo 2^16.
787        let int_16_bit = int % 2i64.pow(16);
788
789        // 5. If int16bit ≥ 2^15, return 𝔽(int16bit - 2^16); otherwise return 𝔽(int16bit).
790        if int_16_bit >= 2i64.pow(15) {
791            Ok((int_16_bit - 2i64.pow(16)) as i16)
792        } else {
793            Ok(int_16_bit as i16)
794        }
795    }
796
797    /// `7.1.9 ToUint16 ( argument )`
798    ///
799    /// More information:
800    ///  - [ECMAScript reference][spec]
801    ///
802    /// [spec]: https://tc39.es/ecma262/#sec-touint16
803    pub fn to_uint16(&self, context: &mut Context) -> JsResult<u16> {
804        // 1. Let number be ? ToNumber(argument).
805        let number = self.to_number(context)?;
806
807        // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
808        if number.is_nan() || number.is_zero() || number.is_infinite() {
809            return Ok(0);
810        }
811
812        // 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
813        let int = number.abs().floor().copysign(number) as i64;
814
815        // 4. Let int16bit be int modulo 2^16.
816        let int_16_bit = int % 2i64.pow(16);
817
818        // 5. Return 𝔽(int16bit).
819        Ok(int_16_bit as u16)
820    }
821
822    /// `7.1.15 ToBigInt64 ( argument )`
823    ///
824    /// More information:
825    ///  - [ECMAScript reference][spec]
826    ///
827    /// [spec]: https://tc39.es/ecma262/#sec-tobigint64
828    pub fn to_big_int64(&self, context: &mut Context) -> JsResult<i64> {
829        // 1. Let n be ? ToBigInt(argument).
830        let n = self.to_bigint(context)?;
831
832        // 2. Let int64bit be ℝ(n) modulo 2^64.
833        let int64_bit = n.as_inner().mod_floor(&TWO_E_64);
834
835        // 3. If int64bit ≥ 2^63, return ℤ(int64bit - 2^64); otherwise return ℤ(int64bit).
836        let value = if int64_bit >= *TWO_E_63 {
837            int64_bit.sub(&*TWO_E_64)
838        } else {
839            int64_bit
840        };
841
842        Ok(value
843            .to_i64()
844            .expect("should be within the range of `i64` by the mod operation"))
845    }
846
847    /// `7.1.16 ToBigUint64 ( argument )`
848    ///
849    /// More information:
850    ///  - [ECMAScript reference][spec]
851    ///
852    /// [spec]: https://tc39.es/ecma262/#sec-tobiguint64
853    pub fn to_big_uint64(&self, context: &mut Context) -> JsResult<u64> {
854        // 1. Let n be ? ToBigInt(argument).
855        let n = self.to_bigint(context)?;
856
857        // 2. Let int64bit be ℝ(n) modulo 2^64.
858        // 3. Return ℤ(int64bit).
859        Ok(n.as_inner()
860            .mod_floor(&TWO_E_64)
861            .to_u64()
862            .expect("should be within the range of `u64` by the mod operation"))
863    }
864
865    /// Converts a value to a non-negative integer if it is a valid integer index value.
866    ///
867    /// See: <https://tc39.es/ecma262/#sec-toindex>
868    pub fn to_index(&self, context: &mut Context) -> JsResult<u64> {
869        // 1. If value is undefined, then
870        if self.is_undefined() {
871            // a. Return 0.
872            return Ok(0);
873        }
874
875        // 2. Else,
876        // a. Let integer be ? ToIntegerOrInfinity(value).
877        let integer = self.to_integer_or_infinity(context)?;
878
879        // b. Let clamped be ! ToLength(𝔽(integer)).
880        let clamped = integer.clamp_finite(0, Number::MAX_SAFE_INTEGER as i64);
881
882        // c. If ! SameValue(𝔽(integer), clamped) is false, throw a RangeError exception.
883        if integer != clamped {
884            return Err(JsNativeError::range()
885                .with_message("Index must be between 0 and  2^53 - 1")
886                .into());
887        }
888
889        // d. Assert: 0 ≤ integer ≤ 2^53 - 1.
890        debug_assert!(0 <= clamped && clamped <= Number::MAX_SAFE_INTEGER as i64);
891
892        // e. Return integer.
893        Ok(clamped as u64)
894    }
895
896    /// Converts argument to an integer suitable for use as the length of an array-like object.
897    ///
898    /// See: <https://tc39.es/ecma262/#sec-tolength>
899    pub fn to_length(&self, context: &mut Context) -> JsResult<u64> {
900        // 1. Let len be ? ToInteger(argument).
901        // 2. If len ≤ +0, return +0.
902        // 3. Return min(len, 2^53 - 1).
903        Ok(self
904            .to_integer_or_infinity(context)?
905            .clamp_finite(0, Number::MAX_SAFE_INTEGER as i64) as u64)
906    }
907
908    /// Abstract operation `ToIntegerOrInfinity ( argument )`
909    ///
910    /// This method converts a `Value` to an integer representing its `Number` value with
911    /// fractional part truncated, or to +∞ or -∞ when that `Number` value is infinite.
912    ///
913    /// More information:
914    /// - [ECMAScript reference][spec]
915    ///
916    /// [spec]: https://tc39.es/ecma262/#sec-tointegerorinfinity
917    pub fn to_integer_or_infinity(&self, context: &mut Context) -> JsResult<IntegerOrInfinity> {
918        // 1. Let number be ? ToNumber(argument).
919        let number = self.to_number(context)?;
920
921        // Continues on `IntegerOrInfinity::from::<f64>`
922        Ok(IntegerOrInfinity::from(number))
923    }
924
925    /// Converts a value to a double precision floating point.
926    ///
927    /// This function is equivalent to the unary `+` operator (`+value`) in JavaScript
928    ///
929    /// See: <https://tc39.es/ecma262/#sec-tonumber>
930    pub fn to_number(&self, context: &mut Context) -> JsResult<f64> {
931        match *self {
932            Self::Null => Ok(0.0),
933            Self::Undefined => Ok(f64::NAN),
934            Self::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
935            Self::String(ref string) => Ok(string.to_number()),
936            Self::Rational(number) => Ok(number),
937            Self::Integer(integer) => Ok(f64::from(integer)),
938            Self::Symbol(_) => Err(JsNativeError::typ()
939                .with_message("argument must not be a symbol")
940                .into()),
941            Self::BigInt(_) => Err(JsNativeError::typ()
942                .with_message("argument must not be a bigint")
943                .into()),
944            Self::Object(_) => {
945                let primitive = self.to_primitive(context, PreferredType::Number)?;
946                primitive.to_number(context)
947            }
948        }
949    }
950
951    /// Converts a value to a 32 bit floating point.
952    pub fn to_f32(&self, context: &mut Context) -> JsResult<f32> {
953        self.to_number(context).map(|n| n as f32)
954    }
955
956    /// This is a more specialized version of `to_numeric`, including `BigInt`.
957    ///
958    /// This function is equivalent to `Number(value)` in JavaScript
959    ///
960    /// See: <https://tc39.es/ecma262/#sec-tonumeric>
961    pub fn to_numeric_number(&self, context: &mut Context) -> JsResult<f64> {
962        let primitive = self.to_primitive(context, PreferredType::Number)?;
963        if let Some(bigint) = primitive.as_bigint() {
964            return Ok(bigint.to_f64());
965        }
966        primitive.to_number(context)
967    }
968
969    /// Check if the `Value` can be converted to an `Object`
970    ///
971    /// The abstract operation `RequireObjectCoercible` takes argument argument.
972    /// It throws an error if argument is a value that cannot be converted to an Object using `ToObject`.
973    /// It is defined by [Table 15][table]
974    ///
975    /// More information:
976    ///  - [ECMAScript reference][spec]
977    ///
978    /// [table]: https://tc39.es/ecma262/#table-14
979    /// [spec]: https://tc39.es/ecma262/#sec-requireobjectcoercible
980    #[inline]
981    pub fn require_object_coercible(&self) -> JsResult<&Self> {
982        if self.is_null_or_undefined() {
983            Err(JsNativeError::typ()
984                .with_message("cannot convert null or undefined to Object")
985                .into())
986        } else {
987            Ok(self)
988        }
989    }
990
991    /// The abstract operation `ToPropertyDescriptor`.
992    ///
993    /// More information:
994    /// - [ECMAScript reference][spec]
995    ///
996    /// [spec]: https://tc39.es/ecma262/#sec-topropertydescriptor
997    #[inline]
998    pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult<PropertyDescriptor> {
999        // 1. If Type(Obj) is not Object, throw a TypeError exception.
1000        self.as_object()
1001            .ok_or_else(|| {
1002                JsNativeError::typ()
1003                    .with_message("Cannot construct a property descriptor from a non-object")
1004                    .into()
1005            })
1006            .and_then(|obj| obj.to_property_descriptor(context))
1007    }
1008
1009    /// `typeof` operator. Returns a string representing the type of the
1010    /// given ECMA Value.
1011    ///
1012    /// More information:
1013    /// - [ECMAScript reference][spec]
1014    ///
1015    /// [spec]: https://tc39.es/ecma262/#sec-typeof-operator
1016    #[must_use]
1017    pub fn type_of(&self) -> &'static str {
1018        match *self {
1019            Self::Rational(_) | Self::Integer(_) => "number",
1020            Self::String(_) => "string",
1021            Self::Boolean(_) => "boolean",
1022            Self::Symbol(_) => "symbol",
1023            Self::Null => "object",
1024            Self::Undefined => "undefined",
1025            Self::BigInt(_) => "bigint",
1026            Self::Object(ref object) => {
1027                if object.is_callable() {
1028                    "function"
1029                } else {
1030                    "object"
1031                }
1032            }
1033        }
1034    }
1035
1036    /// Same as [`JsValue::type_of`], but returning a [`JsString`] instead.
1037    #[must_use]
1038    pub fn js_type_of(&self) -> JsString {
1039        match *self {
1040            Self::Rational(_) | Self::Integer(_) => js_string!("number"),
1041            Self::String(_) => js_string!("string"),
1042            Self::Boolean(_) => js_string!("boolean"),
1043            Self::Symbol(_) => js_string!("symbol"),
1044            Self::Null => js_string!("object"),
1045            Self::Undefined => js_string!("undefined"),
1046            Self::BigInt(_) => js_string!("bigint"),
1047            Self::Object(ref object) => {
1048                if object.is_callable() {
1049                    js_string!("function")
1050                } else {
1051                    js_string!("object")
1052                }
1053            }
1054        }
1055    }
1056
1057    /// Maps a `JsValue` into a `Option<T>` where T is the result of an
1058    /// operation on a defined value. If the value is `JsValue::undefined`,
1059    /// then `JsValue::map` will return None.
1060    ///
1061    /// # Example
1062    ///
1063    /// ```
1064    /// use boa_engine::{JsValue, Context};
1065    ///
1066    /// let mut context = Context::default();
1067    ///
1068    /// let defined_value = JsValue::from(5);
1069    /// let undefined = JsValue::undefined();
1070    ///
1071    /// let defined_result = defined_value.map(|v| v.add(&JsValue::from(5), &mut context)).transpose().unwrap();
1072    /// let undefined_result = undefined.map(|v| v.add(&JsValue::from(5), &mut context)).transpose().unwrap();
1073    ///
1074    /// assert_eq!(defined_result, Some(JsValue::Integer(10)));
1075    /// assert_eq!(undefined_result, None);
1076    ///
1077    /// ```
1078    ///
1079    #[inline]
1080    #[must_use]
1081    pub fn map<T, F>(&self, f: F) -> Option<T>
1082    where
1083        F: FnOnce(&JsValue) -> T,
1084    {
1085        if self.is_undefined() {
1086            return None;
1087        }
1088        Some(f(self))
1089    }
1090
1091    /// Abstract operation `IsArray ( argument )`
1092    ///
1093    /// Check if a value is an array.
1094    ///
1095    /// More information:
1096    ///  - [ECMAScript reference][spec]
1097    ///
1098    /// [spec]: https://tc39.es/ecma262/#sec-isarray
1099    pub(crate) fn is_array(&self) -> JsResult<bool> {
1100        // Note: The spec specifies this function for JsValue.
1101        // The main part of the function is implemented for JsObject.
1102
1103        // 1. If Type(argument) is not Object, return false.
1104        self.as_object()
1105            .map_or(Ok(false), JsObject::is_array_abstract)
1106    }
1107}
1108
1109impl Default for JsValue {
1110    fn default() -> Self {
1111        Self::Undefined
1112    }
1113}
1114
1115/// The preferred type to convert an object to a primitive `Value`.
1116#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1117pub enum PreferredType {
1118    /// Prefer to convert to a `String` primitive.
1119    String,
1120
1121    /// Prefer to convert to a `Number` primitive.
1122    Number,
1123
1124    /// Do not prefer a type to convert to.
1125    Default,
1126}
1127
1128/// Numeric value which can be of two types `Number`, `BigInt`.
1129#[derive(Debug, Clone, PartialEq, PartialOrd)]
1130pub enum Numeric {
1131    /// Double precision floating point number.
1132    Number(f64),
1133    /// `BigInt` an integer of arbitrary size.
1134    BigInt(JsBigInt),
1135}
1136
1137impl From<f64> for Numeric {
1138    #[inline]
1139    fn from(value: f64) -> Self {
1140        Self::Number(value)
1141    }
1142}
1143
1144impl From<f32> for Numeric {
1145    #[inline]
1146    fn from(value: f32) -> Self {
1147        Self::Number(value.into())
1148    }
1149}
1150
1151impl From<i64> for Numeric {
1152    #[inline]
1153    fn from(value: i64) -> Self {
1154        Self::BigInt(value.into())
1155    }
1156}
1157
1158impl From<i32> for Numeric {
1159    #[inline]
1160    fn from(value: i32) -> Self {
1161        Self::Number(value.into())
1162    }
1163}
1164
1165impl From<i16> for Numeric {
1166    #[inline]
1167    fn from(value: i16) -> Self {
1168        Self::Number(value.into())
1169    }
1170}
1171
1172impl From<i8> for Numeric {
1173    #[inline]
1174    fn from(value: i8) -> Self {
1175        Self::Number(value.into())
1176    }
1177}
1178
1179impl From<u64> for Numeric {
1180    #[inline]
1181    fn from(value: u64) -> Self {
1182        Self::BigInt(value.into())
1183    }
1184}
1185
1186impl From<u32> for Numeric {
1187    #[inline]
1188    fn from(value: u32) -> Self {
1189        Self::Number(value.into())
1190    }
1191}
1192
1193impl From<u16> for Numeric {
1194    #[inline]
1195    fn from(value: u16) -> Self {
1196        Self::Number(value.into())
1197    }
1198}
1199
1200impl From<u8> for Numeric {
1201    #[inline]
1202    fn from(value: u8) -> Self {
1203        Self::Number(value.into())
1204    }
1205}
1206
1207impl From<JsBigInt> for Numeric {
1208    #[inline]
1209    fn from(value: JsBigInt) -> Self {
1210        Self::BigInt(value)
1211    }
1212}
1213
1214impl From<Numeric> for JsValue {
1215    fn from(value: Numeric) -> Self {
1216        match value {
1217            Numeric::Number(number) => Self::new(number),
1218            Numeric::BigInt(bigint) => Self::new(bigint),
1219        }
1220    }
1221}