use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{FnArg, ItemFn, ItemImpl, ReturnType, Signature, Type};
use crate::{FFITypeResolver, Namer};
pub fn expand_impl(item: &ItemImpl) -> syn::Result<proc_macro2::TokenStream> {
let impl_ty = &item.self_ty;
let mut wrappers = Vec::new();
for impl_item in &item.items {
if let syn::ImplItem::Fn(method) = impl_item {
wrappers.push(generate_fn_wrapper(Some(impl_ty), &method.sig)?);
}
}
Ok(quote! { #( #wrappers )* })
}
pub fn expand_fn(item: &ItemFn) -> syn::Result<proc_macro2::TokenStream> {
generate_fn_wrapper(None, &item.sig)
}
pub struct WrapMethods {
ty: Type,
sigs: Vec<Signature>,
}
impl Parse for WrapMethods {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ty: Type = input.parse()?;
let content;
syn::braced!(content in input);
let mut sigs = Vec::new();
while !content.is_empty() {
let _: syn::Visibility = content.parse()?;
sigs.push(content.parse()?);
content.parse::<syn::Token![;]>()?;
}
Ok(Self { ty, sigs })
}
}
pub fn expand_wrap_methods(item: &WrapMethods) -> syn::Result<proc_macro2::TokenStream> {
let wrappers: Vec<_> = item
.sigs
.iter()
.map(|sig| generate_fn_wrapper(Some(&item.ty), sig))
.collect::<syn::Result<Vec<_>>>()?;
Ok(quote! { #( #wrappers )* })
}
fn generate_fn_wrapper(
impl_ty: Option<&Type>,
sig: &Signature,
) -> syn::Result<proc_macro2::TokenStream> {
let fn_name = &sig.ident;
let inputs = sig.inputs.iter().collect::<Vec<_>>();
let output = &sig.output;
let is_async = sig.asyncness.is_some();
let mut c_args = Vec::new();
let mut c_args_to_rust = Vec::new();
let mut rust_args = Vec::new();
let ffi_fn_name = Namer::name_fn(fn_name, impl_ty)?;
for arg in inputs {
match arg {
FnArg::Receiver(receiver) => {
let c_ty = super::FFITypeResolver::c_type_of(&receiver.ty, impl_ty)?;
let is_ref = receiver.reference.is_some();
let is_mut = receiver.mutability.is_some();
let (ffi_self_ty, self_conversion) = match (is_ref, is_mut) {
(false, _) => (
quote! { #c_ty },
quote! { let mut this = this.into_rust_owned(); },
),
(true, false) => (
quote! { *const #c_ty },
quote! {
let mut this = &*this;
let this = this.into_rust();
},
),
(true, true) => (
quote! { *mut #c_ty },
quote! {
let this = &mut *this;
let this = this.into_rust_mut();
},
),
};
c_args.push(quote! { mut this: #ffi_self_ty });
c_args_to_rust.push(self_conversion);
rust_args.push(quote! { this });
}
FnArg::Typed(pat_type) => {
let name = match &*pat_type.pat {
syn::Pat::Ident(ident) => &ident.ident,
other => {
return Err(syn::Error::new_spanned(
other,
"unsupported parameter pattern in #[ezffi::export]",
));
}
};
let ty = &pat_type.ty;
if FFITypeResolver::is_primitive(ty) || FFITypeResolver::is_c_callback(ty) {
c_args.push(quote! { mut #name: #ty });
} else {
let c_ty = super::FFITypeResolver::c_type_of(ty, impl_ty)?;
let (c_ty_as_arg, c_arg_to_rust) = match &*pat_type.ty {
Type::Reference(r) => {
let c_ty_str = c_ty.to_string().replace(' ', "");
if c_ty_str == "::ezffi::EzffiSlice" || c_ty_str == "::ezffi::EzffiStr"
{
(
quote! { #c_ty },
if r.mutability.is_some() {
quote! { let #name = #name.into_rust_mut(); }
} else {
quote! { let #name = #name.into_rust(); }
},
)
} else if r.mutability.is_some() {
(
quote! { *mut #c_ty },
quote! {
let mut #name = &mut *#name;
let mut #name = #name.into_rust_mut();
},
)
} else {
(
quote! { *const #c_ty },
quote! {
let #name = &*#name;
let #name = #name.into_rust();
},
)
}
}
Type::Path(_) => (
quote! { #c_ty },
quote! { let mut #name = #name.into_rust_owned(); },
),
_ => {
return Err(syn::Error::new_spanned(
ty,
format!(
"unsupported parameter type in #[ezffi::export]: `{}`",
quote!(#ty)
),
));
}
};
c_args.push(quote! { mut #name: #c_ty_as_arg });
c_args_to_rust.push(c_arg_to_rust);
}
rust_args.push(quote! { #name });
}
}
}
let rust_call = if let Some(ty) = impl_ty {
match ty {
syn::Type::Path(path) => {
let ident = &path.path.segments[0].ident;
quote! { #ident::#fn_name( #( #rust_args ),* ) }
}
other => {
return Err(syn::Error::new_spanned(
other,
format!("cannot wrap a method on impl target `{}`", quote!(#other)),
));
}
}
} else {
quote! { #fn_name( #( #rust_args ),* ) }
};
let rust_call = call_wrapper(is_async, sig, rust_call)?;
let c_ret_ty = match output {
ReturnType::Default => quote! { () },
ReturnType::Type(_, ty) => {
let ty = FFITypeResolver::c_type_of(ty, impl_ty)?;
quote! { #ty }
}
};
let rust_ret_to_c = match output {
ReturnType::Default => quote! {},
ReturnType::Type(_, ty) => from_rust_to_c_call(ty)?,
};
Ok(quote! {
#[doc(hidden)]
#[inline]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn #ffi_fn_name(
#( #c_args ),*
) -> #c_ret_ty {
use ezffi::RustRefIntoC;
use ezffi::RustOwnedIntoC;
use ezffi::CRefIntoRust;
use ezffi::COwnedIntoRust;
#( #c_args_to_rust )*
let result = #rust_call;
#rust_ret_to_c
}
})
}
fn from_rust_to_c_call(ty: &syn::Type) -> syn::Result<proc_macro2::TokenStream> {
if FFITypeResolver::is_primitive(ty) || FFITypeResolver::is_c_callback(ty) {
Ok(quote! { result })
} else {
match ty {
syn::Type::Reference(_) => Ok(quote! { result.ref_into_c() }),
syn::Type::Path(_) => Ok(quote! { result.owned_into_c() }),
_ => Err(syn::Error::new_spanned(
ty,
format!(
"unsupported return type in #[ezffi::export]: `{}`",
quote!(#ty)
),
)),
}
}
}
#[cfg(feature = "async")]
fn call_wrapper(
is_async: bool,
_sig: &Signature,
call: proc_macro2::TokenStream,
) -> syn::Result<proc_macro2::TokenStream> {
if is_async {
Ok(quote! { ::ezffi::dispatch_async(#call) })
} else {
Ok(call)
}
}
#[cfg(not(feature = "async"))]
fn call_wrapper(
is_async: bool,
sig: &Signature,
call: proc_macro2::TokenStream,
) -> syn::Result<proc_macro2::TokenStream> {
if is_async {
let span = sig
.asyncness
.as_ref()
.map(|a| a.span)
.unwrap_or_else(proc_macro2::Span::call_site);
return Err(syn::Error::new(
span,
"async functions require enabling the `async` feature on ezffi",
));
}
Ok(call)
}