use proc_macro2::{Ident, TokenStream};
use quote::quote;
use std::iter;
use super::attributes::AsyncRuntime;
use crate::fnsig::{FnKind, FnSignature};
pub(super) fn gen_fn_scaffolding(
sig: FnSignature,
ar: &Option<AsyncRuntime>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
if sig.receiver.is_some() {
return Err(syn::Error::new(
sig.span,
"Unexpected self param (Note: uniffi::export must be used on the impl block, not its containing fn's)"
));
}
if !sig.is_async {
if let Some(async_runtime) = ar {
return Err(syn::Error::new_spanned(
async_runtime,
"this attribute is only allowed on async functions",
));
}
}
let metadata_items = (!udl_mode).then(|| {
sig.metadata_items()
.unwrap_or_else(syn::Error::into_compile_error)
});
let scaffolding_func = gen_ffi_function(&sig, ar, udl_mode)?;
Ok(quote! {
#scaffolding_func
#metadata_items
})
}
pub(super) fn gen_constructor_scaffolding(
sig: FnSignature,
ar: &Option<AsyncRuntime>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
if sig.receiver.is_some() {
return Err(syn::Error::new(
sig.span,
"constructors must not have a self parameter",
));
}
let metadata_items = (!udl_mode).then(|| {
sig.metadata_items()
.unwrap_or_else(syn::Error::into_compile_error)
});
let scaffolding_func = gen_ffi_function(&sig, ar, udl_mode)?;
Ok(quote! {
#scaffolding_func
#metadata_items
})
}
pub(super) fn gen_method_scaffolding(
sig: FnSignature,
ar: &Option<AsyncRuntime>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
let scaffolding_func = if sig.receiver.is_none() {
return Err(syn::Error::new(
sig.span,
"associated functions are not currently supported",
));
} else {
gen_ffi_function(&sig, ar, udl_mode)?
};
let metadata_items = (!udl_mode).then(|| {
sig.metadata_items()
.unwrap_or_else(syn::Error::into_compile_error)
});
Ok(quote! {
#scaffolding_func
#metadata_items
})
}
struct ScaffoldingBits {
param_names: Vec<TokenStream>,
param_types: Vec<TokenStream>,
lift_closure: TokenStream,
rust_fn_call: TokenStream,
convert_result: TokenStream,
}
impl ScaffoldingBits {
fn new_for_function(sig: &FnSignature, udl_mode: bool) -> Self {
let ident = &sig.ident;
let call_params = sig.rust_call_params(false);
let rust_fn_call = quote! { #ident(#call_params) };
let convert_result = if udl_mode && sig.looks_like_result {
quote! { uniffi_result.map_err(::std::convert::Into::into) }
} else {
quote! { uniffi_result }
};
Self {
param_names: sig.scaffolding_param_names().collect(),
param_types: sig.scaffolding_param_types().collect(),
lift_closure: sig.lift_closure(None),
rust_fn_call,
convert_result,
}
}
fn new_for_method(
sig: &FnSignature,
self_ident: &Ident,
is_trait: bool,
udl_mode: bool,
) -> Self {
let ident = &sig.ident;
let lift_impl = if is_trait {
quote! {
<::std::sync::Arc<dyn #self_ident> as ::uniffi::Lift<crate::UniFfiTag>>
}
} else {
quote! {
<::std::sync::Arc<#self_ident> as ::uniffi::Lift<crate::UniFfiTag>>
}
};
let try_lift_self = if is_trait {
quote! {
{
let boxed_foreign_arc = unsafe { Box::from_raw(uniffi_self_lowered as *mut ::std::sync::Arc<dyn #self_ident>) };
Ok(*boxed_foreign_arc)
}
}
} else {
quote! { #lift_impl::try_lift(uniffi_self_lowered) }
};
let lift_closure = sig.lift_closure(Some(quote! {
match #try_lift_self {
Ok(v) => v,
Err(e) => return Err(("self", e))
}
}));
let call_params = sig.rust_call_params(true);
let rust_fn_call = quote! { uniffi_args.0.#ident(#call_params) };
let convert_result = if udl_mode && sig.looks_like_result {
quote! { uniffi_result .map_err(::std::convert::Into::into) }
} else {
quote! { uniffi_result }
};
Self {
param_names: iter::once(quote! { uniffi_self_lowered })
.chain(sig.scaffolding_param_names())
.collect(),
param_types: iter::once(quote! { #lift_impl::FfiType })
.chain(sig.scaffolding_param_types())
.collect(),
lift_closure,
rust_fn_call,
convert_result,
}
}
fn new_for_constructor(sig: &FnSignature, self_ident: &Ident, udl_mode: bool) -> Self {
let ident = &sig.ident;
let call_params = sig.rust_call_params(false);
let rust_fn_call = quote! { #self_ident::#ident(#call_params) };
let convert_result = match (udl_mode, sig.looks_like_result) {
(true, false) => quote! { ::std::sync::Arc::new(uniffi_result) },
(true, true) => {
quote! { uniffi_result.map(::std::sync::Arc::new).map_err(::std::convert::Into::into) }
}
(false, _) => quote! { uniffi_result },
};
Self {
param_names: sig.scaffolding_param_names().collect(),
param_types: sig.scaffolding_param_types().collect(),
lift_closure: sig.lift_closure(None),
rust_fn_call,
convert_result,
}
}
}
pub(super) fn gen_ffi_function(
sig: &FnSignature,
ar: &Option<AsyncRuntime>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
let ScaffoldingBits {
param_names,
param_types,
lift_closure,
rust_fn_call,
convert_result,
} = match &sig.kind {
FnKind::Function => ScaffoldingBits::new_for_function(sig, udl_mode),
FnKind::Method { self_ident } => {
ScaffoldingBits::new_for_method(sig, self_ident, false, udl_mode)
}
FnKind::TraitMethod { self_ident, .. } => {
ScaffoldingBits::new_for_method(sig, self_ident, true, udl_mode)
}
FnKind::Constructor { self_ident } => {
ScaffoldingBits::new_for_constructor(sig, self_ident, udl_mode)
}
};
let vis = match udl_mode {
false => quote! { pub },
true => quote! {},
};
let ffi_ident = sig.scaffolding_fn_ident()?;
let name = &sig.name;
let return_ty = &sig.return_ty;
let return_impl = &sig.lower_return_impl();
Ok(if !sig.is_async {
quote! {
#[doc(hidden)]
#[no_mangle]
#vis extern "C" fn #ffi_ident(
#(#param_names: #param_types,)*
call_status: &mut ::uniffi::RustCallStatus,
) -> #return_impl::ReturnType {
::uniffi::deps::log::debug!(#name);
let uniffi_lift_args = #lift_closure;
::uniffi::rust_call(call_status, || {
#return_impl::lower_return(
match uniffi_lift_args() {
Ok(uniffi_args) => {
let uniffi_result = #rust_fn_call;
#convert_result
}
Err((arg_name, anyhow_error)) => {
#return_impl::handle_failed_lift(arg_name, anyhow_error)
},
}
)
})
}
}
} else {
let mut future_expr = rust_fn_call;
if matches!(ar, Some(AsyncRuntime::Tokio(_))) {
future_expr = quote! { ::uniffi::deps::async_compat::Compat::new(#future_expr) }
}
quote! {
#[doc(hidden)]
#[no_mangle]
pub extern "C" fn #ffi_ident(#(#param_names: #param_types,)*) -> ::uniffi::Handle {
::uniffi::deps::log::debug!(#name);
let uniffi_lift_args = #lift_closure;
match uniffi_lift_args() {
Ok(uniffi_args) => {
::uniffi::rust_future_new::<_, #return_ty, _>(
async move {
let uniffi_result = #future_expr.await;
#convert_result
},
crate::UniFfiTag
)
},
Err((arg_name, anyhow_error)) => {
::uniffi::rust_future_new::<_, #return_ty, _>(
async move {
#return_impl::handle_failed_lift(arg_name, anyhow_error)
},
crate::UniFfiTag,
)
},
}
}
}
})
}