Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use std::iter;
use super::attributes::AsyncRuntime;
use crate::{
ffiops,
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
})
}
// Pieces of code for the scaffolding function
struct ScaffoldingBits {
/// Parameter names for the scaffolding function
param_names: Vec<TokenStream>,
/// Parameter types for the scaffolding function
param_types: Vec<TokenStream>,
/// Lift closure. See `FnSignature::lift_closure` for an explanation of this.
lift_closure: TokenStream,
/// Expression to call the Rust function after a successful lift.
rust_fn_call: TokenStream,
/// Convert the result of `rust_fn_call`, stored in a variable named `uniffi_result` into its final value.
/// This is used to do things like error conversion / Arc wrapping
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) };
// UDL mode adds an extra conversion (#1749)
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 self_type = if is_trait {
quote! { ::std::sync::Arc<dyn #self_ident> }
} else {
quote! { ::std::sync::Arc<#self_ident> }
};
let lift_type = ffiops::lift_type(&self_type);
let try_lift = ffiops::try_lift(&self_type);
let try_lift_self = if is_trait {
// For trait interfaces we need to special case this. Trait interfaces normally lift
// foreign trait impl pointers. However, for a method call, we want to lift a Rust
// pointer.
quote! {
{
let boxed_foreign_arc = unsafe {
::std::boxed::Box::from_raw(
uniffi_self_lowered as *mut ::std::sync::Arc<dyn #self_ident>,
)
};
// Take a clone for our own use.
::std::result::Result::Ok(*boxed_foreign_arc)
}
}
} else {
quote! { #try_lift(uniffi_self_lowered) }
};
let lift_closure = sig.lift_closure(Some(quote! {
match #try_lift_self {
::std::result::Result::Ok(v) => v,
::std::result::Result::Err(e) => {
return ::std::result::Result::Err(("self", e));
}
}
}));
let call_params = sig.rust_call_params(true);
let rust_fn_call = quote! { uniffi_args.0.#ident(#call_params) };
// UDL mode adds an extra conversion (#1749)
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_type })
.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) };
// UDL mode adds extra conversions (#1749)
let convert_result = match (udl_mode, sig.looks_like_result) {
// For UDL
(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,
}
}
}
/// Generate a scaffolding function
///
/// `pre_fn_call` is the statements that we should execute before the rust call
/// `rust_fn` is the Rust function to call.
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)
}
};
// Scaffolding functions are logically `pub`, but we don't use that in UDL mode since UDL has
// historically not required types to be `pub`
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 ffi_return_ty = ffiops::lower_return_type(return_ty);
let lower_return = ffiops::lower_return(return_ty);
let handle_failed_lift = ffiops::lower_return_handle_failed_lift(return_ty);
Ok(if !sig.is_async {
let scaffolding_fn_ffi_buffer_version =
ffi_buffer_scaffolding_fn(&ffi_ident, &ffi_return_ty, ¶m_types, true);
quote! {
#[doc(hidden)]
#[no_mangle]
#vis extern "C" fn #ffi_ident(
#(#param_names: #param_types,)*
call_status: &mut ::uniffi::RustCallStatus,
) -> #ffi_return_ty {
::uniffi::deps::log::debug!(#name);
let uniffi_lift_args = #lift_closure;
::uniffi::rust_call(call_status, || {
match uniffi_lift_args() {
::std::result::Result::Ok(uniffi_args) => {
let uniffi_result = #rust_fn_call;
#lower_return(#convert_result)
}
::std::result::Result::Err((arg_name, error)) => {
#handle_failed_lift(::uniffi::LiftArgsError { arg_name, error} )
},
}
})
}
#scaffolding_fn_ffi_buffer_version
}
} 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) }
}
let scaffolding_fn_ffi_buffer_version =
ffi_buffer_scaffolding_fn(&ffi_ident, "e! { ::uniffi::Handle}, ¶m_types, false);
quote! {
#[doc(hidden)]
#[no_mangle]
pub extern "C" fn #ffi_ident(#(#param_names: #param_types,)*) -> ::uniffi::Handle {
::uniffi::deps::log::debug!(#name);
let uniffi_lifted_args = (#lift_closure)();
::uniffi::rust_future_new::<_, #return_ty, _>(
async move {
match uniffi_lifted_args {
::std::result::Result::Ok(uniffi_args) => {
let uniffi_result = #future_expr.await;
Ok(#convert_result)
}
::std::result::Result::Err((arg_name, error)) => {
Err(::uniffi::LiftArgsError { arg_name, error })
},
}
},
crate::UniFfiTag
)
}
#scaffolding_fn_ffi_buffer_version
}
})
}
#[cfg(feature = "scaffolding-ffi-buffer-fns")]
fn ffi_buffer_scaffolding_fn(
fn_ident: &Ident,
return_type: &TokenStream,
param_types: &[TokenStream],
has_rust_call_status: bool,
) -> TokenStream {
let fn_name = fn_ident.to_string();
let ffi_buffer_fn_name = uniffi_meta::ffi_buffer_symbol_name(&fn_name);
let ident = Ident::new(&ffi_buffer_fn_name, proc_macro2::Span::call_site());
let type_list: Vec<_> = param_types.iter().map(|ty| quote! { #ty }).collect();
if has_rust_call_status {
quote! {
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn #ident(
arg_ptr: *mut ::uniffi::FfiBufferElement,
return_ptr: *mut ::uniffi::FfiBufferElement,
) {
let mut arg_buf = unsafe { ::std::slice::from_raw_parts(arg_ptr, ::uniffi::ffi_buffer_size!(#(#type_list),*)) };
let mut return_buf = unsafe { ::std::slice::from_raw_parts_mut(return_ptr, ::uniffi::ffi_buffer_size!(#return_type, ::uniffi::RustCallStatus)) };
let mut out_status: ::uniffi::RustCallStatus = ::std::default::Default::default();
let return_value = #fn_ident(
#(
<#type_list as ::uniffi::FfiSerialize>::read(&mut arg_buf),
)*
&mut out_status,
);
<#return_type as ::uniffi::FfiSerialize>::write(&mut return_buf, return_value);
<::uniffi::RustCallStatus as ::uniffi::FfiSerialize>::write(&mut return_buf, out_status);
}
}
} else {
quote! {
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn #ident(
arg_ptr: *mut ::uniffi::FfiBufferElement,
return_ptr: *mut ::uniffi::FfiBufferElement,
) {
let mut arg_buf = unsafe { ::std::slice::from_raw_parts(arg_ptr, ::uniffi::ffi_buffer_size!(#(#type_list),*)) };
let mut return_buf = unsafe { ::std::slice::from_raw_parts_mut(return_ptr, ::uniffi::ffi_buffer_size!(#return_type)) };
let return_value = #fn_ident(#(
<#type_list as ::uniffi::FfiSerialize>::read(&mut arg_buf),
)*);
<#return_type as ::uniffi::FfiSerialize>::put(&mut return_buf, return_value);
}
}
}
}
#[cfg(not(feature = "scaffolding-ffi-buffer-fns"))]
fn ffi_buffer_scaffolding_fn(
_fn_ident: &Ident,
_return_type: &TokenStream,
_param_types: &[TokenStream],
_add_rust_call_status: bool,
) -> TokenStream {
quote! {}
}