use crate::{
default::{default_value_metadata_calls, DefaultValue},
export::{DefaultMap, ExportFnArgs, ExportedImplFnArgs},
util::{create_metadata_items, ident_to_string, mod_path, try_metadata_value_from_usize},
};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{spanned::Spanned, FnArg, Ident, Pat, Receiver, ReturnType, Type};
pub(crate) struct FnSignature {
pub kind: FnKind,
pub span: Span,
pub mod_path: String,
pub ident: Ident,
pub name: String,
pub is_async: bool,
pub receiver: Option<ReceiverArg>,
pub args: Vec<NamedArg>,
pub return_ty: TokenStream,
pub looks_like_result: bool,
pub docstring: String,
}
impl FnSignature {
pub(crate) fn new_function(
sig: syn::Signature,
args: ExportFnArgs,
docstring: String,
) -> syn::Result<Self> {
Self::new(FnKind::Function, sig, args.name, args.defaults, docstring)
}
pub(crate) fn new_method(
self_ident: Ident,
sig: syn::Signature,
args: ExportedImplFnArgs,
docstring: String,
) -> syn::Result<Self> {
Self::new(
FnKind::Method { self_ident },
sig,
args.name,
args.defaults,
docstring,
)
}
pub(crate) fn new_constructor(
self_ident: Ident,
sig: syn::Signature,
args: ExportedImplFnArgs,
docstring: String,
) -> syn::Result<Self> {
Self::new(
FnKind::Constructor { self_ident },
sig,
args.name,
args.defaults,
docstring,
)
}
pub(crate) fn new_trait_method(
self_ident: Ident,
sig: syn::Signature,
args: ExportedImplFnArgs,
index: u32,
docstring: String,
) -> syn::Result<Self> {
Self::new(
FnKind::TraitMethod { self_ident, index },
sig,
args.name,
args.defaults,
docstring,
)
}
pub(crate) fn new(
kind: FnKind,
sig: syn::Signature,
name: Option<String>,
mut defaults: DefaultMap,
docstring: String,
) -> syn::Result<Self> {
let span = sig.span();
let ident = sig.ident;
let looks_like_result = looks_like_result(&sig.output);
let output = match sig.output {
ReturnType::Default => quote! { () },
ReturnType::Type(_, ty) => quote! { #ty },
};
let is_async = sig.asyncness.is_some();
let mut input_iter = sig
.inputs
.into_iter()
.map(|a| Arg::new(a, &mut defaults))
.peekable();
let receiver = input_iter
.next_if(|a| matches!(a, Ok(a) if a.is_receiver()))
.map(|a| match a {
Ok(Arg {
kind: ArgKind::Receiver(r),
..
}) => r,
_ => unreachable!(),
});
let args = input_iter
.map(|a| {
a.and_then(|a| match a.kind {
ArgKind::Named(named) => Ok(named),
ArgKind::Receiver(_) => {
Err(syn::Error::new(a.span, "Unexpected receiver argument"))
}
})
})
.collect::<syn::Result<Vec<_>>>()?;
if let Some(ident) = defaults.idents().first() {
return Err(syn::Error::new(
ident.span(),
format!("Unknown default argument: {}", ident),
));
}
Ok(Self {
kind,
span,
mod_path: mod_path()?,
name: name.unwrap_or_else(|| ident_to_string(&ident)),
ident,
is_async,
receiver,
args,
return_ty: output,
looks_like_result,
docstring,
})
}
pub fn lower_return_impl(&self) -> TokenStream {
let return_ty = &self.return_ty;
quote! {
<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>
}
}
pub fn lift_return_impl(&self) -> TokenStream {
let return_ty = &self.return_ty;
quote! {
<#return_ty as ::uniffi::LiftReturn<crate::UniFfiTag>>
}
}
pub fn lift_closure(&self, self_lift: Option<TokenStream>) -> TokenStream {
let arg_lifts = self.args.iter().map(|arg| {
let ident = &arg.ident;
let lift_impl = arg.lift_impl();
let name = &arg.name;
quote! {
match #lift_impl::try_lift(#ident) {
Ok(v) => v,
Err(e) => return Err((#name, e)),
}
}
});
let all_lifts = self_lift.into_iter().chain(arg_lifts);
quote! {
move || Ok((
#(#all_lifts,)*
))
}
}
pub fn rust_call_params(&self, self_lift: bool) -> TokenStream {
let start_idx = if self_lift { 1 } else { 0 };
let args = self.args.iter().enumerate().map(|(i, arg)| {
let idx = syn::Index::from(i + start_idx);
let ty = &arg.ty;
match &arg.ref_type {
None => quote! { uniffi_args.#idx },
Some(ref_type) => quote! {
<#ty as ::std::borrow::Borrow<#ref_type>>::borrow(&uniffi_args.#idx)
},
}
});
quote! { #(#args),* }
}
pub fn params(&self) -> impl Iterator<Item = TokenStream> + '_ {
self.args.iter().map(NamedArg::param)
}
pub fn scaffolding_fn_ident(&self) -> syn::Result<Ident> {
let name = &self.name;
let name = match &self.kind {
FnKind::Function => uniffi_meta::fn_symbol_name(&self.mod_path, name),
FnKind::Method { self_ident } | FnKind::TraitMethod { self_ident, .. } => {
uniffi_meta::method_symbol_name(&self.mod_path, &ident_to_string(self_ident), name)
}
FnKind::Constructor { self_ident } => uniffi_meta::constructor_symbol_name(
&self.mod_path,
&ident_to_string(self_ident),
name,
),
};
Ok(Ident::new(&name, Span::call_site()))
}
pub fn scaffolding_param_names(&self) -> impl Iterator<Item = TokenStream> + '_ {
self.args.iter().map(|a| {
let ident = &a.ident;
quote! { #ident }
})
}
pub fn scaffolding_param_types(&self) -> impl Iterator<Item = TokenStream> + '_ {
self.args.iter().map(|a| {
let lift_impl = a.lift_impl();
quote! { #lift_impl::FfiType }
})
}
pub(crate) fn metadata_expr(&self) -> syn::Result<TokenStream> {
let Self {
name,
return_ty,
is_async,
mod_path,
docstring,
..
} = &self;
let args_len = try_metadata_value_from_usize(
self.args.len(),
"UniFFI limits functions to 256 arguments",
)?;
let arg_metadata_calls = self
.args
.iter()
.map(NamedArg::arg_metadata)
.collect::<syn::Result<Vec<_>>>()?;
match &self.kind {
FnKind::Function => Ok(quote! {
::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::FUNC)
.concat_str(#mod_path)
.concat_str(#name)
.concat_bool(#is_async)
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META)
.concat_long_str(#docstring)
}),
FnKind::Method { self_ident } => {
let object_name = ident_to_string(self_ident);
Ok(quote! {
::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::METHOD)
.concat_str(#mod_path)
.concat_str(#object_name)
.concat_str(#name)
.concat_bool(#is_async)
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META)
.concat_long_str(#docstring)
})
}
FnKind::TraitMethod { self_ident, index } => {
let object_name = ident_to_string(self_ident);
Ok(quote! {
::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TRAIT_METHOD)
.concat_str(#mod_path)
.concat_str(#object_name)
.concat_u32(#index)
.concat_str(#name)
.concat_bool(#is_async)
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META)
.concat_long_str(#docstring)
})
}
FnKind::Constructor { self_ident } => {
let object_name = ident_to_string(self_ident);
Ok(quote! {
::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CONSTRUCTOR)
.concat_str(#mod_path)
.concat_str(#object_name)
.concat_str(#name)
.concat_bool(#is_async)
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META)
.concat_long_str(#docstring)
})
}
}
}
pub(crate) fn metadata_items(&self) -> syn::Result<TokenStream> {
let Self { name, .. } = &self;
match &self.kind {
FnKind::Function => Ok(create_metadata_items(
"func",
name,
self.metadata_expr()?,
Some(self.checksum_symbol_name()),
)),
FnKind::Method { self_ident } => {
let object_name = ident_to_string(self_ident);
Ok(create_metadata_items(
"method",
&format!("{object_name}_{name}"),
self.metadata_expr()?,
Some(self.checksum_symbol_name()),
))
}
FnKind::TraitMethod { self_ident, .. } => {
let object_name = ident_to_string(self_ident);
Ok(create_metadata_items(
"method",
&format!("{object_name}_{name}"),
self.metadata_expr()?,
Some(self.checksum_symbol_name()),
))
}
FnKind::Constructor { self_ident } => {
let object_name = ident_to_string(self_ident);
Ok(create_metadata_items(
"constructor",
&format!("{object_name}_{name}"),
self.metadata_expr()?,
Some(self.checksum_symbol_name()),
))
}
}
}
pub(crate) fn metadata_items_for_callback_interface(&self) -> syn::Result<TokenStream> {
let Self {
name,
return_ty,
is_async,
mod_path,
docstring,
..
} = &self;
match &self.kind {
FnKind::TraitMethod {
self_ident, index, ..
} => {
let object_name = ident_to_string(self_ident);
let args_len = try_metadata_value_from_usize(
self.args.len(),
"UniFFI limits functions to 256 arguments",
)?;
let arg_metadata_calls = self
.args
.iter()
.map(NamedArg::arg_metadata)
.collect::<syn::Result<Vec<_>>>()?;
let metadata_expr = quote! {
::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TRAIT_METHOD)
.concat_str(#mod_path)
.concat_str(#object_name)
.concat_u32(#index)
.concat_str(#name)
.concat_bool(#is_async)
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LiftReturn<crate::UniFfiTag>>::TYPE_ID_META)
.concat_long_str(#docstring)
};
Ok(create_metadata_items(
"method",
&format!("{object_name}_{name}"),
metadata_expr,
Some(self.checksum_symbol_name()),
))
}
_ => panic!(
"metadata_items_for_callback_interface can only be called with `TraitMethod` sigs"
),
}
}
pub(crate) fn checksum_symbol_name(&self) -> String {
let name = &self.name;
match &self.kind {
FnKind::Function => uniffi_meta::fn_checksum_symbol_name(&self.mod_path, name),
FnKind::Method { self_ident } | FnKind::TraitMethod { self_ident, .. } => {
uniffi_meta::method_checksum_symbol_name(
&self.mod_path,
&ident_to_string(self_ident),
name,
)
}
FnKind::Constructor { self_ident } => uniffi_meta::constructor_checksum_symbol_name(
&self.mod_path,
&ident_to_string(self_ident),
name,
),
}
}
}
pub(crate) struct Arg {
pub(crate) span: Span,
pub(crate) kind: ArgKind,
}
pub(crate) enum ArgKind {
Receiver(ReceiverArg),
Named(NamedArg),
}
impl Arg {
fn new(syn_arg: FnArg, defaults: &mut DefaultMap) -> syn::Result<Self> {
let span = syn_arg.span();
let kind = match syn_arg {
FnArg::Typed(p) => match *p.pat {
Pat::Ident(i) => Ok(ArgKind::Named(NamedArg::new(i.ident, &p.ty, defaults)?)),
_ => Err(syn::Error::new_spanned(p, "Argument name missing")),
},
FnArg::Receiver(receiver) => Ok(ArgKind::Receiver(ReceiverArg::from(receiver))),
}?;
Ok(Self { span, kind })
}
pub(crate) fn is_receiver(&self) -> bool {
matches!(self.kind, ArgKind::Receiver(_))
}
}
pub(crate) enum ReceiverArg {
Ref,
Arc,
}
impl From<Receiver> for ReceiverArg {
fn from(receiver: Receiver) -> Self {
if let Type::Path(p) = *receiver.ty {
if let Some(segment) = p.path.segments.last() {
if segment.ident == "Arc" {
return ReceiverArg::Arc;
}
}
}
Self::Ref
}
}
pub(crate) struct NamedArg {
pub(crate) ident: Ident,
pub(crate) name: String,
pub(crate) ty: TokenStream,
pub(crate) ref_type: Option<Type>,
pub(crate) default: Option<DefaultValue>,
}
impl NamedArg {
pub(crate) fn new(ident: Ident, ty: &Type, defaults: &mut DefaultMap) -> syn::Result<Self> {
Ok(match ty {
Type::Reference(r) => {
let inner = &r.elem;
Self {
name: ident_to_string(&ident),
ty: quote! { <#inner as ::uniffi::LiftRef<crate::UniFfiTag>>::LiftType },
ref_type: Some(*inner.clone()),
default: defaults.remove(&ident),
ident,
}
}
_ => Self {
name: ident_to_string(&ident),
ty: quote! { #ty },
ref_type: None,
default: defaults.remove(&ident),
ident,
},
})
}
pub(crate) fn lift_impl(&self) -> TokenStream {
let ty = &self.ty;
quote! { <#ty as ::uniffi::Lift<crate::UniFfiTag>> }
}
pub(crate) fn lower_impl(&self) -> TokenStream {
let ty = &self.ty;
quote! { <#ty as ::uniffi::Lower<crate::UniFfiTag>> }
}
pub(crate) fn param(&self) -> TokenStream {
let ident = &self.ident;
let ty = &self.ty;
quote! { #ident: #ty }
}
pub(crate) fn arg_metadata(&self) -> syn::Result<TokenStream> {
let name = &self.name;
let lift_impl = self.lift_impl();
let default_calls = default_value_metadata_calls(&self.default)?;
Ok(quote! {
.concat_str(#name)
.concat(#lift_impl::TYPE_ID_META)
#default_calls
})
}
}
fn looks_like_result(return_type: &ReturnType) -> bool {
if let ReturnType::Type(_, ty) = return_type {
if let Type::Path(p) = &**ty {
if let Some(seg) = p.path.segments.last() {
if seg.ident == "Result" {
return true;
}
}
}
}
false
}
#[derive(Debug)]
pub(crate) enum FnKind {
Function,
Constructor { self_ident: Ident },
Method { self_ident: Ident },
TraitMethod { self_ident: Ident, index: u32 },
}