wry_bindgen/
value.rs

1//! JsValue - An opaque reference to a JavaScript value
2//!
3//! This type represents a reference to a JavaScript value on the JS heap.
4//! API compatible with wasm-bindgen's JsValue.
5
6use alloc::string::String;
7use core::fmt;
8
9/// Offset for reserved JS value indices.
10/// Values below JSIDX_RESERVED are special constants that don't need drop/clone.
11pub(crate) const JSIDX_OFFSET: u64 = 128;
12
13/// Index for the `undefined` JS value.
14pub(crate) const JSIDX_UNDEFINED: u64 = JSIDX_OFFSET;
15
16/// Index for the `null` JS value.
17pub(crate) const JSIDX_NULL: u64 = JSIDX_OFFSET + 1;
18
19/// Index for the `true` JS value.
20pub(crate) const JSIDX_TRUE: u64 = JSIDX_OFFSET + 2;
21
22/// Index for the `false` JS value.
23pub(crate) const JSIDX_FALSE: u64 = JSIDX_OFFSET + 3;
24
25/// First usable heap ID. IDs below this are reserved for special values.
26pub(crate) const JSIDX_RESERVED: u64 = JSIDX_OFFSET + 4;
27
28/// An opaque reference to a JavaScript heap object.
29///
30/// This type is the wry-bindgen equivalent of wasm-bindgen's `JsValue`.
31/// It represents any JavaScript value and is used as the base type for
32/// all imported JS types.
33///
34/// JsValue is intentionally opaque - you cannot inspect or create values
35/// directly. All values come from JavaScript via the IPC protocol.
36///
37/// Unlike wasm-bindgen which runs in a single-threaded Wasm environment,
38/// this implementation uses the IPC protocol to communicate with JS.
39pub struct JsValue {
40    #[doc(hidden)]
41    pub idx: u64,
42}
43
44impl JsValue {
45    /// The `null` JS value constant.
46    pub const NULL: JsValue = JsValue::_new(JSIDX_NULL);
47
48    /// The `undefined` JS value constant.
49    pub const UNDEFINED: JsValue = JsValue::_new(JSIDX_UNDEFINED);
50
51    /// The `true` JS value constant.
52    pub const TRUE: JsValue = JsValue::_new(JSIDX_TRUE);
53
54    /// The `false` JS value constant.
55    pub const FALSE: JsValue = JsValue::_new(JSIDX_FALSE);
56
57    /// Create a new JsValue from an index (const fn for static values).
58    #[inline]
59    const fn _new(idx: u64) -> JsValue {
60        JsValue { idx }
61    }
62
63    /// Create a new JsValue from a heap ID.
64    ///
65    /// This is called internally when decoding a value from JS.
66    #[inline]
67    pub(crate) fn from_id(id: u64) -> Self {
68        Self { idx: id }
69    }
70
71    /// Get the heap ID for this value.
72    ///
73    /// This is used internally for encoding values to send to JS.
74    #[inline]
75    pub fn id(&self) -> u64 {
76        self.idx
77    }
78
79    /// Returns the value as f64 without type checking.
80    /// Used by serde-wasm-bindgen for numeric conversions.
81    #[inline]
82    pub fn unchecked_into_f64(&self) -> f64 {
83        self.as_f64().unwrap_or(f64::NAN)
84    }
85
86    /// Check if this value is an instance of a specific JS type.
87    #[inline]
88    pub fn has_type<T: crate::JsCast>(&self) -> bool {
89        T::is_type_of(self)
90    }
91
92    /// Get the internal ABI representation (heap index), consuming self.
93    /// This is used by the convert module for low-level interop.
94    /// Returns u32 for wasm-bindgen compatibility.
95    #[inline]
96    pub fn into_abi(self) -> u32 {
97        let id = self.idx;
98        core::mem::forget(self);
99        id as u32
100    }
101
102    /// Creates a new JS value representing `undefined`.
103    #[inline]
104    pub const fn undefined() -> JsValue {
105        JsValue::UNDEFINED
106    }
107
108    /// Creates a new JS value representing `null`.
109    #[inline]
110    pub const fn null() -> JsValue {
111        JsValue::NULL
112    }
113
114    /// Creates a new JS value which is a boolean.
115    #[inline]
116    pub const fn from_bool(b: bool) -> JsValue {
117        if b { JsValue::TRUE } else { JsValue::FALSE }
118    }
119
120    /// Creates a JS string from a Rust string.
121    #[allow(clippy::should_implement_trait)]
122    pub fn from_str(s: &str) -> JsValue {
123        s.into()
124    }
125
126    /// Creates a JS number from an f64.
127    pub fn from_f64(n: f64) -> JsValue {
128        n.into()
129    }
130}
131
132impl Clone for JsValue {
133    #[inline]
134    fn clone(&self) -> JsValue {
135        // Reserved values don't need cloning - they're constants
136        if self.idx < JSIDX_RESERVED {
137            return JsValue { idx: self.idx };
138        }
139
140        // Clone the value on the JS heap
141        crate::js_helpers::js_clone_heap_ref(self.idx)
142    }
143}
144
145impl Drop for JsValue {
146    #[inline]
147    fn drop(&mut self) {
148        // Reserved values don't need dropping - they're constants
149        if self.idx < JSIDX_RESERVED {
150            return;
151        }
152
153        // Drop the value on the JS heap
154        crate::batch::queue_js_drop(self.idx);
155    }
156}
157
158impl PartialEq<&str> for JsValue {
159    fn eq(&self, other: &&str) -> bool {
160        match self.as_string() {
161            Some(s) => &s == other,
162            None => false,
163        }
164    }
165}
166
167impl PartialEq<JsValue> for &str {
168    fn eq(&self, other: &JsValue) -> bool {
169        match other.as_string() {
170            Some(s) => self == &s,
171            None => false,
172        }
173    }
174}
175
176impl PartialEq<str> for JsValue {
177    fn eq(&self, other: &str) -> bool {
178        match self.as_string() {
179            Some(s) => s == other,
180            None => false,
181        }
182    }
183}
184
185impl PartialEq<String> for JsValue {
186    fn eq(&self, other: &String) -> bool {
187        match self.as_string() {
188            Some(s) => &s == other,
189            None => false,
190        }
191    }
192}
193
194impl PartialEq<JsValue> for String {
195    fn eq(&self, other: &JsValue) -> bool {
196        match other.as_string() {
197            Some(s) => self == &s,
198            None => false,
199        }
200    }
201}
202
203impl PartialEq<&String> for JsValue {
204    fn eq(&self, other: &&String) -> bool {
205        match self.as_string() {
206            Some(s) => &s == *other,
207            None => false,
208        }
209    }
210}
211
212impl PartialEq<JsValue> for &String {
213    fn eq(&self, other: &JsValue) -> bool {
214        match other.as_string() {
215            Some(s) => *self == &s,
216            None => false,
217        }
218    }
219}
220
221impl PartialEq<bool> for JsValue {
222    fn eq(&self, other: &bool) -> bool {
223        match self.as_bool() {
224            Some(b) => b == *other,
225            None => false,
226        }
227    }
228}
229
230impl PartialEq<JsValue> for bool {
231    fn eq(&self, other: &JsValue) -> bool {
232        match other.as_bool() {
233            Some(b) => *self == b,
234            None => false,
235        }
236    }
237}
238
239impl PartialEq<f32> for JsValue {
240    fn eq(&self, other: &f32) -> bool {
241        match self.as_f64() {
242            Some(n) => n == (*other as f64),
243            None => false,
244        }
245    }
246}
247
248impl PartialEq<JsValue> for f32 {
249    fn eq(&self, other: &JsValue) -> bool {
250        match other.as_f64() {
251            Some(n) => (*self as f64) == n,
252            None => false,
253        }
254    }
255}
256
257impl PartialEq<f64> for JsValue {
258    fn eq(&self, other: &f64) -> bool {
259        match self.as_f64() {
260            Some(n) => n == *other,
261            None => false,
262        }
263    }
264}
265
266impl PartialEq<JsValue> for f64 {
267    fn eq(&self, other: &JsValue) -> bool {
268        match other.as_f64() {
269            Some(n) => *self == n,
270            None => false,
271        }
272    }
273}
274
275// Macro for integer PartialEq implementations
276macro_rules! impl_partial_eq_int {
277    ($($t:ty),*) => {
278        $(
279            impl PartialEq<$t> for JsValue {
280                fn eq(&self, other: &$t) -> bool {
281                    match self.as_f64() {
282                        Some(n) => n == (*other as f64),
283                        None => false,
284                    }
285                }
286            }
287
288            impl PartialEq<JsValue> for $t {
289                fn eq(&self, other: &JsValue) -> bool {
290                    match other.as_f64() {
291                        Some(n) => (*self as f64) == n,
292                        None => false,
293                    }
294                }
295            }
296        )*
297    };
298}
299
300impl_partial_eq_int!(
301    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
302);
303
304impl fmt::Debug for JsValue {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        f.write_str(&self.as_debug_string())
307    }
308}
309
310impl PartialEq for JsValue {
311    fn eq(&self, other: &Self) -> bool {
312        self.idx == other.idx
313    }
314}
315
316impl Eq for JsValue {}
317
318impl core::hash::Hash for JsValue {
319    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
320        self.idx.hash(state);
321    }
322}
323
324impl Default for JsValue {
325    fn default() -> Self {
326        Self::UNDEFINED
327    }
328}
329
330// Additional methods needed by js-sys for BigInt operations
331impl JsValue {
332    /// Checked division.
333    pub fn checked_div(&self, rhs: &JsValue) -> JsValue {
334        crate::js_helpers::js_checked_div(self, rhs)
335    }
336
337    /// Power operation.
338    pub fn pow(&self, rhs: &JsValue) -> JsValue {
339        crate::js_helpers::js_pow(self, rhs)
340    }
341
342    /// Bitwise AND.
343    pub fn bit_and(&self, rhs: &JsValue) -> JsValue {
344        crate::js_helpers::js_bit_and(self, rhs)
345    }
346
347    /// Bitwise OR.
348    pub fn bit_or(&self, rhs: &JsValue) -> JsValue {
349        crate::js_helpers::js_bit_or(self, rhs)
350    }
351
352    /// Bitwise XOR.
353    pub fn bit_xor(&self, rhs: &JsValue) -> JsValue {
354        crate::js_helpers::js_bit_xor(self, rhs)
355    }
356
357    /// Bitwise NOT.
358    pub fn bit_not(&self) -> JsValue {
359        crate::js_helpers::js_bit_not(self)
360    }
361
362    /// Left shift.
363    pub fn shl(&self, rhs: &JsValue) -> JsValue {
364        crate::js_helpers::js_shl(self, rhs)
365    }
366
367    /// Signed right shift.
368    pub fn shr(&self, rhs: &JsValue) -> JsValue {
369        crate::js_helpers::js_shr(self, rhs)
370    }
371
372    /// Unsigned right shift.
373    pub fn unsigned_shr(&self, rhs: &JsValue) -> u32 {
374        crate::js_helpers::js_unsigned_shr(self, rhs)
375    }
376
377    /// Add.
378    pub fn add(&self, rhs: &JsValue) -> JsValue {
379        crate::js_helpers::js_add(self, rhs)
380    }
381
382    /// Subtract.
383    pub fn sub(&self, rhs: &JsValue) -> JsValue {
384        crate::js_helpers::js_sub(self, rhs)
385    }
386
387    /// Multiply.
388    pub fn mul(&self, rhs: &JsValue) -> JsValue {
389        crate::js_helpers::js_mul(self, rhs)
390    }
391
392    /// Divide.
393    pub fn div(&self, rhs: &JsValue) -> JsValue {
394        crate::js_helpers::js_div(self, rhs)
395    }
396
397    /// Remainder.
398    pub fn rem(&self, rhs: &JsValue) -> JsValue {
399        crate::js_helpers::js_rem(self, rhs)
400    }
401
402    /// Negate.
403    pub fn neg(&self) -> JsValue {
404        crate::js_helpers::js_neg(self)
405    }
406
407    /// Less than comparison.
408    pub fn lt(&self, rhs: &JsValue) -> bool {
409        crate::js_helpers::js_lt(self, rhs)
410    }
411
412    /// Less than or equal comparison.
413    pub fn le(&self, rhs: &JsValue) -> bool {
414        crate::js_helpers::js_le(self, rhs)
415    }
416
417    /// Greater than comparison.
418    pub fn gt(&self, rhs: &JsValue) -> bool {
419        crate::js_helpers::js_gt(self, rhs)
420    }
421
422    /// Greater than or equal comparison.
423    pub fn ge(&self, rhs: &JsValue) -> bool {
424        crate::js_helpers::js_ge(self, rhs)
425    }
426
427    /// Loose equality (==).
428    pub fn loose_eq(&self, rhs: &JsValue) -> bool {
429        crate::js_helpers::js_loose_eq(self, rhs)
430    }
431
432    /// Check if this value is a falsy value in JavaScript.
433    pub fn is_falsy(&self) -> bool {
434        crate::js_helpers::js_is_falsy(self)
435    }
436
437    /// Check if this value is a truthy value in JavaScript.
438    pub fn is_truthy(&self) -> bool {
439        crate::js_helpers::js_is_truthy(self)
440    }
441
442    /// Check if this value is an object.
443    pub fn is_object(&self) -> bool {
444        crate::js_helpers::js_is_object(self)
445    }
446
447    /// Check if this value is a function.
448    pub fn is_function(&self) -> bool {
449        crate::js_helpers::js_is_function(self)
450    }
451
452    /// Check if this value is a string.
453    pub fn is_string(&self) -> bool {
454        crate::js_helpers::js_is_string(self)
455    }
456
457    /// Check if this value is a symbol.
458    pub fn is_symbol(&self) -> bool {
459        crate::js_helpers::js_is_symbol(self)
460    }
461
462    /// Check if this value is a bigint.
463    pub fn is_bigint(&self) -> bool {
464        crate::js_helpers::js_is_bigint(self)
465    }
466
467    /// Check if this value is undefined.
468    pub fn is_undefined(&self) -> bool {
469        if self.idx == JSIDX_UNDEFINED {
470            return true;
471        }
472        crate::js_helpers::js_is_undefined(self)
473    }
474
475    /// Check if this value is null.
476    pub fn is_null(&self) -> bool {
477        if self.idx == JSIDX_NULL {
478            return true;
479        }
480        crate::js_helpers::js_is_null(self)
481    }
482
483    /// Get the typeof this value as a string.
484    pub fn js_typeof(&self) -> JsValue {
485        crate::js_helpers::js_typeof(self)
486    }
487
488    /// Check if this value has a property with the given name.
489    pub fn js_in(&self, obj: &JsValue) -> bool {
490        crate::js_helpers::js_in(self, obj)
491    }
492
493    /// Get the value as a bool.
494    pub fn as_bool(&self) -> Option<bool> {
495        match self.idx {
496            JSIDX_TRUE => Some(true),
497            JSIDX_FALSE => Some(false),
498            idx if idx < JSIDX_RESERVED => None,
499            _ => {
500                // For heap values, check via JS
501                if crate::js_helpers::js_is_true(self) {
502                    Some(true)
503                } else if crate::js_helpers::js_is_false(self) {
504                    Some(false)
505                } else {
506                    None
507                }
508            }
509        }
510    }
511
512    /// Get the value as an f64.
513    pub fn as_f64(&self) -> Option<f64> {
514        crate::js_helpers::js_as_f64(self)
515    }
516
517    /// Get the value as a string.
518    pub fn as_string(&self) -> Option<String> {
519        crate::js_helpers::js_as_string(self)
520    }
521
522    /// Get a debug string representation of the value.
523    pub fn as_debug_string(&self) -> String {
524        crate::js_helpers::js_debug_string(self)
525    }
526}
527
528// Operator trait implementations for JsValue references
529use core::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub};
530
531impl Neg for &JsValue {
532    type Output = JsValue;
533    fn neg(self) -> JsValue {
534        JsValue::neg(self)
535    }
536}
537
538impl Not for &JsValue {
539    type Output = bool;
540
541    fn not(self) -> Self::Output {
542        JsValue::is_falsy(self)
543    }
544}
545
546impl BitAnd<&JsValue> for &JsValue {
547    type Output = JsValue;
548    fn bitand(self, rhs: &JsValue) -> JsValue {
549        JsValue::bit_and(self, rhs)
550    }
551}
552
553impl BitOr<&JsValue> for &JsValue {
554    type Output = JsValue;
555    fn bitor(self, rhs: &JsValue) -> JsValue {
556        JsValue::bit_or(self, rhs)
557    }
558}
559
560impl BitXor<&JsValue> for &JsValue {
561    type Output = JsValue;
562    fn bitxor(self, rhs: &JsValue) -> JsValue {
563        JsValue::bit_xor(self, rhs)
564    }
565}
566
567impl Shl<&JsValue> for &JsValue {
568    type Output = JsValue;
569    fn shl(self, rhs: &JsValue) -> JsValue {
570        JsValue::shl(self, rhs)
571    }
572}
573
574impl Shr<&JsValue> for &JsValue {
575    type Output = JsValue;
576    fn shr(self, rhs: &JsValue) -> JsValue {
577        JsValue::shr(self, rhs)
578    }
579}
580
581impl Add<&JsValue> for &JsValue {
582    type Output = JsValue;
583    fn add(self, rhs: &JsValue) -> JsValue {
584        JsValue::add(self, rhs)
585    }
586}
587
588impl Sub<&JsValue> for &JsValue {
589    type Output = JsValue;
590    fn sub(self, rhs: &JsValue) -> JsValue {
591        JsValue::sub(self, rhs)
592    }
593}
594
595impl Mul<&JsValue> for &JsValue {
596    type Output = JsValue;
597    fn mul(self, rhs: &JsValue) -> JsValue {
598        JsValue::mul(self, rhs)
599    }
600}
601
602impl Div<&JsValue> for &JsValue {
603    type Output = JsValue;
604    fn div(self, rhs: &JsValue) -> JsValue {
605        JsValue::div(self, rhs)
606    }
607}
608
609impl Rem<&JsValue> for &JsValue {
610    type Output = JsValue;
611    fn rem(self, rhs: &JsValue) -> JsValue {
612        JsValue::rem(self, rhs)
613    }
614}
615
616impl Neg for JsValue {
617    type Output = JsValue;
618    fn neg(self) -> JsValue {
619        JsValue::neg(&self)
620    }
621}
622
623impl Not for JsValue {
624    type Output = bool;
625    fn not(self) -> Self::Output {
626        JsValue::is_falsy(&self)
627    }
628}
629
630// Macro for binary operators with all ownership combinations
631macro_rules! impl_binary_op {
632    ($trait:ident, $method:ident, $js_method:ident) => {
633        // JsValue op JsValue
634        impl $trait for JsValue {
635            type Output = JsValue;
636            fn $method(self, rhs: JsValue) -> JsValue {
637                JsValue::$js_method(&self, &rhs)
638            }
639        }
640
641        // JsValue op &JsValue
642        impl $trait<&JsValue> for JsValue {
643            type Output = JsValue;
644            fn $method(self, rhs: &JsValue) -> JsValue {
645                JsValue::$js_method(&self, rhs)
646            }
647        }
648
649        // &JsValue op JsValue
650        impl<'a> $trait<JsValue> for &'a JsValue {
651            type Output = JsValue;
652            fn $method(self, rhs: JsValue) -> JsValue {
653                JsValue::$js_method(self, &rhs)
654            }
655        }
656    };
657}
658
659impl_binary_op!(Add, add, add);
660impl_binary_op!(Sub, sub, sub);
661impl_binary_op!(Mul, mul, mul);
662impl_binary_op!(Div, div, div);
663impl_binary_op!(Rem, rem, rem);
664impl_binary_op!(BitAnd, bitand, bit_and);
665impl_binary_op!(BitOr, bitor, bit_or);
666impl_binary_op!(BitXor, bitxor, bit_xor);
667impl_binary_op!(Shl, shl, shl);
668impl_binary_op!(Shr, shr, shr);
669
670impl From<bool> for JsValue {
671    fn from(val: bool) -> Self {
672        JsValue::from_bool(val)
673    }
674}