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}