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