1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 use proc_macro2::{Ident, Span, TokenStream};
6 use quote::{quote, quote_spanned};
7 
8 use uniffi_meta::ObjectImpl;
9 
10 use crate::{
11     export::{
12         attributes::ExportTraitArgs, callback_interface, gen_method_scaffolding, item::ImplItem,
13     },
14     object::interface_meta_static_var,
15     util::{ident_to_string, tagged_impl_header},
16 };
17 
gen_trait_scaffolding( mod_path: &str, args: ExportTraitArgs, self_ident: Ident, items: Vec<ImplItem>, udl_mode: bool, with_foreign: bool, docstring: String, ) -> syn::Result<TokenStream>18 pub(super) fn gen_trait_scaffolding(
19     mod_path: &str,
20     args: ExportTraitArgs,
21     self_ident: Ident,
22     items: Vec<ImplItem>,
23     udl_mode: bool,
24     with_foreign: bool,
25     docstring: String,
26 ) -> syn::Result<TokenStream> {
27     if let Some(rt) = args.async_runtime {
28         return Err(syn::Error::new_spanned(rt, "not supported for traits"));
29     }
30     let trait_name = ident_to_string(&self_ident);
31     let trait_impl = with_foreign.then(|| {
32         callback_interface::trait_impl(mod_path, &self_ident, &items)
33             .unwrap_or_else(|e| e.into_compile_error())
34     });
35 
36     let clone_fn_ident = Ident::new(
37         &uniffi_meta::clone_fn_symbol_name(mod_path, &trait_name),
38         Span::call_site(),
39     );
40     let free_fn_ident = Ident::new(
41         &uniffi_meta::free_fn_symbol_name(mod_path, &trait_name),
42         Span::call_site(),
43     );
44 
45     let helper_fn_tokens = quote! {
46         #[doc(hidden)]
47         #[no_mangle]
48         /// Clone a pointer to this object type
49         ///
50         /// Safety: Only pass pointers returned by a UniFFI call.  Do not pass pointers that were
51         /// passed to the free function.
52         pub unsafe extern "C" fn #clone_fn_ident(
53             ptr: *const ::std::ffi::c_void,
54             call_status: &mut ::uniffi::RustCallStatus
55         ) -> *const ::std::ffi::c_void {
56             uniffi::rust_call(call_status, || {
57                 let ptr = ptr as *mut std::sync::Arc<dyn #self_ident>;
58                 let arc = unsafe { ::std::sync::Arc::clone(&*ptr) };
59                 Ok(::std::boxed::Box::into_raw(::std::boxed::Box::new(arc)) as  *const ::std::ffi::c_void)
60             })
61         }
62 
63         #[doc(hidden)]
64         #[no_mangle]
65         /// Free a pointer to this object type
66         ///
67         /// Safety: Only pass pointers returned by a UniFFI call.  Do not pass pointers that were
68         /// passed to the free function.
69         ///
70         /// Note: clippy doesn't complain about this being unsafe, but it definitely is since it
71         /// calls `Box::from_raw`.
72         pub unsafe extern "C" fn #free_fn_ident(
73             ptr: *const ::std::ffi::c_void,
74             call_status: &mut ::uniffi::RustCallStatus
75         ) {
76             uniffi::rust_call(call_status, || {
77                 assert!(!ptr.is_null());
78                 drop(unsafe { ::std::boxed::Box::from_raw(ptr as *mut std::sync::Arc<dyn #self_ident>) });
79                 Ok(())
80             });
81         }
82     };
83 
84     let impl_tokens: TokenStream = items
85         .into_iter()
86         .map(|item| match item {
87             ImplItem::Method(sig) => gen_method_scaffolding(sig, &None, udl_mode),
88             _ => unreachable!("traits have no constructors"),
89         })
90         .collect::<syn::Result<_>>()?;
91 
92     let meta_static_var = (!udl_mode).then(|| {
93         let imp = if with_foreign {
94             ObjectImpl::CallbackTrait
95         } else {
96             ObjectImpl::Trait
97         };
98         interface_meta_static_var(&self_ident, imp, mod_path, docstring)
99             .unwrap_or_else(syn::Error::into_compile_error)
100     });
101     let ffi_converter_tokens = ffi_converter(mod_path, &self_ident, udl_mode, with_foreign);
102 
103     Ok(quote_spanned! { self_ident.span() =>
104         #meta_static_var
105         #helper_fn_tokens
106         #trait_impl
107         #impl_tokens
108         #ffi_converter_tokens
109     })
110 }
111 
ffi_converter( mod_path: &str, trait_ident: &Ident, udl_mode: bool, with_foreign: bool, ) -> TokenStream112 pub(crate) fn ffi_converter(
113     mod_path: &str,
114     trait_ident: &Ident,
115     udl_mode: bool,
116     with_foreign: bool,
117 ) -> TokenStream {
118     let impl_spec = tagged_impl_header("FfiConverterArc", &quote! { dyn #trait_ident }, udl_mode);
119     let lift_ref_impl_spec = tagged_impl_header("LiftRef", &quote! { dyn #trait_ident }, udl_mode);
120     let trait_name = ident_to_string(trait_ident);
121     let try_lift = if with_foreign {
122         let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name);
123         quote! {
124             fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc<Self>> {
125                 Ok(::std::sync::Arc::new(<#trait_impl_ident>::new(v as u64)))
126             }
127         }
128     } else {
129         quote! {
130             fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc<Self>> {
131                 unsafe {
132                     Ok(*::std::boxed::Box::from_raw(v as *mut ::std::sync::Arc<Self>))
133                 }
134             }
135         }
136     };
137     let metadata_code = if with_foreign {
138         quote! { ::uniffi::metadata::codes::TYPE_CALLBACK_TRAIT_INTERFACE }
139     } else {
140         quote! { ::uniffi::metadata::codes::TYPE_TRAIT_INTERFACE }
141     };
142 
143     quote! {
144         // All traits must be `Sync + Send`. The generated scaffolding will fail to compile
145         // if they are not, but unfortunately it fails with an unactionably obscure error message.
146         // By asserting the requirement explicitly, we help Rust produce a more scrutable error message
147         // and thus help the user debug why the requirement isn't being met.
148         uniffi::deps::static_assertions::assert_impl_all!(dyn #trait_ident: ::core::marker::Sync, ::core::marker::Send);
149 
150         unsafe #impl_spec {
151             type FfiType = *const ::std::os::raw::c_void;
152 
153             fn lower(obj: ::std::sync::Arc<Self>) -> Self::FfiType {
154                 ::std::boxed::Box::into_raw(::std::boxed::Box::new(obj)) as *const ::std::os::raw::c_void
155             }
156 
157             #try_lift
158 
159             fn write(obj: ::std::sync::Arc<Self>, buf: &mut Vec<u8>) {
160                 ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8);
161                 ::uniffi::deps::bytes::BufMut::put_u64(
162                     buf,
163                     <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(obj) as u64,
164                 );
165             }
166 
167             fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc<Self>> {
168                 ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8);
169                 ::uniffi::check_remaining(buf, 8)?;
170                 <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::try_lift(
171                     ::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType)
172             }
173 
174             const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(#metadata_code)
175                 .concat_str(#mod_path)
176                 .concat_str(#trait_name);
177         }
178 
179         unsafe #lift_ref_impl_spec {
180             type LiftType = ::std::sync::Arc<dyn #trait_ident>;
181         }
182     }
183 }
184