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