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