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, TokenStream};
6 use quote::quote;
7 use std::iter;
8 
9 use super::attributes::AsyncRuntime;
10 use crate::fnsig::{FnKind, FnSignature};
11 
gen_fn_scaffolding( sig: FnSignature, ar: &Option<AsyncRuntime>, udl_mode: bool, ) -> syn::Result<TokenStream>12 pub(super) fn gen_fn_scaffolding(
13     sig: FnSignature,
14     ar: &Option<AsyncRuntime>,
15     udl_mode: bool,
16 ) -> syn::Result<TokenStream> {
17     if sig.receiver.is_some() {
18         return Err(syn::Error::new(
19             sig.span,
20             "Unexpected self param (Note: uniffi::export must be used on the impl block, not its containing fn's)"
21         ));
22     }
23     if !sig.is_async {
24         if let Some(async_runtime) = ar {
25             return Err(syn::Error::new_spanned(
26                 async_runtime,
27                 "this attribute is only allowed on async functions",
28             ));
29         }
30     }
31     let metadata_items = (!udl_mode).then(|| {
32         sig.metadata_items()
33             .unwrap_or_else(syn::Error::into_compile_error)
34     });
35     let scaffolding_func = gen_ffi_function(&sig, ar, udl_mode)?;
36     Ok(quote! {
37         #scaffolding_func
38         #metadata_items
39     })
40 }
41 
gen_constructor_scaffolding( sig: FnSignature, ar: &Option<AsyncRuntime>, udl_mode: bool, ) -> syn::Result<TokenStream>42 pub(super) fn gen_constructor_scaffolding(
43     sig: FnSignature,
44     ar: &Option<AsyncRuntime>,
45     udl_mode: bool,
46 ) -> syn::Result<TokenStream> {
47     if sig.receiver.is_some() {
48         return Err(syn::Error::new(
49             sig.span,
50             "constructors must not have a self parameter",
51         ));
52     }
53     let metadata_items = (!udl_mode).then(|| {
54         sig.metadata_items()
55             .unwrap_or_else(syn::Error::into_compile_error)
56     });
57     let scaffolding_func = gen_ffi_function(&sig, ar, udl_mode)?;
58     Ok(quote! {
59         #scaffolding_func
60         #metadata_items
61     })
62 }
63 
gen_method_scaffolding( sig: FnSignature, ar: &Option<AsyncRuntime>, udl_mode: bool, ) -> syn::Result<TokenStream>64 pub(super) fn gen_method_scaffolding(
65     sig: FnSignature,
66     ar: &Option<AsyncRuntime>,
67     udl_mode: bool,
68 ) -> syn::Result<TokenStream> {
69     let scaffolding_func = if sig.receiver.is_none() {
70         return Err(syn::Error::new(
71             sig.span,
72             "associated functions are not currently supported",
73         ));
74     } else {
75         gen_ffi_function(&sig, ar, udl_mode)?
76     };
77 
78     let metadata_items = (!udl_mode).then(|| {
79         sig.metadata_items()
80             .unwrap_or_else(syn::Error::into_compile_error)
81     });
82     Ok(quote! {
83         #scaffolding_func
84         #metadata_items
85     })
86 }
87 
88 // Pieces of code for the scaffolding function
89 struct ScaffoldingBits {
90     /// Parameter names for the scaffolding function
91     param_names: Vec<TokenStream>,
92     /// Parameter types for the scaffolding function
93     param_types: Vec<TokenStream>,
94     /// Lift closure.  See `FnSignature::lift_closure` for an explanation of this.
95     lift_closure: TokenStream,
96     /// Expression to call the Rust function after a successful lift.
97     rust_fn_call: TokenStream,
98     /// Convert the result of `rust_fn_call`, stored in a variable named `uniffi_result` into its final value.
99     /// This is used to do things like error conversion / Arc wrapping
100     convert_result: TokenStream,
101 }
102 
103 impl ScaffoldingBits {
new_for_function(sig: &FnSignature, udl_mode: bool) -> Self104     fn new_for_function(sig: &FnSignature, udl_mode: bool) -> Self {
105         let ident = &sig.ident;
106         let call_params = sig.rust_call_params(false);
107         let rust_fn_call = quote! { #ident(#call_params) };
108         // UDL mode adds an extra conversion (#1749)
109         let convert_result = if udl_mode && sig.looks_like_result {
110             quote! { uniffi_result.map_err(::std::convert::Into::into) }
111         } else {
112             quote! { uniffi_result }
113         };
114 
115         Self {
116             param_names: sig.scaffolding_param_names().collect(),
117             param_types: sig.scaffolding_param_types().collect(),
118             lift_closure: sig.lift_closure(None),
119             rust_fn_call,
120             convert_result,
121         }
122     }
123 
new_for_method( sig: &FnSignature, self_ident: &Ident, is_trait: bool, udl_mode: bool, ) -> Self124     fn new_for_method(
125         sig: &FnSignature,
126         self_ident: &Ident,
127         is_trait: bool,
128         udl_mode: bool,
129     ) -> Self {
130         let ident = &sig.ident;
131         let lift_impl = if is_trait {
132             quote! {
133                 <::std::sync::Arc<dyn #self_ident> as ::uniffi::Lift<crate::UniFfiTag>>
134             }
135         } else {
136             quote! {
137                 <::std::sync::Arc<#self_ident> as ::uniffi::Lift<crate::UniFfiTag>>
138             }
139         };
140         let try_lift_self = if is_trait {
141             // For trait interfaces we need to special case this.  Trait interfaces normally lift
142             // foreign trait impl pointers.  However, for a method call, we want to lift a Rust
143             // pointer.
144             quote! {
145                 {
146                     let boxed_foreign_arc = unsafe { Box::from_raw(uniffi_self_lowered as *mut ::std::sync::Arc<dyn #self_ident>) };
147                     // Take a clone for our own use.
148                     Ok(*boxed_foreign_arc)
149                 }
150             }
151         } else {
152             quote! { #lift_impl::try_lift(uniffi_self_lowered) }
153         };
154 
155         let lift_closure = sig.lift_closure(Some(quote! {
156             match #try_lift_self {
157                 Ok(v) => v,
158                 Err(e) => return Err(("self", e))
159             }
160         }));
161         let call_params = sig.rust_call_params(true);
162         let rust_fn_call = quote! { uniffi_args.0.#ident(#call_params) };
163         // UDL mode adds an extra conversion (#1749)
164         let convert_result = if udl_mode && sig.looks_like_result {
165             quote! { uniffi_result .map_err(::std::convert::Into::into) }
166         } else {
167             quote! { uniffi_result }
168         };
169 
170         Self {
171             param_names: iter::once(quote! { uniffi_self_lowered })
172                 .chain(sig.scaffolding_param_names())
173                 .collect(),
174             param_types: iter::once(quote! { #lift_impl::FfiType })
175                 .chain(sig.scaffolding_param_types())
176                 .collect(),
177             lift_closure,
178             rust_fn_call,
179             convert_result,
180         }
181     }
182 
new_for_constructor(sig: &FnSignature, self_ident: &Ident, udl_mode: bool) -> Self183     fn new_for_constructor(sig: &FnSignature, self_ident: &Ident, udl_mode: bool) -> Self {
184         let ident = &sig.ident;
185         let call_params = sig.rust_call_params(false);
186         let rust_fn_call = quote! { #self_ident::#ident(#call_params) };
187         // UDL mode adds extra conversions (#1749)
188         let convert_result = match (udl_mode, sig.looks_like_result) {
189             // For UDL
190             (true, false) => quote! { ::std::sync::Arc::new(uniffi_result) },
191             (true, true) => {
192                 quote! { uniffi_result.map(::std::sync::Arc::new).map_err(::std::convert::Into::into) }
193             }
194             (false, _) => quote! { uniffi_result },
195         };
196 
197         Self {
198             param_names: sig.scaffolding_param_names().collect(),
199             param_types: sig.scaffolding_param_types().collect(),
200             lift_closure: sig.lift_closure(None),
201             rust_fn_call,
202             convert_result,
203         }
204     }
205 }
206 
207 /// Generate a scaffolding function
208 ///
209 /// `pre_fn_call` is the statements that we should execute before the rust call
210 /// `rust_fn` is the Rust function to call.
gen_ffi_function( sig: &FnSignature, ar: &Option<AsyncRuntime>, udl_mode: bool, ) -> syn::Result<TokenStream>211 pub(super) fn gen_ffi_function(
212     sig: &FnSignature,
213     ar: &Option<AsyncRuntime>,
214     udl_mode: bool,
215 ) -> syn::Result<TokenStream> {
216     let ScaffoldingBits {
217         param_names,
218         param_types,
219         lift_closure,
220         rust_fn_call,
221         convert_result,
222     } = match &sig.kind {
223         FnKind::Function => ScaffoldingBits::new_for_function(sig, udl_mode),
224         FnKind::Method { self_ident } => {
225             ScaffoldingBits::new_for_method(sig, self_ident, false, udl_mode)
226         }
227         FnKind::TraitMethod { self_ident, .. } => {
228             ScaffoldingBits::new_for_method(sig, self_ident, true, udl_mode)
229         }
230         FnKind::Constructor { self_ident } => {
231             ScaffoldingBits::new_for_constructor(sig, self_ident, udl_mode)
232         }
233     };
234     // Scaffolding functions are logically `pub`, but we don't use that in UDL mode since UDL has
235     // historically not required types to be `pub`
236     let vis = match udl_mode {
237         false => quote! { pub },
238         true => quote! {},
239     };
240 
241     let ffi_ident = sig.scaffolding_fn_ident()?;
242     let name = &sig.name;
243     let return_ty = &sig.return_ty;
244     let return_impl = &sig.lower_return_impl();
245 
246     Ok(if !sig.is_async {
247         quote! {
248             #[doc(hidden)]
249             #[no_mangle]
250             #vis extern "C" fn #ffi_ident(
251                 #(#param_names: #param_types,)*
252                 call_status: &mut ::uniffi::RustCallStatus,
253             ) -> #return_impl::ReturnType {
254                 ::uniffi::deps::log::debug!(#name);
255                 let uniffi_lift_args = #lift_closure;
256                 ::uniffi::rust_call(call_status, || {
257                     #return_impl::lower_return(
258                         match uniffi_lift_args() {
259                             Ok(uniffi_args) => {
260                                 let uniffi_result = #rust_fn_call;
261                                 #convert_result
262                             }
263                             Err((arg_name, anyhow_error)) => {
264                                 #return_impl::handle_failed_lift(arg_name, anyhow_error)
265                             },
266                         }
267                     )
268                 })
269             }
270         }
271     } else {
272         let mut future_expr = rust_fn_call;
273         if matches!(ar, Some(AsyncRuntime::Tokio(_))) {
274             future_expr = quote! { ::uniffi::deps::async_compat::Compat::new(#future_expr) }
275         }
276 
277         quote! {
278             #[doc(hidden)]
279             #[no_mangle]
280             pub extern "C" fn #ffi_ident(#(#param_names: #param_types,)*) -> ::uniffi::Handle {
281                 ::uniffi::deps::log::debug!(#name);
282                 let uniffi_lift_args = #lift_closure;
283                 match uniffi_lift_args() {
284                     Ok(uniffi_args) => {
285                         ::uniffi::rust_future_new::<_, #return_ty, _>(
286                             async move {
287                                 let uniffi_result = #future_expr.await;
288                                 #convert_result
289                             },
290                             crate::UniFfiTag
291                         )
292                     },
293                     Err((arg_name, anyhow_error)) => {
294                         ::uniffi::rust_future_new::<_, #return_ty, _>(
295                             async move {
296                                 #return_impl::handle_failed_lift(arg_name, anyhow_error)
297                             },
298                             crate::UniFfiTag,
299                         )
300                     },
301                 }
302             }
303         }
304     })
305 }
306