/* 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::{ default::{default_value_metadata_calls, DefaultValue}, export::{AsyncRuntime, DefaultMap, ExportFnArgs}, ffiops, 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, // The identifier of the Rust function. pub ident: Ident, // The foreign name for this function, usually == ident. pub name: String, pub is_async: bool, pub async_runtime: Option, pub receiver: Option, pub args: Vec, pub return_ty: TokenStream, // Does this the return type look like a result? // Only use this in UDL mode. // In general, it's not reliable because it fails for type aliases. 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::new(FnKind::Function, sig, args, docstring) } pub(crate) fn new_method( self_ident: Ident, foreign_self_ident: Ident, sig: syn::Signature, args: ExportFnArgs, docstring: String, ) -> syn::Result { Self::new( FnKind::Method { self_ident, foreign_self_ident, }, sig, args, docstring, ) } pub(crate) fn new_constructor( self_ident: Ident, foreign_self_ident: Ident, sig: syn::Signature, args: ExportFnArgs, docstring: String, ) -> syn::Result { Self::new( FnKind::Constructor { self_ident, foreign_self_ident, }, sig, args, docstring, ) } pub(crate) fn new_trait_method( self_ident: Ident, sig: syn::Signature, args: ExportFnArgs, index: u32, docstring: String, ) -> syn::Result { Self::new( FnKind::TraitMethod { self_ident, index }, sig, args, docstring, ) } pub(crate) fn new( kind: FnKind, sig: syn::Signature, mut export_fn_args: ExportFnArgs, docstring: String, ) -> syn::Result { 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 export_fn_args.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::>>()?; if let Some(ident) = export_fn_args.defaults.idents().first() { return Err(syn::Error::new( ident.span(), format!("Unknown default argument: {}", ident), )); } if !is_async && export_fn_args.async_runtime.is_some() { return Err(syn::Error::new( export_fn_args.async_runtime.span(), "Function not async".to_string(), )); } Ok(Self { kind, span, mod_path: mod_path()?, name: export_fn_args .name .unwrap_or_else(|| ident_to_string(&ident)), ident, is_async, async_runtime: export_fn_args.async_runtime, receiver, args, return_ty: output, looks_like_result, docstring, }) } /// Generate a closure that tries to lift all arguments into a tuple. /// /// The closure moves all scaffolding arguments into itself and returns: /// - The lifted argument tuple on success /// - The field name and error on failure (`Err(&'static str, anyhow::Error>`) pub fn lift_closure(&self, self_lift: Option) -> TokenStream { let arg_lifts = self.args.iter().map(|arg| { let ident = &arg.ident; let try_lift = ffiops::try_lift(&arg.ty); let name = &arg.name; quote! { match #try_lift(#ident) { ::std::result::Result::Ok(v) => v, ::std::result::Result::Err(e) => { return ::std::result::Result::Err((#name, e)) } } } }); let all_lifts = self_lift.into_iter().chain(arg_lifts); quote! { move || ::std::result::Result::Ok(( #(#all_lifts,)* )) } } /// Call a Rust function from a [Self::lift_closure] success. /// /// This takes an Ok value returned by `lift_closure` with the name `uniffi_args` and generates /// a series of parameters to pass to the Rust function. 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),* } } /// Parameters expressions for each of our arguments pub fn params(&self) -> impl Iterator + '_ { self.args.iter().map(NamedArg::param) } /// Name of the scaffolding function to generate for this function pub fn scaffolding_fn_ident(&self) -> syn::Result { let name = &self.name; let name = match &self.kind { FnKind::Function => uniffi_meta::fn_symbol_name(&self.mod_path, name), FnKind::Method { foreign_self_ident, .. } => { let object_name = ident_to_string(foreign_self_ident); uniffi_meta::method_symbol_name(&self.mod_path, &object_name, name) } FnKind::TraitMethod { self_ident, .. } => { uniffi_meta::method_symbol_name(&self.mod_path, &ident_to_string(self_ident), name) } FnKind::Constructor { foreign_self_ident, .. } => { let object_name = ident_to_string(foreign_self_ident); uniffi_meta::constructor_symbol_name(&self.mod_path, &object_name, name) } }; Ok(Ident::new(&name, Span::call_site())) } /// Scaffolding parameters expressions for each of our arguments pub fn scaffolding_param_names(&self) -> impl Iterator + '_ { self.args.iter().map(|a| { let ident = &a.ident; quote! { #ident } }) } pub fn scaffolding_param_types(&self) -> impl Iterator + '_ { self.args.iter().map(|a| ffiops::lift_type(&a.ty)) } /// Generate metadata items for this function pub(crate) fn metadata_expr(&self) -> syn::Result { let Self { name, return_ty, is_async, docstring, .. } = &self; let args_len = try_metadata_value_from_usize( // Use param_lifts to calculate this instead of sig.inputs to avoid counting any self // params self.args.len(), "UniFFI limits functions to 256 arguments", )?; let arg_metadata_calls = self .args .iter() .map(NamedArg::arg_metadata) .collect::>>()?; let type_id_meta = ffiops::type_id_meta(return_ty); match &self.kind { FnKind::Function => Ok(quote! { ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::FUNC) .concat_str(module_path!()) .concat_str(#name) .concat_bool(#is_async) .concat_value(#args_len) #(#arg_metadata_calls)* .concat(#type_id_meta) .concat_long_str(#docstring) }), FnKind::Method { foreign_self_ident, .. } => { let object_name = ident_to_string(foreign_self_ident); Ok(quote! { ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::METHOD) .concat_str(module_path!()) .concat_str(#object_name) .concat_str(#name) .concat_bool(#is_async) .concat_value(#args_len) #(#arg_metadata_calls)* .concat(#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(module_path!()) .concat_str(#object_name) .concat_u32(#index) .concat_str(#name) .concat_bool(#is_async) .concat_value(#args_len) #(#arg_metadata_calls)* .concat(#type_id_meta) .concat_long_str(#docstring) }) } FnKind::Constructor { foreign_self_ident, .. } => { let object_name = ident_to_string(foreign_self_ident); Ok(quote! { ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CONSTRUCTOR) .concat_str(module_path!()) .concat_str(#object_name) .concat_str(#name) .concat_bool(#is_async) .concat_value(#args_len) #(#arg_metadata_calls)* .concat(#type_id_meta) .concat_long_str(#docstring) }) } } } pub(crate) fn metadata_items(&self) -> syn::Result { 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 { foreign_self_ident, .. } => { let object_name = ident_to_string(foreign_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 { foreign_self_ident, .. } => { let object_name = ident_to_string(foreign_self_ident); Ok(create_metadata_items( "constructor", &format!("{object_name}_{name}"), self.metadata_expr()?, Some(self.checksum_symbol_name()), )) } } } 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 { foreign_self_ident, .. } => { let object_name = ident_to_string(foreign_self_ident); uniffi_meta::method_checksum_symbol_name(&self.mod_path, &object_name, name) } FnKind::TraitMethod { self_ident, .. } => uniffi_meta::method_checksum_symbol_name( &self.mod_path, &ident_to_string(self_ident), name, ), FnKind::Constructor { foreign_self_ident, .. } => { let object_name = ident_to_string(foreign_self_ident); uniffi_meta::constructor_checksum_symbol_name(&self.mod_path, &object_name, name) } } } } pub(crate) struct Arg { pub(crate) span: Span, pub(crate) kind: ArgKind, } #[allow(clippy::large_enum_variant)] pub(crate) enum ArgKind { Receiver(ReceiverArg), Named(NamedArg), } impl Arg { fn new(syn_arg: FnArg, defaults: &mut DefaultMap) -> syn::Result { 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 for ReceiverArg { fn from(receiver: Receiver) -> Self { if let Type::Path(p) = *receiver.ty { if let Some(segment) = p.path.segments.last() { // This comparison will fail if a user uses a typedef for Arc. Maybe we could // implement some system like TYPE_ID_META to figure this out from the type system. // However, this seems good enough for now. 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, pub(crate) default: Option, } impl NamedArg { pub(crate) fn new(ident: Ident, ty: &Type, defaults: &mut DefaultMap) -> syn::Result { Ok(match ty { Type::Reference(r) => { let inner = &r.elem; Self { name: ident_to_string(&ident), ty: ffiops::lift_ref_type(inner), 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, }, }) } /// Generate the parameter for this Arg 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 { let name = &self.name; let type_id_meta = ffiops::type_id_meta(&self.ty); let default_calls = default_value_metadata_calls(&self.default)?; Ok(quote! { .concat_str(#name) .concat(#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, foreign_self_ident: Ident, }, Method { self_ident: Ident, foreign_self_ident: Ident, }, TraitMethod { self_ident: Ident, index: u32, }, }