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