Skip to main content

wry_bindgen/
convert.rs

1//! Conversion traits for wasm-bindgen API compatibility.
2//!
3//! These traits provide compatibility with code that uses wasm-bindgen's
4//! low-level ABI conversion types.
5
6use crate::JsValue;
7use crate::batch::with_runtime;
8use crate::encode::{BinaryDecode, BinaryEncode, EncodeTypeDef};
9use core::mem::ManuallyDrop;
10use core::ops::Deref;
11
12/// Marker for types accepted by wasm-bindgen-shaped APIs that conceptually
13/// convert into a Wasm ABI value.
14///
15/// Wry-bindgen does not use wasm-bindgen's raw ABI transport on desktop; the
16/// generated glue uses the binary protocol instead. These traits are kept as
17/// markers for `js-sys`/`web-sys` signatures that use wasm-bindgen's unstable
18/// conversion traits as bounds.
19pub trait IntoWasmAbi: BinaryEncode + EncodeTypeDef {
20    #[inline]
21    fn into_abi(self) -> u32
22    where
23        Self: Sized + IntoAbiId,
24    {
25        self.into_abi_id()
26    }
27}
28
29/// Marker for types accepted by wasm-bindgen-shaped APIs that conceptually
30/// convert from a Wasm ABI value.
31pub trait FromWasmAbi: BinaryDecode + EncodeTypeDef {
32    /// Recreate a JS-reference-like value from a heap id.
33    ///
34    /// This is only a compatibility hook for crates that preserve `JsValue`
35    /// references through serde or similar adapters. Generated Wry bindings use
36    /// the binary protocol instead.
37    ///
38    /// # Safety
39    ///
40    /// The caller must pass an id for a live JavaScript heap value that is valid
41    /// for `Self`.
42    #[inline]
43    unsafe fn from_abi(js: u32) -> Self
44    where
45        Self: Sized + FromAbiId,
46    {
47        unsafe { Self::from_abi_id(js) }
48    }
49}
50
51/// Marker for types that may appear as `Option<T>` in wasm-bindgen-shaped APIs.
52pub trait OptionIntoWasmAbi: IntoWasmAbi {}
53
54/// Marker for types that may be received as `Option<T>` in wasm-bindgen-shaped APIs.
55pub trait OptionFromWasmAbi: FromWasmAbi {}
56
57/// Marker for values that have a wasm-bindgen ABI representation.
58pub trait WasmAbi {}
59
60/// Marker for types that can be borrowed from wasm-bindgen-shaped APIs.
61pub trait RefFromWasmAbi {
62    /// Recreate a non-dropping reference anchor from a heap id.
63    ///
64    /// # Safety
65    ///
66    /// The caller must pass an id for a live JavaScript heap value that remains
67    /// valid for the returned anchor.
68    #[inline]
69    unsafe fn ref_from_abi(js: u32) -> AbiRef<Self>
70    where
71        Self: Sized + FromAbiId,
72    {
73        AbiRef(ManuallyDrop::new(unsafe { Self::from_abi_id(js) }))
74    }
75}
76
77/// Non-dropping anchor returned by `RefFromWasmAbi::ref_from_abi`.
78pub struct AbiRef<T>(ManuallyDrop<T>);
79
80impl<T> Deref for AbiRef<T> {
81    type Target = T;
82
83    #[inline]
84    fn deref(&self) -> &Self::Target {
85        &self.0
86    }
87}
88
89impl<T> AsRef<T> for AbiRef<T> {
90    #[inline]
91    fn as_ref(&self) -> &T {
92        self
93    }
94}
95
96#[doc(hidden)]
97pub trait IntoAbiId {
98    fn into_abi_id(self) -> u32;
99}
100
101#[doc(hidden)]
102pub trait FromAbiId {
103    unsafe fn from_abi_id(js: u32) -> Self;
104}
105
106impl<T> IntoAbiId for T
107where
108    T: AsRef<JsValue>,
109{
110    #[inline]
111    fn into_abi_id(self) -> u32 {
112        let id = self.as_ref().id();
113        core::mem::forget(self);
114        id as u32
115    }
116}
117
118impl<T> FromAbiId for T
119where
120    T: JsCast,
121{
122    #[inline]
123    unsafe fn from_abi_id(js: u32) -> Self {
124        T::unchecked_from_js(JsValue::from_id(js as u64))
125    }
126}
127
128impl<T> IntoWasmAbi for T where T: BinaryEncode + EncodeTypeDef {}
129impl<T> FromWasmAbi for T where T: BinaryDecode + EncodeTypeDef {}
130impl<T> OptionIntoWasmAbi for T where T: IntoWasmAbi {}
131impl<T> OptionFromWasmAbi for T where T: FromWasmAbi {}
132impl<T: ?Sized> WasmAbi for T {}
133impl<T: ?Sized> RefFromWasmAbi for T {}
134
135/// Converts a `JsValue` into a Rust type by checking at runtime.
136pub trait TryFromJsValue: Sized {
137    fn try_from_js_value(value: JsValue) -> Result<Self, JsValue> {
138        Self::try_from_js_value_ref(&value).ok_or(value)
139    }
140
141    fn try_from_js_value_ref(value: &JsValue) -> Option<Self>;
142}
143
144use crate::ipc::{DecodeError, DecodedData};
145use crate::{__rt::marker::ErasableGeneric, JsCast};
146use core::marker::PhantomData;
147
148/// Marker for type-safe generic upcast relationships.
149pub trait UpcastFrom<S: ?Sized> {}
150
151/// Type-safe generic upcast helper.
152pub trait Upcast<T: ?Sized> {
153    #[inline]
154    fn upcast(&self) -> &T
155    where
156        Self: ErasableGeneric,
157        T: Sized + ErasableGeneric<Repr = <Self as ErasableGeneric>::Repr>,
158    {
159        unsafe { &*(self as *const Self as *const T) }
160    }
161
162    #[inline]
163    fn upcast_into(self) -> T
164    where
165        Self: Sized + ErasableGeneric,
166        T: Sized + ErasableGeneric<Repr = <Self as ErasableGeneric>::Repr>,
167    {
168        unsafe { core::mem::transmute_copy(&core::mem::ManuallyDrop::new(self)) }
169    }
170}
171
172impl<S, T> Upcast<T> for S
173where
174    T: UpcastFrom<S> + ?Sized,
175    S: ?Sized,
176{
177}
178
179impl<'a, T, Target> UpcastFrom<&'a T> for &'a Target where Target: UpcastFrom<T> {}
180impl<'a, T, Target> UpcastFrom<&'a mut T> for &'a mut Target where Target: UpcastFrom<T> {}
181
182macro_rules! impl_tuple_upcast {
183    ([$($ty:ident)+] [$($target:ident)+]) => {
184        impl<$($ty,)+ $($target,)+> UpcastFrom<($($ty,)+)> for ($($target,)+)
185        where
186            $($ty: JsGeneric,)+
187            $($target: JsGeneric + UpcastFrom<$ty>,)+
188        {
189        }
190
191        impl<$($ty,)+ $($target,)+> UpcastFrom<($($ty,)+)> for crate::sys::JsOption<($($target,)+)>
192        where
193            $($ty: JsGeneric,)+
194            $($target: JsGeneric + UpcastFrom<$ty>,)+
195        {
196        }
197    };
198}
199
200impl_tuple_upcast!([T1][Target1]);
201impl_tuple_upcast!([T1 T2] [Target1 Target2]);
202impl_tuple_upcast!([T1 T2 T3] [Target1 Target2 Target3]);
203impl_tuple_upcast!([T1 T2 T3 T4] [Target1 Target2 Target3 Target4]);
204impl_tuple_upcast!([T1 T2 T3 T4 T5] [Target1 Target2 Target3 Target4 Target5]);
205impl_tuple_upcast!([T1 T2 T3 T4 T5 T6] [Target1 Target2 Target3 Target4 Target5 Target6]);
206impl_tuple_upcast!([T1 T2 T3 T4 T5 T6 T7] [Target1 Target2 Target3 Target4 Target5 Target6 Target7]);
207impl_tuple_upcast!([T1 T2 T3 T4 T5 T6 T7 T8] [Target1 Target2 Target3 Target4 Target5 Target6 Target7 Target8]);
208
209macro_rules! impl_fn_upcasts {
210    () => {
211        impl_fn_upcasts!(@arities
212            [0 []]
213            [1 [A1 B1] O1]
214            [2 [A1 B1 A2 B2] O2]
215            [3 [A1 B1 A2 B2 A3 B3] O3]
216            [4 [A1 B1 A2 B2 A3 B3 A4 B4] O4]
217            [5 [A1 B1 A2 B2 A3 B3 A4 B4 A5 B5] O5]
218            [6 [A1 B1 A2 B2 A3 B3 A4 B4 A5 B5 A6 B6] O6]
219            [7 [A1 B1 A2 B2 A3 B3 A4 B4 A5 B5 A6 B6 A7 B7] O7]
220            [8 [A1 B1 A2 B2 A3 B3 A4 B4 A5 B5 A6 B6 A7 B7 A8 B8] O8]
221        );
222    };
223
224    (@arities) => {};
225
226    (@arities [$n:tt $args:tt $($opt:ident)?] $([$rest_n:tt $rest_args:tt $($rest_opt:ident)?])*) => {
227        impl_fn_upcasts!(@same $args);
228        impl_fn_upcasts!(@cross_all $args [] $([$rest_n $rest_args $($rest_opt)?])*);
229        impl_fn_upcasts!(@arities $([$rest_n $rest_args $($rest_opt)?])*);
230    };
231
232    (@same []) => {
233        impl<R1, R2> UpcastFrom<fn() -> R1> for fn() -> R2
234        where
235            R2: UpcastFrom<R1>,
236        {
237        }
238
239        impl<'a, R1, R2> UpcastFrom<dyn Fn() -> R1 + 'a> for dyn Fn() -> R2 + 'a
240        where
241            R2: UpcastFrom<R1>,
242        {
243        }
244
245        impl<'a, R1, R2> UpcastFrom<dyn FnMut() -> R1 + 'a> for dyn FnMut() -> R2 + 'a
246        where
247            R2: UpcastFrom<R1>,
248        {
249        }
250    };
251
252    (@same [$($A1:ident $A2:ident)+]) => {
253        impl<R1, R2, $($A1, $A2),+> UpcastFrom<fn($($A1),+) -> R1> for fn($($A2),+) -> R2
254        where
255            R2: UpcastFrom<R1>,
256            $($A1: UpcastFrom<$A2>,)+
257        {
258        }
259
260        impl<'a, R1, R2, $($A1, $A2),+> UpcastFrom<dyn Fn($($A1),+) -> R1 + 'a> for dyn Fn($($A2),+) -> R2 + 'a
261        where
262            R2: UpcastFrom<R1>,
263            $($A1: UpcastFrom<$A2>,)+
264        {
265        }
266
267        impl<'a, R1, R2, $($A1, $A2),+> UpcastFrom<dyn FnMut($($A1),+) -> R1 + 'a> for dyn FnMut($($A2),+) -> R2 + 'a
268        where
269            R2: UpcastFrom<R1>,
270            $($A1: UpcastFrom<$A2>,)+
271        {
272        }
273    };
274
275    (@cross_all $args:tt $opts:tt) => {};
276
277    (@cross_all $args:tt [$($opts:ident)*] [$next_n:tt $next_args:tt $next_opt:ident] $([$rest_n:tt $rest_args:tt $($rest_opt:ident)?])*) => {
278        impl_fn_upcasts!(@extend $args [$($opts)* $next_opt]);
279        impl_fn_upcasts!(@shrink $args [$($opts)* $next_opt]);
280        impl_fn_upcasts!(@cross_all $args [$($opts)* $next_opt] $([$rest_n $rest_args $($rest_opt)?])*);
281    };
282
283    (@extend [] [$($O:ident)+]) => {
284        impl<R1, R2, $($O),+> UpcastFrom<fn() -> R1> for fn($($O),+) -> R2
285        where
286            R2: UpcastFrom<R1>,
287            $($O: UpcastFrom<crate::sys::Undefined>,)+
288        {
289        }
290
291        impl<'a, R1, R2, $($O),+> UpcastFrom<dyn Fn() -> R1 + 'a> for dyn Fn($($O),+) -> R2 + 'a
292        where
293            R2: UpcastFrom<R1>,
294            $($O: UpcastFrom<crate::sys::Undefined>,)+
295        {
296        }
297
298        impl<'a, R1, R2, $($O),+> UpcastFrom<dyn FnMut() -> R1 + 'a> for dyn FnMut($($O),+) -> R2 + 'a
299        where
300            R2: UpcastFrom<R1>,
301            $($O: UpcastFrom<crate::sys::Undefined>,)+
302        {
303        }
304    };
305
306    (@extend [$($A1:ident $A2:ident)+] [$($O:ident)+]) => {
307        impl<R1, R2, $($A1, $A2,)+ $($O),+> UpcastFrom<fn($($A1),+) -> R1> for fn($($A2,)+ $($O),+) -> R2
308        where
309            R2: UpcastFrom<R1>,
310            $($A1: UpcastFrom<$A2>,)+
311            $($O: UpcastFrom<crate::sys::Undefined>,)+
312        {
313        }
314
315        impl<'a, R1, R2, $($A1, $A2,)+ $($O),+> UpcastFrom<dyn Fn($($A1),+) -> R1 + 'a> for dyn Fn($($A2,)+ $($O),+) -> R2 + 'a
316        where
317            R2: UpcastFrom<R1>,
318            $($A1: UpcastFrom<$A2>,)+
319            $($O: UpcastFrom<crate::sys::Undefined>,)+
320        {
321        }
322
323        impl<'a, R1, R2, $($A1, $A2,)+ $($O),+> UpcastFrom<dyn FnMut($($A1),+) -> R1 + 'a> for dyn FnMut($($A2,)+ $($O),+) -> R2 + 'a
324        where
325            R2: UpcastFrom<R1>,
326            $($A1: UpcastFrom<$A2>,)+
327            $($O: UpcastFrom<crate::sys::Undefined>,)+
328        {
329        }
330    };
331
332    (@shrink [] [$($O:ident)+]) => {
333        impl<R1, R2, $($O),+> UpcastFrom<fn($($O),+) -> R1> for fn() -> R2
334        where
335            R2: UpcastFrom<R1>,
336            $($O: UpcastFrom<crate::sys::Undefined>,)+
337        {
338        }
339
340        impl<'a, R1, R2, $($O),+> UpcastFrom<dyn Fn($($O),+) -> R1 + 'a> for dyn Fn() -> R2 + 'a
341        where
342            R2: UpcastFrom<R1>,
343            $($O: UpcastFrom<crate::sys::Undefined>,)+
344        {
345        }
346
347        impl<'a, R1, R2, $($O),+> UpcastFrom<dyn FnMut($($O),+) -> R1 + 'a> for dyn FnMut() -> R2 + 'a
348        where
349            R2: UpcastFrom<R1>,
350            $($O: UpcastFrom<crate::sys::Undefined>,)+
351        {
352        }
353    };
354
355    (@shrink [$($A1:ident $A2:ident)+] [$($O:ident)+]) => {
356        impl<R1, R2, $($A1, $A2,)+ $($O),+> UpcastFrom<fn($($A1,)+ $($O),+) -> R1> for fn($($A2),+) -> R2
357        where
358            R2: UpcastFrom<R1>,
359            $($A1: UpcastFrom<$A2>,)+
360            $($O: UpcastFrom<crate::sys::Undefined>,)+
361        {
362        }
363
364        impl<'a, R1, R2, $($A1, $A2,)+ $($O),+> UpcastFrom<dyn Fn($($A1,)+ $($O),+) -> R1 + 'a> for dyn Fn($($A2),+) -> R2 + 'a
365        where
366            R2: UpcastFrom<R1>,
367            $($A1: UpcastFrom<$A2>,)+
368            $($O: UpcastFrom<crate::sys::Undefined>,)+
369        {
370        }
371
372        impl<'a, R1, R2, $($A1, $A2,)+ $($O),+> UpcastFrom<dyn FnMut($($A1,)+ $($O),+) -> R1 + 'a> for dyn FnMut($($A2),+) -> R2 + 'a
373        where
374            R2: UpcastFrom<R1>,
375            $($A1: UpcastFrom<$A2>,)+
376            $($O: UpcastFrom<crate::sys::Undefined>,)+
377        {
378        }
379    };
380}
381
382impl_fn_upcasts!();
383
384/// Convenience bound for JS values whose generic parameters erase to `JsValue`.
385pub trait JsGeneric:
386    crate::__rt::marker::ErasableGeneric<Repr = JsValue>
387    + UpcastFrom<Self>
388    + Upcast<Self>
389    + Upcast<JsValue>
390    + JsCast
391    + crate::encode::EncodeTypeDef
392    + crate::encode::BinaryEncode
393    + crate::encode::BinaryDecode
394    + crate::encode::BatchableResult
395    + 'static
396{
397}
398
399impl<T> JsGeneric for T where
400    T: crate::__rt::marker::ErasableGeneric<Repr = JsValue>
401        + UpcastFrom<T>
402        + Upcast<JsValue>
403        + JsCast
404        + crate::encode::EncodeTypeDef
405        + crate::encode::BinaryEncode
406        + crate::encode::BinaryDecode
407        + crate::encode::BatchableResult
408        + 'static
409{
410}
411
412/// Converts a value into its canonical JS-generic representation.
413pub trait IntoJsGeneric {
414    type JsCanon: JsGeneric;
415
416    fn to_js(self) -> Self::JsCanon;
417}
418
419impl IntoJsGeneric for JsValue {
420    type JsCanon = JsValue;
421
422    #[inline]
423    fn to_js(self) -> JsValue {
424        self
425    }
426}
427
428impl<T: IntoJsGeneric + Clone> IntoJsGeneric for &T {
429    type JsCanon = T::JsCanon;
430
431    #[inline]
432    fn to_js(self) -> T::JsCanon {
433        self.clone().to_js()
434    }
435}
436
437impl UpcastFrom<JsValue> for JsValue {}
438
439/// Trait for types that can be decoded as references from binary data.
440///
441/// This is the wry-bindgen equivalent of wasm-bindgen's `RefFromWasmAbi`.
442/// The `Anchor` type holds the decoded value and keeps the reference valid
443/// during callback invocation.
444pub trait RefFromBinaryDecode {
445    /// The anchor type that keeps the decoded reference valid.
446    type Anchor: core::ops::Deref<Target = Self>;
447
448    /// Decode a reference anchor from binary data.
449    fn ref_decode(decoder: &mut DecodedData) -> Result<Self::Anchor, DecodeError>;
450}
451
452/// Anchor type for JsCast references.
453///
454/// This holds a `JsValue` and provides a reference to the target type `T`
455/// through the `JsCast` trait.
456pub struct JsCastAnchor<T: JsCast> {
457    value: JsValue,
458    _marker: PhantomData<T>,
459}
460
461impl<T: JsCast> core::ops::Deref for JsCastAnchor<T> {
462    type Target = T;
463
464    fn deref(&self) -> &Self::Target {
465        T::unchecked_from_js_ref(&self.value)
466    }
467}
468
469// Blanket implementation for all JsCast types (including JsValue)
470impl<T: JsCast + 'static> RefFromBinaryDecode for T {
471    type Anchor = JsCastAnchor<T>;
472
473    fn ref_decode(_decoder: &mut DecodedData) -> Result<Self::Anchor, DecodeError> {
474        // For borrowed refs, we use the borrow stack (indices 1-127) instead of heap IDs.
475        // JS puts the value on its borrow stack without sending an ID, so we sync by
476        // getting the next borrow ID from our batch state.
477        let id = with_runtime(|runtime| runtime.get_next_borrow_id());
478        let value = JsValue::from_id(id);
479        Ok(JsCastAnchor {
480            value,
481            _marker: PhantomData,
482        })
483    }
484}