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 crate::{
6     export::ImplItem,
7     fnsig::{FnKind, FnSignature, ReceiverArg},
8     util::{
9         create_metadata_items, derive_ffi_traits, ident_to_string, mod_path, tagged_impl_header,
10     },
11 };
12 use proc_macro2::{Span, TokenStream};
13 use quote::{format_ident, quote};
14 use std::iter;
15 use syn::Ident;
16 
17 /// Generate a trait impl that calls foreign callbacks
18 ///
19 /// This generates:
20 ///    * A `repr(C)` VTable struct where each field is the FFI function for the trait method.
21 ///    * A FFI function for foreign code to set their VTable for the interface
22 ///    * An implementation of the trait using that VTable
trait_impl( mod_path: &str, trait_ident: &Ident, items: &[ImplItem], ) -> syn::Result<TokenStream>23 pub(super) fn trait_impl(
24     mod_path: &str,
25     trait_ident: &Ident,
26     items: &[ImplItem],
27 ) -> syn::Result<TokenStream> {
28     let trait_name = ident_to_string(trait_ident);
29     let trait_impl_ident = trait_impl_ident(&trait_name);
30     let vtable_type = format_ident!("UniFfiTraitVtable{trait_name}");
31     let vtable_cell = format_ident!("UNIFFI_TRAIT_CELL_{}", trait_name.to_uppercase());
32     let init_ident = Ident::new(
33         &uniffi_meta::init_callback_vtable_fn_symbol_name(mod_path, &trait_name),
34         Span::call_site(),
35     );
36     let methods = items
37         .iter()
38         .map(|item| match item {
39             ImplItem::Constructor(sig) => Err(syn::Error::new(
40                 sig.span,
41                 "Constructors not allowed in trait interfaces",
42             )),
43             ImplItem::Method(sig) => Ok(sig),
44         })
45         .collect::<syn::Result<Vec<_>>>()?;
46 
47     let vtable_fields = methods.iter()
48         .map(|sig| {
49             let ident = &sig.ident;
50             let param_names = sig.scaffolding_param_names();
51             let param_types = sig.scaffolding_param_types();
52             let lift_return = sig.lift_return_impl();
53             if !sig.is_async {
54                 quote! {
55                     #ident: extern "C" fn(
56                         uniffi_handle: u64,
57                         #(#param_names: #param_types,)*
58                         uniffi_out_return: &mut #lift_return::ReturnType,
59                         uniffi_out_call_status: &mut ::uniffi::RustCallStatus,
60                     ),
61                 }
62             } else {
63                 quote! {
64                     #ident: extern "C" fn(
65                         uniffi_handle: u64,
66                         #(#param_names: #param_types,)*
67                         uniffi_future_callback: ::uniffi::ForeignFutureCallback<#lift_return::ReturnType>,
68                         uniffi_callback_data: u64,
69                         uniffi_out_return: &mut ::uniffi::ForeignFuture,
70                     ),
71                 }
72             }
73         });
74 
75     let trait_impl_methods = methods
76         .iter()
77         .map(|sig| gen_method_impl(sig, &vtable_cell))
78         .collect::<syn::Result<Vec<_>>>()?;
79     let has_async_method = methods.iter().any(|m| m.is_async);
80     let impl_attributes = has_async_method.then(|| quote! { #[::async_trait::async_trait] });
81 
82     Ok(quote! {
83         struct #vtable_type {
84             #(#vtable_fields)*
85             uniffi_free: extern "C" fn(handle: u64),
86         }
87 
88         static #vtable_cell: ::uniffi::UniffiForeignPointerCell::<#vtable_type> = ::uniffi::UniffiForeignPointerCell::<#vtable_type>::new();
89 
90         #[no_mangle]
91         extern "C" fn #init_ident(vtable: ::std::ptr::NonNull<#vtable_type>) {
92             #vtable_cell.set(vtable);
93         }
94 
95         #[derive(Debug)]
96         struct #trait_impl_ident {
97             handle: u64,
98         }
99 
100         impl #trait_impl_ident {
101             fn new(handle: u64) -> Self {
102                 Self { handle }
103             }
104         }
105 
106         ::uniffi::deps::static_assertions::assert_impl_all!(#trait_impl_ident: ::core::marker::Send);
107 
108         #impl_attributes
109         impl #trait_ident for #trait_impl_ident {
110             #(#trait_impl_methods)*
111         }
112 
113         impl ::std::ops::Drop for #trait_impl_ident {
114             fn drop(&mut self) {
115                 let vtable = #vtable_cell.get();
116                 (vtable.uniffi_free)(self.handle);
117             }
118         }
119     })
120 }
121 
trait_impl_ident(trait_name: &str) -> Ident122 pub fn trait_impl_ident(trait_name: &str) -> Ident {
123     Ident::new(
124         &format!("UniFFICallbackHandler{trait_name}"),
125         Span::call_site(),
126     )
127 }
128 
ffi_converter_callback_interface_impl( trait_ident: &Ident, trait_impl_ident: &Ident, udl_mode: bool, ) -> TokenStream129 pub fn ffi_converter_callback_interface_impl(
130     trait_ident: &Ident,
131     trait_impl_ident: &Ident,
132     udl_mode: bool,
133 ) -> TokenStream {
134     let trait_name = ident_to_string(trait_ident);
135     let dyn_trait = quote! { dyn #trait_ident };
136     let box_dyn_trait = quote! { ::std::boxed::Box<#dyn_trait> };
137     let lift_impl_spec = tagged_impl_header("Lift", &box_dyn_trait, udl_mode);
138     let derive_ffi_traits = derive_ffi_traits(&box_dyn_trait, udl_mode, &["LiftRef", "LiftReturn"]);
139     let mod_path = match mod_path() {
140         Ok(p) => p,
141         Err(e) => return e.into_compile_error(),
142     };
143 
144     quote! {
145         #[doc(hidden)]
146         #[automatically_derived]
147         unsafe #lift_impl_spec {
148             type FfiType = u64;
149 
150             fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<Self> {
151                 Ok(::std::boxed::Box::new(<#trait_impl_ident>::new(v)))
152             }
153 
154             fn try_read(buf: &mut &[u8]) -> ::uniffi::deps::anyhow::Result<Self> {
155                 use uniffi::deps::bytes::Buf;
156                 ::uniffi::check_remaining(buf, 8)?;
157                 <Self as ::uniffi::Lift<crate::UniFfiTag>>::try_lift(buf.get_u64())
158             }
159 
160             const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(
161                 ::uniffi::metadata::codes::TYPE_CALLBACK_INTERFACE,
162             )
163             .concat_str(#mod_path)
164             .concat_str(#trait_name);
165         }
166 
167         #derive_ffi_traits
168     }
169 }
170 
171 /// Generate a single method for [trait_impl].  This implements a trait method by invoking a
172 /// foreign-supplied callback.
gen_method_impl(sig: &FnSignature, vtable_cell: &Ident) -> syn::Result<TokenStream>173 fn gen_method_impl(sig: &FnSignature, vtable_cell: &Ident) -> syn::Result<TokenStream> {
174     let FnSignature {
175         ident,
176         is_async,
177         return_ty,
178         kind,
179         receiver,
180         name,
181         span,
182         ..
183     } = sig;
184 
185     if !matches!(kind, FnKind::TraitMethod { .. }) {
186         return Err(syn::Error::new(
187             *span,
188             format!(
189                 "Internal UniFFI error: Unexpected function kind for callback interface {name}: {kind:?}",
190             ),
191         ));
192     }
193 
194     let self_param = match receiver {
195         Some(ReceiverArg::Ref) => quote! { &self },
196         Some(ReceiverArg::Arc) => quote! { self: Arc<Self> },
197         None => {
198             return Err(syn::Error::new(
199                 *span,
200                 "callback interface methods must take &self as their first argument",
201             ));
202         }
203     };
204 
205     let params = sig.params();
206     let lower_exprs = sig.args.iter().map(|a| {
207         let lower_impl = a.lower_impl();
208         let ident = &a.ident;
209         quote! { #lower_impl::lower(#ident) }
210     });
211 
212     let lift_return = sig.lift_return_impl();
213 
214     if !is_async {
215         Ok(quote! {
216             fn #ident(#self_param, #(#params),*) -> #return_ty {
217                 let vtable = #vtable_cell.get();
218                 let mut uniffi_call_status = ::uniffi::RustCallStatus::new();
219                 let mut uniffi_return_value: #lift_return::ReturnType = ::uniffi::FfiDefault::ffi_default();
220                 (vtable.#ident)(self.handle, #(#lower_exprs,)* &mut uniffi_return_value, &mut uniffi_call_status);
221                 #lift_return::lift_foreign_return(uniffi_return_value, uniffi_call_status)
222             }
223         })
224     } else {
225         Ok(quote! {
226             async fn #ident(#self_param, #(#params),*) -> #return_ty {
227                 let vtable = #vtable_cell.get();
228                 ::uniffi::foreign_async_call::<_, #return_ty, crate::UniFfiTag>(move |uniffi_future_callback, uniffi_future_callback_data| {
229                     let mut uniffi_foreign_future: ::uniffi::ForeignFuture = ::uniffi::FfiDefault::ffi_default();
230                     (vtable.#ident)(self.handle, #(#lower_exprs,)* uniffi_future_callback, uniffi_future_callback_data, &mut uniffi_foreign_future);
231                     uniffi_foreign_future
232                 }).await
233             }
234         })
235     }
236 }
237 
metadata_items( self_ident: &Ident, items: &[ImplItem], module_path: &str, docstring: String, ) -> syn::Result<Vec<TokenStream>>238 pub(super) fn metadata_items(
239     self_ident: &Ident,
240     items: &[ImplItem],
241     module_path: &str,
242     docstring: String,
243 ) -> syn::Result<Vec<TokenStream>> {
244     let trait_name = ident_to_string(self_ident);
245     let callback_interface_items = create_metadata_items(
246         "callback_interface",
247         &trait_name,
248         quote! {
249             ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CALLBACK_INTERFACE)
250                 .concat_str(#module_path)
251                 .concat_str(#trait_name)
252                 .concat_long_str(#docstring)
253         },
254         None,
255     );
256 
257     iter::once(Ok(callback_interface_items))
258         .chain(items.iter().map(|item| match item {
259             ImplItem::Method(sig) => sig.metadata_items_for_callback_interface(),
260             _ => unreachable!("traits have no constructors"),
261         }))
262         .collect()
263 }
264