boa/value/
mod.rs

1//! This module implements the JavaScript Value.
2//!
3//! Javascript values, utility methods and conversion between Javascript values and Rust values.
4
5#[cfg(test)]
6mod tests;
7
8use crate::{
9    builtins::{
10        number::{f64_to_int32, f64_to_uint32},
11        Number,
12    },
13    object::{JsObject, Object, ObjectData},
14    property::{PropertyDescriptor, PropertyKey},
15    symbol::{JsSymbol, WellKnownSymbols},
16    BoaProfiler, Context, JsBigInt, JsResult, JsString,
17};
18use gc::{Finalize, Trace};
19use std::{
20    collections::HashSet,
21    convert::TryFrom,
22    fmt::{self, Display},
23    str::FromStr,
24};
25
26mod conversions;
27pub(crate) mod display;
28mod equality;
29mod hash;
30mod operations;
31mod r#type;
32
33pub use conversions::*;
34pub use display::ValueDisplay;
35pub use equality::*;
36pub use hash::*;
37pub use operations::*;
38pub use r#type::Type;
39
40/// A Javascript value
41#[derive(Trace, Finalize, Debug, Clone)]
42pub enum JsValue {
43    /// `null` - A null value, for when a value doesn't exist.
44    Null,
45    /// `undefined` - An undefined value, for when a field or index doesn't exist.
46    Undefined,
47    /// `boolean` - A `true` / `false` value, for if a certain criteria is met.
48    Boolean(bool),
49    /// `String` - A UTF-8 string, such as `"Hello, world"`.
50    String(JsString),
51    /// `Number` - A 64-bit floating point number, such as `3.1415`
52    Rational(f64),
53    /// `Number` - A 32-bit integer, such as `42`.
54    Integer(i32),
55    /// `BigInt` - holds any arbitrary large signed integer.
56    BigInt(JsBigInt),
57    /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values.
58    Object(JsObject),
59    /// `Symbol` - A Symbol Primitive type.
60    Symbol(JsSymbol),
61}
62
63/// Represents the result of ToIntegerOrInfinity operation
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum IntegerOrInfinity {
66    Integer(i64),
67    PositiveInfinity,
68    NegativeInfinity,
69}
70
71impl JsValue {
72    /// Create a new [`JsValue`].
73    #[inline]
74    pub fn new<T>(value: T) -> Self
75    where
76        T: Into<Self>,
77    {
78        value.into()
79    }
80
81    /// Creates a new `undefined` value.
82    #[inline]
83    pub fn undefined() -> Self {
84        Self::Undefined
85    }
86
87    /// Creates a new `null` value.
88    #[inline]
89    pub fn null() -> Self {
90        Self::Null
91    }
92
93    /// Creates a new number with `NaN` value.
94    #[inline]
95    pub fn nan() -> Self {
96        Self::Rational(f64::NAN)
97    }
98
99    /// Creates a new number with `Infinity` value.
100    #[inline]
101    pub fn positive_inifnity() -> Self {
102        Self::Rational(f64::INFINITY)
103    }
104
105    /// Creates a new number with `-Infinity` value.
106    #[inline]
107    pub fn negative_inifnity() -> Self {
108        Self::Rational(f64::NEG_INFINITY)
109    }
110
111    /// Returns a new empty object
112    pub(crate) fn new_object(context: &Context) -> Self {
113        let _timer = BoaProfiler::global().start_event("new_object", "value");
114        context.construct_object().into()
115    }
116
117    /// Returns true if the value is an object
118    #[inline]
119    pub fn is_object(&self) -> bool {
120        matches!(self, Self::Object(_))
121    }
122
123    #[inline]
124    pub fn as_object(&self) -> Option<JsObject> {
125        match *self {
126            Self::Object(ref o) => Some(o.clone()),
127            _ => None,
128        }
129    }
130
131    /// Returns true if the value is a symbol.
132    #[inline]
133    pub fn is_symbol(&self) -> bool {
134        matches!(self, Self::Symbol(_))
135    }
136
137    pub fn as_symbol(&self) -> Option<JsSymbol> {
138        match self {
139            Self::Symbol(symbol) => Some(symbol.clone()),
140            _ => None,
141        }
142    }
143
144    /// Returns true if the value is a function
145    #[inline]
146    pub fn is_function(&self) -> bool {
147        matches!(self, Self::Object(o) if o.is_function())
148    }
149
150    /// Returns true if the value is undefined.
151    #[inline]
152    pub fn is_undefined(&self) -> bool {
153        matches!(self, Self::Undefined)
154    }
155
156    /// Returns true if the value is null.
157    #[inline]
158    pub fn is_null(&self) -> bool {
159        matches!(self, Self::Null)
160    }
161
162    /// Returns true if the value is null or undefined.
163    #[inline]
164    pub fn is_null_or_undefined(&self) -> bool {
165        matches!(self, Self::Null | Self::Undefined)
166    }
167
168    /// Returns true if the value is a 64-bit floating-point number.
169    #[inline]
170    pub fn is_double(&self) -> bool {
171        matches!(self, Self::Rational(_))
172    }
173
174    /// Returns true if the value is integer.
175    #[inline]
176    #[allow(clippy::float_cmp)]
177    pub fn is_integer(&self) -> bool {
178        // If it can fit in a i32 and the trucated version is
179        // equal to the original then it is an integer.
180        let is_racional_intiger = |n: f64| n == ((n as i32) as f64);
181
182        match *self {
183            Self::Integer(_) => true,
184            Self::Rational(n) if is_racional_intiger(n) => true,
185            _ => false,
186        }
187    }
188
189    /// Returns true if the value is a number.
190    #[inline]
191    pub fn is_number(&self) -> bool {
192        matches!(self, Self::Rational(_) | Self::Integer(_))
193    }
194
195    #[inline]
196    pub fn as_number(&self) -> Option<f64> {
197        match *self {
198            Self::Integer(integer) => Some(integer.into()),
199            Self::Rational(rational) => Some(rational),
200            _ => None,
201        }
202    }
203
204    /// Returns true if the value is a string.
205    #[inline]
206    pub fn is_string(&self) -> bool {
207        matches!(self, Self::String(_))
208    }
209
210    /// Returns the string if the values is a string, otherwise `None`.
211    #[inline]
212    pub fn as_string(&self) -> Option<&JsString> {
213        match self {
214            Self::String(ref string) => Some(string),
215            _ => None,
216        }
217    }
218
219    /// Returns true if the value is a boolean.
220    #[inline]
221    pub fn is_boolean(&self) -> bool {
222        matches!(self, Self::Boolean(_))
223    }
224
225    #[inline]
226    pub fn as_boolean(&self) -> Option<bool> {
227        match self {
228            Self::Boolean(boolean) => Some(*boolean),
229            _ => None,
230        }
231    }
232
233    /// Returns true if the value is a bigint.
234    #[inline]
235    pub fn is_bigint(&self) -> bool {
236        matches!(self, Self::BigInt(_))
237    }
238
239    /// Returns an optional reference to a `BigInt` if the value is a BigInt primitive.
240    #[inline]
241    pub fn as_bigint(&self) -> Option<&JsBigInt> {
242        match self {
243            Self::BigInt(bigint) => Some(bigint),
244            _ => None,
245        }
246    }
247
248    /// Converts the value to a `bool` type.
249    ///
250    /// More information:
251    ///  - [ECMAScript][spec]
252    ///
253    /// [spec]: https://tc39.es/ecma262/#sec-toboolean
254    pub fn to_boolean(&self) -> bool {
255        match *self {
256            Self::Undefined | Self::Null => false,
257            Self::Symbol(_) | Self::Object(_) => true,
258            Self::String(ref s) if !s.is_empty() => true,
259            Self::Rational(n) if n != 0.0 && !n.is_nan() => true,
260            Self::Integer(n) if n != 0 => true,
261            Self::BigInt(ref n) if !n.is_zero() => true,
262            Self::Boolean(v) => v,
263            _ => false,
264        }
265    }
266
267    /// Resolve the property in the object.
268    ///
269    /// A copy of the Property is returned.
270    pub(crate) fn get_property<Key>(&self, key: Key) -> Option<PropertyDescriptor>
271    where
272        Key: Into<PropertyKey>,
273    {
274        let key = key.into();
275        let _timer = BoaProfiler::global().start_event("Value::get_property", "value");
276        match self {
277            Self::Object(ref object) => {
278                // TODO: had to skip `__get_own_properties__` since we don't have context here
279                let property = object.borrow().properties().get(&key).cloned();
280                if property.is_some() {
281                    return property;
282                }
283
284                object.borrow().prototype_instance().get_property(key)
285            }
286            _ => None,
287        }
288    }
289
290    /// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist
291    /// get_field receives a Property from get_prop(). It should then return the `[[Get]]` result value if that's set, otherwise fall back to `[[Value]]`
292    pub(crate) fn get_field<K>(&self, key: K, context: &mut Context) -> JsResult<Self>
293    where
294        K: Into<PropertyKey>,
295    {
296        let _timer = BoaProfiler::global().start_event("Value::get_field", "value");
297        if let Self::Object(ref obj) = *self {
298            obj.clone()
299                .__get__(&key.into(), obj.clone().into(), context)
300        } else {
301            Ok(JsValue::undefined())
302        }
303    }
304
305    /// Set the field in the value
306    ///
307    /// Similar to `7.3.4 Set ( O, P, V, Throw )`, but returns the value instead of a boolean.
308    ///
309    /// More information:
310    ///  - [ECMAScript][spec]
311    ///
312    /// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw
313    #[inline]
314    pub(crate) fn set_field<K, V>(
315        &self,
316        key: K,
317        value: V,
318        throw: bool,
319        context: &mut Context,
320    ) -> JsResult<JsValue>
321    where
322        K: Into<PropertyKey>,
323        V: Into<JsValue>,
324    {
325        // 1. Assert: Type(O) is Object.
326        // TODO: Currently the value may not be an object.
327        //       In that case this function does nothing.
328        // 2. Assert: IsPropertyKey(P) is true.
329        // 3. Assert: Type(Throw) is Boolean.
330
331        let key = key.into();
332        let value = value.into();
333        let _timer = BoaProfiler::global().start_event("Value::set_field", "value");
334        if let Self::Object(ref obj) = *self {
335            // 4. Let success be ? O.[[Set]](P, V, O).
336            let success = obj
337                .clone()
338                .__set__(key, value.clone(), obj.clone().into(), context)?;
339
340            // 5. If success is false and Throw is true, throw a TypeError exception.
341            // 6. Return success.
342            if !success && throw {
343                return Err(context.construct_type_error("Cannot assign value to property"));
344            } else {
345                return Ok(value);
346            }
347        }
348        Ok(value)
349    }
350
351    /// Set the kind of an object.
352    #[inline]
353    pub fn set_data(&self, data: ObjectData) {
354        if let Self::Object(ref obj) = *self {
355            obj.borrow_mut().data = data;
356        }
357    }
358
359    /// Set the property in the value.
360    #[inline]
361    pub(crate) fn set_property<K, P>(&self, key: K, property: P)
362    where
363        K: Into<PropertyKey>,
364        P: Into<PropertyDescriptor>,
365    {
366        if let Some(object) = self.as_object() {
367            object.insert(key.into(), property.into());
368        }
369    }
370
371    /// The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType.
372    ///
373    /// <https://tc39.es/ecma262/#sec-toprimitive>
374    pub fn to_primitive(
375        &self,
376        context: &mut Context,
377        preferred_type: PreferredType,
378    ) -> JsResult<JsValue> {
379        // 1. Assert: input is an ECMAScript language value. (always a value not need to check)
380        // 2. If Type(input) is Object, then
381        if let JsValue::Object(obj) = self {
382            if let Some(exotic_to_prim) =
383                obj.get_method(context, WellKnownSymbols::to_primitive())?
384            {
385                let hint = match preferred_type {
386                    PreferredType::String => "string",
387                    PreferredType::Number => "number",
388                    PreferredType::Default => "default",
389                }
390                .into();
391                let result = exotic_to_prim.call(self, &[hint], context)?;
392                return if result.is_object() {
393                    Err(context.construct_type_error("Symbol.toPrimitive cannot return an object"))
394                } else {
395                    Ok(result)
396                };
397            }
398
399            let mut hint = preferred_type;
400
401            if hint == PreferredType::Default {
402                hint = PreferredType::Number;
403            };
404
405            // g. Return ? OrdinaryToPrimitive(input, hint).
406            obj.ordinary_to_primitive(context, hint)
407        } else {
408            // 3. Return input.
409            Ok(self.clone())
410        }
411    }
412
413    /// Converts the value to a `BigInt`.
414    ///
415    /// This function is equivelent to `BigInt(value)` in JavaScript.
416    pub fn to_bigint(&self, context: &mut Context) -> JsResult<JsBigInt> {
417        match self {
418            JsValue::Null => Err(context.construct_type_error("cannot convert null to a BigInt")),
419            JsValue::Undefined => {
420                Err(context.construct_type_error("cannot convert undefined to a BigInt"))
421            }
422            JsValue::String(ref string) => {
423                if let Some(value) = JsBigInt::from_string(string) {
424                    Ok(value)
425                } else {
426                    Err(context.construct_syntax_error(format!(
427                        "cannot convert string '{}' to bigint primitive",
428                        string
429                    )))
430                }
431            }
432            JsValue::Boolean(true) => Ok(JsBigInt::one()),
433            JsValue::Boolean(false) => Ok(JsBigInt::zero()),
434            JsValue::Integer(num) => Ok(JsBigInt::new(*num)),
435            JsValue::Rational(num) => {
436                if let Ok(bigint) = JsBigInt::try_from(*num) {
437                    return Ok(bigint);
438                }
439                Err(context.construct_type_error(format!(
440                    "The number {} cannot be converted to a BigInt because it is not an integer",
441                    num
442                )))
443            }
444            JsValue::BigInt(b) => Ok(b.clone()),
445            JsValue::Object(_) => {
446                let primitive = self.to_primitive(context, PreferredType::Number)?;
447                primitive.to_bigint(context)
448            }
449            JsValue::Symbol(_) => {
450                Err(context.construct_type_error("cannot convert Symbol to a BigInt"))
451            }
452        }
453    }
454
455    /// Returns an object that implements `Display`.
456    ///
457    /// # Examples
458    ///
459    /// ```
460    /// use boa::JsValue;
461    ///
462    /// let value = JsValue::new(3);
463    ///
464    /// println!("{}", value.display());
465    /// ```
466    #[inline]
467    pub fn display(&self) -> ValueDisplay<'_> {
468        ValueDisplay { value: self }
469    }
470
471    /// Converts the value to a string.
472    ///
473    /// This function is equivalent to `String(value)` in JavaScript.
474    pub fn to_string(&self, context: &mut Context) -> JsResult<JsString> {
475        match self {
476            JsValue::Null => Ok("null".into()),
477            JsValue::Undefined => Ok("undefined".into()),
478            JsValue::Boolean(boolean) => Ok(boolean.to_string().into()),
479            JsValue::Rational(rational) => Ok(Number::to_native_string(*rational).into()),
480            JsValue::Integer(integer) => Ok(integer.to_string().into()),
481            JsValue::String(string) => Ok(string.clone()),
482            JsValue::Symbol(_) => {
483                Err(context.construct_type_error("can't convert symbol to string"))
484            }
485            JsValue::BigInt(ref bigint) => Ok(bigint.to_string().into()),
486            JsValue::Object(_) => {
487                let primitive = self.to_primitive(context, PreferredType::String)?;
488                primitive.to_string(context)
489            }
490        }
491    }
492
493    /// Converts the value to an Object.
494    ///
495    /// This function is equivalent to `Object(value)` in JavaScript
496    ///
497    /// See: <https://tc39.es/ecma262/#sec-toobject>
498    pub fn to_object(&self, context: &mut Context) -> JsResult<JsObject> {
499        match self {
500            JsValue::Undefined | JsValue::Null => {
501                Err(context.construct_type_error("cannot convert 'null' or 'undefined' to object"))
502            }
503            JsValue::Boolean(boolean) => {
504                let prototype = context.standard_objects().boolean_object().prototype();
505                Ok(JsObject::new(Object::with_prototype(
506                    prototype.into(),
507                    ObjectData::boolean(*boolean),
508                )))
509            }
510            JsValue::Integer(integer) => {
511                let prototype = context.standard_objects().number_object().prototype();
512                Ok(JsObject::new(Object::with_prototype(
513                    prototype.into(),
514                    ObjectData::number(f64::from(*integer)),
515                )))
516            }
517            JsValue::Rational(rational) => {
518                let prototype = context.standard_objects().number_object().prototype();
519                Ok(JsObject::new(Object::with_prototype(
520                    prototype.into(),
521                    ObjectData::number(*rational),
522                )))
523            }
524            JsValue::String(ref string) => {
525                let prototype = context.standard_objects().string_object().prototype();
526
527                let object = JsObject::new(Object::with_prototype(
528                    prototype.into(),
529                    ObjectData::string(string.clone()),
530                ));
531                // Make sure the correct length is set on our new string object
532                object.insert_property(
533                    "length",
534                    PropertyDescriptor::builder()
535                        .value(string.encode_utf16().count())
536                        .writable(false)
537                        .enumerable(false)
538                        .configurable(false),
539                );
540                Ok(object)
541            }
542            JsValue::Symbol(ref symbol) => {
543                let prototype = context.standard_objects().symbol_object().prototype();
544                Ok(JsObject::new(Object::with_prototype(
545                    prototype.into(),
546                    ObjectData::symbol(symbol.clone()),
547                )))
548            }
549            JsValue::BigInt(ref bigint) => {
550                let prototype = context.standard_objects().bigint_object().prototype();
551                Ok(JsObject::new(Object::with_prototype(
552                    prototype.into(),
553                    ObjectData::big_int(bigint.clone()),
554                )))
555            }
556            JsValue::Object(jsobject) => Ok(jsobject.clone()),
557        }
558    }
559
560    /// Converts the value to a `PropertyKey`, that can be used as a key for properties.
561    ///
562    /// See <https://tc39.es/ecma262/#sec-topropertykey>
563    pub fn to_property_key(&self, context: &mut Context) -> JsResult<PropertyKey> {
564        Ok(match self {
565            // Fast path:
566            JsValue::String(string) => string.clone().into(),
567            JsValue::Symbol(symbol) => symbol.clone().into(),
568            // Slow path:
569            _ => match self.to_primitive(context, PreferredType::String)? {
570                JsValue::String(ref string) => string.clone().into(),
571                JsValue::Symbol(ref symbol) => symbol.clone().into(),
572                primitive => primitive.to_string(context)?.into(),
573            },
574        })
575    }
576
577    /// It returns value converted to a numeric value of type `Number` or `BigInt`.
578    ///
579    /// See: <https://tc39.es/ecma262/#sec-tonumeric>
580    pub fn to_numeric(&self, context: &mut Context) -> JsResult<Numeric> {
581        let primitive = self.to_primitive(context, PreferredType::Number)?;
582        if let Some(bigint) = primitive.as_bigint() {
583            return Ok(bigint.clone().into());
584        }
585        Ok(self.to_number(context)?.into())
586    }
587
588    /// Converts a value to an integral 32 bit unsigned integer.
589    ///
590    /// This function is equivalent to `value | 0` in JavaScript
591    ///
592    /// See: <https://tc39.es/ecma262/#sec-touint32>
593    pub fn to_u32(&self, context: &mut Context) -> JsResult<u32> {
594        // This is the fast path, if the value is Integer we can just return it.
595        if let JsValue::Integer(number) = *self {
596            return Ok(number as u32);
597        }
598        let number = self.to_number(context)?;
599
600        Ok(f64_to_uint32(number))
601    }
602
603    /// Converts a value to an integral 32 bit signed integer.
604    ///
605    /// See: <https://tc39.es/ecma262/#sec-toint32>
606    pub fn to_i32(&self, context: &mut Context) -> JsResult<i32> {
607        // This is the fast path, if the value is Integer we can just return it.
608        if let JsValue::Integer(number) = *self {
609            return Ok(number);
610        }
611        let number = self.to_number(context)?;
612
613        Ok(f64_to_int32(number))
614    }
615
616    /// Converts a value to a non-negative integer if it is a valid integer index value.
617    ///
618    /// See: <https://tc39.es/ecma262/#sec-toindex>
619    pub fn to_index(&self, context: &mut Context) -> JsResult<usize> {
620        if self.is_undefined() {
621            return Ok(0);
622        }
623
624        let integer_index = self.to_integer(context)?;
625
626        if integer_index < 0.0 {
627            return Err(context.construct_range_error("Integer index must be >= 0"));
628        }
629
630        if integer_index > Number::MAX_SAFE_INTEGER {
631            return Err(
632                context.construct_range_error("Integer index must be less than 2**(53) - 1")
633            );
634        }
635
636        Ok(integer_index as usize)
637    }
638
639    /// Converts argument to an integer suitable for use as the length of an array-like object.
640    ///
641    /// See: <https://tc39.es/ecma262/#sec-tolength>
642    pub fn to_length(&self, context: &mut Context) -> JsResult<usize> {
643        // 1. Let len be ? ToInteger(argument).
644        let len = self.to_integer(context)?;
645
646        // 2. If len ≤ +0, return +0.
647        if len < 0.0 {
648            return Ok(0);
649        }
650
651        // 3. Return min(len, 2^53 - 1).
652        Ok(len.min(Number::MAX_SAFE_INTEGER) as usize)
653    }
654
655    /// Converts a value to an integral Number value.
656    ///
657    /// See: <https://tc39.es/ecma262/#sec-tointeger>
658    pub fn to_integer(&self, context: &mut Context) -> JsResult<f64> {
659        // 1. Let number be ? ToNumber(argument).
660        let number = self.to_number(context)?;
661
662        // 2. If number is +∞ or -∞, return number.
663        if !number.is_finite() {
664            // 3. If number is NaN, +0, or -0, return +0.
665            if number.is_nan() {
666                return Ok(0.0);
667            }
668            return Ok(number);
669        }
670
671        // 4. Let integer be the Number value that is the same sign as number and whose magnitude is floor(abs(number)).
672        // 5. If integer is -0, return +0.
673        // 6. Return integer.
674        Ok(number.trunc() + 0.0) // We add 0.0 to convert -0.0 to +0.0
675    }
676
677    /// Converts a value to a double precision floating point.
678    ///
679    /// This function is equivalent to the unary `+` operator (`+value`) in JavaScript
680    ///
681    /// See: <https://tc39.es/ecma262/#sec-tonumber>
682    pub fn to_number(&self, context: &mut Context) -> JsResult<f64> {
683        match *self {
684            JsValue::Null => Ok(0.0),
685            JsValue::Undefined => Ok(f64::NAN),
686            JsValue::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
687            JsValue::String(ref string) => Ok(string.string_to_number()),
688            JsValue::Rational(number) => Ok(number),
689            JsValue::Integer(integer) => Ok(f64::from(integer)),
690            JsValue::Symbol(_) => {
691                Err(context.construct_type_error("argument must not be a symbol"))
692            }
693            JsValue::BigInt(_) => {
694                Err(context.construct_type_error("argument must not be a bigint"))
695            }
696            JsValue::Object(_) => {
697                let primitive = self.to_primitive(context, PreferredType::Number)?;
698                primitive.to_number(context)
699            }
700        }
701    }
702
703    /// This is a more specialized version of `to_numeric`, including `BigInt`.
704    ///
705    /// This function is equivalent to `Number(value)` in JavaScript
706    ///
707    /// See: <https://tc39.es/ecma262/#sec-tonumeric>
708    pub fn to_numeric_number(&self, context: &mut Context) -> JsResult<f64> {
709        let primitive = self.to_primitive(context, PreferredType::Number)?;
710        if let Some(bigint) = primitive.as_bigint() {
711            return Ok(bigint.to_f64());
712        }
713        primitive.to_number(context)
714    }
715
716    /// Check if the `Value` can be converted to an `Object`
717    ///
718    /// The abstract operation `RequireObjectCoercible` takes argument argument.
719    /// It throws an error if argument is a value that cannot be converted to an Object using `ToObject`.
720    /// It is defined by [Table 15][table]
721    ///
722    /// More information:
723    ///  - [ECMAScript reference][spec]
724    ///
725    /// [table]: https://tc39.es/ecma262/#table-14
726    /// [spec]: https://tc39.es/ecma262/#sec-requireobjectcoercible
727    #[inline]
728    pub fn require_object_coercible(&self, context: &mut Context) -> JsResult<&JsValue> {
729        if self.is_null_or_undefined() {
730            Err(context.construct_type_error("cannot convert null or undefined to Object"))
731        } else {
732            Ok(self)
733        }
734    }
735
736    #[inline]
737    pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult<PropertyDescriptor> {
738        // 1. If Type(Obj) is not Object, throw a TypeError exception.
739        match self {
740            JsValue::Object(ref obj) => obj.to_property_descriptor(context),
741            _ => Err(context
742                .construct_type_error("Cannot construct a property descriptor from a non-object")),
743        }
744    }
745
746    /// Converts argument to an integer, +∞, or -∞.
747    ///
748    /// See: <https://tc39.es/ecma262/#sec-tointegerorinfinity>
749    pub fn to_integer_or_infinity(&self, context: &mut Context) -> JsResult<IntegerOrInfinity> {
750        // 1. Let number be ? ToNumber(argument).
751        let number = self.to_number(context)?;
752
753        // 2. If number is NaN, +0𝔽, or -0𝔽, return 0.
754        if number.is_nan() || number == 0.0 || number == -0.0 {
755            Ok(IntegerOrInfinity::Integer(0))
756        } else if number.is_infinite() && number.is_sign_positive() {
757            // 3. If number is +∞𝔽, return +∞.
758            Ok(IntegerOrInfinity::PositiveInfinity)
759        } else if number.is_infinite() && number.is_sign_negative() {
760            // 4. If number is -∞𝔽, return -∞.
761            Ok(IntegerOrInfinity::NegativeInfinity)
762        } else {
763            // 5. Let integer be floor(abs(ℝ(number))).
764            let integer = number.abs().floor();
765            let integer = integer.min(Number::MAX_SAFE_INTEGER) as i64;
766
767            // 6. If number < +0𝔽, set integer to -integer.
768            // 7. Return integer.
769            if number < 0.0 {
770                Ok(IntegerOrInfinity::Integer(-integer))
771            } else {
772                Ok(IntegerOrInfinity::Integer(integer))
773            }
774        }
775    }
776
777    /// `typeof` operator. Returns a string representing the type of the
778    /// given ECMA Value.
779    ///
780    /// More information:
781    /// - [EcmaScript reference][spec]
782    ///
783    /// [spec]: https://tc39.es/ecma262/#sec-typeof-operator
784    pub fn type_of(&self) -> JsString {
785        match *self {
786            Self::Rational(_) | Self::Integer(_) => "number",
787            Self::String(_) => "string",
788            Self::Boolean(_) => "boolean",
789            Self::Symbol(_) => "symbol",
790            Self::Null => "object",
791            Self::Undefined => "undefined",
792            Self::BigInt(_) => "bigint",
793            Self::Object(ref object) => {
794                if object.is_callable() {
795                    "function"
796                } else {
797                    "object"
798                }
799            }
800        }
801        .into()
802    }
803
804    /// Check if it is an array.
805    ///
806    /// More information:
807    ///  - [ECMAScript reference][spec]
808    ///
809    /// [spec]: https://tc39.es/ecma262/#sec-isarray
810    pub(crate) fn is_array(&self, _context: &mut Context) -> JsResult<bool> {
811        // 1. If Type(argument) is not Object, return false.
812        if let Some(object) = self.as_object() {
813            // 2. If argument is an Array exotic object, return true.
814            //     a. If argument.[[ProxyHandler]] is null, throw a TypeError exception.
815            // 3. If argument is a Proxy exotic object, then
816            //     b. Let target be argument.[[ProxyTarget]].
817            //     c. Return ? IsArray(target).
818            // 4. Return false.
819            Ok(object.is_array())
820        } else {
821            Ok(false)
822        }
823    }
824}
825
826impl Default for JsValue {
827    fn default() -> Self {
828        Self::Undefined
829    }
830}
831
832/// The preffered type to convert an object to a primitive `Value`.
833#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
834pub enum PreferredType {
835    String,
836    Number,
837    Default,
838}
839
840/// Numeric value which can be of two types `Number`, `BigInt`.
841#[derive(Debug, Clone, PartialEq, PartialOrd)]
842pub enum Numeric {
843    /// Double precision floating point number.
844    Number(f64),
845    /// BigInt an integer of arbitrary size.
846    BigInt(JsBigInt),
847}
848
849impl From<f64> for Numeric {
850    #[inline]
851    fn from(value: f64) -> Self {
852        Self::Number(value)
853    }
854}
855
856impl From<i32> for Numeric {
857    #[inline]
858    fn from(value: i32) -> Self {
859        Self::Number(value.into())
860    }
861}
862
863impl From<i16> for Numeric {
864    #[inline]
865    fn from(value: i16) -> Self {
866        Self::Number(value.into())
867    }
868}
869
870impl From<i8> for Numeric {
871    #[inline]
872    fn from(value: i8) -> Self {
873        Self::Number(value.into())
874    }
875}
876
877impl From<u32> for Numeric {
878    #[inline]
879    fn from(value: u32) -> Self {
880        Self::Number(value.into())
881    }
882}
883
884impl From<u16> for Numeric {
885    #[inline]
886    fn from(value: u16) -> Self {
887        Self::Number(value.into())
888    }
889}
890
891impl From<u8> for Numeric {
892    #[inline]
893    fn from(value: u8) -> Self {
894        Self::Number(value.into())
895    }
896}
897
898impl From<JsBigInt> for Numeric {
899    #[inline]
900    fn from(value: JsBigInt) -> Self {
901        Self::BigInt(value)
902    }
903}
904
905impl From<Numeric> for JsValue {
906    fn from(value: Numeric) -> Self {
907        match value {
908            Numeric::Number(number) => Self::new(number),
909            Numeric::BigInt(bigint) => Self::new(bigint),
910        }
911    }
912}