minijinja/value/
type_erase.rs

1/// Utility macro that creates a type erased version of a trait.
2///
3/// This is used in the engine to create a `DynObject` for an `Object`.
4/// For the exact use of this look at where the macro is invoked.
5macro_rules! type_erase {
6    ($v:vis trait $t_name:ident => $erased_t_name:ident {
7        $(fn $f:ident(&self $(, $p:ident: $t:ty $(,)?)*) $(-> $r:ty)?;)*
8        $(
9            impl $impl_name:path {
10                $(
11                    fn $f_impl:ident(
12                        &self $(, $p_impl:ident: $t_impl:ty $(,)?)*
13                    ) $(-> $r_impl:ty)?;
14                )*
15            }
16        )*
17    }) => {
18        #[doc = concat!("Type-erased version of [`", stringify!($t_name), "`]")]
19        $v struct $erased_t_name {
20            ptr: *const (),
21            vtable: *const (),
22        }
23
24        const _: () = {
25            struct VTable {
26                $($f: fn(*const (), $($p: $t),*) $(-> $r)?,)*
27                $($($f_impl: fn(*const (), $($p_impl: $t_impl),*) $(-> $r_impl)?,)*)*
28                __type_id: fn() -> std::any::TypeId,
29                __type_name: fn() -> &'static str,
30                __incref: fn(*const ()),
31                __decref: fn(*const ()),
32            }
33
34            #[inline(always)]
35            fn vt(e: &$erased_t_name) -> &VTable {
36                unsafe { &*(e.vtable as *const VTable) }
37            }
38
39            impl $erased_t_name {
40                #[doc = concat!("Returns a new boxed, type-erased [`", stringify!($t_name), "`].")]
41                $v fn new<T: $t_name + 'static>(v: std::sync::Arc<T>) -> Self {
42                    let ptr = std::sync::Arc::into_raw(v) as *const T as *const ();
43                    let vtable = &VTable {
44                        $(
45                            $f: |ptr, $($p),*| unsafe {
46                                let arc = std::mem::ManuallyDrop::new(std::sync::Arc::<T>::from_raw(ptr as *const T));
47                                <T as $t_name>::$f(&arc, $($p),*)
48                            },
49                        )*
50                        $($(
51                            $f_impl: |ptr, $($p_impl),*| unsafe {
52                                let arc = std::mem::ManuallyDrop::new(std::sync::Arc::<T>::from_raw(ptr as *const T));
53                                <T as $impl_name>::$f_impl(&*arc, $($p_impl),*)
54                            },
55                        )*)*
56                        __type_id: || std::any::TypeId::of::<T>(),
57                        __type_name: || std::any::type_name::<T>(),
58                        __incref: |ptr| unsafe {
59                            std::sync::Arc::<T>::increment_strong_count(ptr as *const T);
60                        },
61                        __decref: |ptr| unsafe {
62                            std::sync::Arc::from_raw(ptr as *const T);
63                        },
64                    };
65
66                    Self { ptr, vtable: vtable as *const VTable as *const () }
67                }
68
69                $(
70                    #[doc = concat!(
71                        "Calls [`", stringify!($t_name), "::", stringify!($f),
72                        "`] of the underlying boxed value."
73                    )]
74                    $v fn $f(&self, $($p: $t),*) $(-> $r)? {
75                        (vt(self).$f)(self.ptr, $($p),*)
76                    }
77                )*
78
79                /// Returns the type name of the concrete underlying type.
80                $v fn type_name(&self) -> &'static str {
81                    (vt(self).__type_name)()
82                }
83
84                /// Downcast to `T` if the boxed value holds a `T`.
85                ///
86                /// This works like [`Any::downcast_ref`](std::any::Any#method.downcast_ref).
87                $v fn downcast_ref<T: 'static>(&self) -> Option<&T> {
88                    if (vt(self).__type_id)() == std::any::TypeId::of::<T>() {
89                        unsafe {
90                            return Some(&*(self.ptr as *const T));
91                        }
92                    }
93
94                    None
95                }
96
97                /// Downcast to `T` if the boxed value holds a `T`.
98                ///
99                /// This is similar to [`downcast_ref`](Self::downcast_ref) but returns the [`Arc`].
100                $v fn downcast<T: 'static>(&self) -> Option<Arc<T>> {
101                    if (vt(self).__type_id)() == std::any::TypeId::of::<T>() {
102                        unsafe {
103                            std::sync::Arc::<T>::increment_strong_count(self.ptr as *const T);
104                            return Some(std::sync::Arc::<T>::from_raw(self.ptr as *const T));
105                        }
106                    }
107
108                    None
109                }
110
111                /// Checks if the boxed value is a `T`.
112                ///
113                /// This works like [`Any::is`](std::any::Any#method.is).
114                $v fn is<T: 'static>(&self) -> bool {
115                    self.downcast::<T>().is_some()
116                }
117            }
118
119            impl Clone for $erased_t_name {
120                fn clone(&self) -> Self {
121                    (vt(self).__incref)(self.ptr);
122                    Self {
123                        ptr: self.ptr,
124                        vtable: self.vtable,
125                    }
126                }
127            }
128
129            impl Drop for $erased_t_name {
130                fn drop(&mut self) {
131                    (vt(self).__decref)(self.ptr);
132                }
133            }
134
135            impl<T: $t_name + 'static> From<Arc<T>> for $erased_t_name {
136                fn from(value: Arc<T>) -> Self {
137                    $erased_t_name::new(value)
138                }
139            }
140
141            $(
142                impl $impl_name for $erased_t_name {
143                    $(
144                        fn $f_impl(&self, $($p_impl: $t_impl),*) $(-> $r_impl)? {
145                            (vt(self).$f_impl)(self.ptr, $($p_impl),*)
146                        }
147                    )*
148                }
149            )*
150        };
151    };
152}