diff --git a/uniffi_bindgen/src/interface/attributes.rs b/uniffi_bindgen/src/interface/attributes.rs index b0763d930f..7f9595d1ba 100644 --- a/uniffi_bindgen/src/interface/attributes.rs +++ b/uniffi_bindgen/src/interface/attributes.rs @@ -169,6 +169,12 @@ impl FunctionAttributes { } } +impl FromIterator for FunctionAttributes { + fn from_iter>(iter: T) -> Self { + Self(Vec::from_iter(iter)) + } +} + impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for FunctionAttributes { type Error = anyhow::Error; fn try_from( diff --git a/uniffi_bindgen/src/interface/function.rs b/uniffi_bindgen/src/interface/function.rs index 4eff0795cb..ebca0f210e 100644 --- a/uniffi_bindgen/src/interface/function.rs +++ b/uniffi_bindgen/src/interface/function.rs @@ -36,14 +36,11 @@ use std::hash::{Hash, Hasher}; use anyhow::{bail, Result}; +use super::attributes::{ArgumentAttributes, Attribute, FunctionAttributes}; use super::ffi::{FFIArgument, FFIFunction}; use super::literal::{convert_default_value, Literal}; use super::types::{Type, TypeIterator}; -use super::{ - attributes::{ArgumentAttributes, FunctionAttributes}, - convert_type, -}; -use super::{APIConverter, ComponentInterface}; +use super::{convert_type, APIConverter, ComponentInterface}; /// Represents a standalone function. /// @@ -137,7 +134,7 @@ impl From for Function { arguments, return_type, ffi_func, - attributes: Default::default(), + attributes: meta.throws.map(Attribute::Throws).into_iter().collect(), } } } diff --git a/uniffi_macros/src/export.rs b/uniffi_macros/src/export.rs index dc4470f869..c31b0fdce6 100644 --- a/uniffi_macros/src/export.rs +++ b/uniffi_macros/src/export.rs @@ -12,18 +12,18 @@ pub(crate) mod metadata; mod scaffolding; pub use self::metadata::gen_metadata; -use self::scaffolding::{gen_fn_scaffolding, gen_method_scaffolding}; -use crate::{ - export::metadata::convert::convert_type, - util::{assert_type_eq, create_metadata_static_var}, +use self::{ + metadata::convert::{convert_type, try_split_result}, + scaffolding::{gen_fn_scaffolding, gen_method_scaffolding}, }; +use crate::util::{assert_type_eq, create_metadata_static_var}; // TODO(jplatte): Ensure no generics, no async, … // TODO(jplatte): Aggregate errors instead of short-circuiting, whereever possible pub enum ExportItem { Function { - sig: Box, + sig: Signature, metadata: FnMetadata, }, Impl { @@ -33,10 +33,48 @@ pub enum ExportItem { } pub struct Method { - sig: syn::Signature, + sig: Signature, metadata: MethodMetadata, } +pub struct Signature { + ident: Ident, + inputs: Vec, + output: Option, +} + +impl Signature { + fn new(item: syn::Signature) -> syn::Result { + let output = match item.output { + syn::ReturnType::Default => None, + syn::ReturnType::Type(_, ty) => Some(FunctionReturn::new(ty)?), + }; + + Ok(Self { + ident: item.ident, + inputs: item.inputs.into_iter().collect(), + output, + }) + } +} + +pub struct FunctionReturn { + ty: Box, + throws: Option, +} + +impl FunctionReturn { + fn new(ty: Box) -> syn::Result { + Ok(match try_split_result(&ty)? { + Some((ok_type, throws)) => FunctionReturn { + ty: Box::new(ok_type.to_owned()), + throws: Some(throws), + }, + None => FunctionReturn { ty, throws: None }, + }) + } +} + pub fn expand_export(metadata: ExportItem, mod_path: &[String]) -> TokenStream { match metadata { ExportItem::Function { sig, metadata } => { @@ -92,7 +130,7 @@ pub fn expand_export(metadata: ExportItem, mod_path: &[String]) -> TokenStream { } } -fn fn_type_assertions(sig: &syn::Signature) -> TokenStream { +fn fn_type_assertions(sig: &Signature) -> TokenStream { // Convert uniffi_meta::Type back to a Rust type fn convert_type_back(ty: &Type) -> TokenStream { match &ty { @@ -143,10 +181,7 @@ fn fn_type_assertions(sig: &syn::Signature) -> TokenStream { _ => Some(&pat_ty.ty), }, }); - let output_type = match &sig.output { - syn::ReturnType::Default => None, - syn::ReturnType::Type(_, ty) => Some(ty), - }; + let output_type = sig.output.as_ref().map(|s| &s.ty); let type_assertions: BTreeMap<_, _> = input_types .chain(output_type) @@ -158,6 +193,18 @@ fn fn_type_assertions(sig: &syn::Signature) -> TokenStream { }) }) .collect(); + let input_output_type_assertions: TokenStream = type_assertions.into_values().collect(); + + let throws_type_assertion = sig.output.as_ref().and_then(|s| { + let ident = s.throws.as_ref()?; + Some(assert_type_eq( + ident, + quote! { crate::uniffi_types::#ident }, + )) + }); - type_assertions.into_values().collect() + quote! { + #input_output_type_assertions + #throws_type_assertion + } } diff --git a/uniffi_macros/src/export/metadata/convert.rs b/uniffi_macros/src/export/metadata/convert.rs index 2d0027b695..6d8d87acc8 100644 --- a/uniffi_macros/src/export/metadata/convert.rs +++ b/uniffi_macros/src/export/metadata/convert.rs @@ -4,12 +4,9 @@ use proc_macro2::Ident; use quote::ToTokens; -use syn::{punctuated::Punctuated, Token}; use uniffi_meta::{FnParamMetadata, Type}; -pub(super) fn fn_param_metadata( - params: &Punctuated, -) -> syn::Result> { +pub(super) fn fn_param_metadata(params: &[syn::FnArg]) -> syn::Result> { params .iter() .filter_map(|a| { @@ -37,13 +34,6 @@ pub(super) fn fn_param_metadata( .collect() } -pub(super) fn return_type_metadata(ty: &syn::ReturnType) -> syn::Result> { - Ok(match ty { - syn::ReturnType::Default => None, - syn::ReturnType::Type(_, ty) => Some(convert_type(ty)?), - }) -} - pub(crate) fn convert_type(ty: &syn::Type) -> syn::Result { let type_path = type_as_type_path(ty)?; @@ -119,11 +109,7 @@ fn convert_generic_type1(ident: &Ident, arg: &syn::GenericArgument) -> syn::Resu let arg = arg_as_type(arg)?; match ident.to_string().as_str() { "Arc" => Ok(Type::ArcObject { - object_name: type_as_type_path(arg)? - .path - .get_ident() - .ok_or_else(|| type_not_supported(arg))? - .to_string(), + object_name: type_as_type_name(arg)?.to_string(), }), "Option" => Ok(Type::Option { inner_type: convert_type(arg)?.into(), @@ -152,6 +138,13 @@ fn convert_generic_type2( } } +fn type_as_type_name(arg: &syn::Type) -> syn::Result<&Ident> { + type_as_type_path(arg)? + .path + .get_ident() + .ok_or_else(|| type_not_supported(arg)) +} + pub(super) fn type_as_type_path(ty: &syn::Type) -> syn::Result<&syn::TypePath> { match ty { syn::Type::Group(g) => type_as_type_path(&g.elem), @@ -177,3 +170,46 @@ fn type_not_supported(ty: &impl ToTokens) -> syn::Error { "this type is not currently supported by uniffi::export in this position", ) } + +pub(crate) fn try_split_result(ty: &syn::Type) -> syn::Result> { + let type_path = type_as_type_path(ty)?; + + if type_path.qself.is_some() { + return Err(syn::Error::new_spanned( + type_path, + "qualified self types are not currently supported by uniffi::export", + )); + } + + if type_path.path.segments.len() > 1 { + return Err(syn::Error::new_spanned( + type_path, + "qualified paths in types are not currently supported by uniffi::export", + )); + } + + let (ident, a) = match &type_path.path.segments.first() { + Some(seg) => match &seg.arguments { + syn::PathArguments::AngleBracketed(a) => (&seg.ident, a), + syn::PathArguments::None | syn::PathArguments::Parenthesized(_) => return Ok(None), + }, + None => return Ok(None), + }; + + let mut it = a.args.iter(); + if let Some(arg1) = it.next() { + if let Some(arg2) = it.next() { + if it.next().is_none() { + let arg1 = arg_as_type(arg1)?; + let arg2 = arg_as_type(arg2)?; + + if let "Result" = ident.to_string().as_str() { + let throws = type_as_type_name(arg2)?.to_owned(); + return Ok(Some((arg1, throws))); + } + } + } + } + + Ok(None) +} diff --git a/uniffi_macros/src/export/metadata/function.rs b/uniffi_macros/src/export/metadata/function.rs index b19e8108c6..9c4edb9869 100644 --- a/uniffi_macros/src/export/metadata/function.rs +++ b/uniffi_macros/src/export/metadata/function.rs @@ -4,23 +4,29 @@ use uniffi_meta::FnMetadata; -use super::convert::{fn_param_metadata, return_type_metadata}; -use crate::export::ExportItem; +use super::convert::{convert_type, fn_param_metadata}; +use crate::export::{ExportItem, Signature}; pub(super) fn gen_fn_metadata(sig: syn::Signature, mod_path: &[String]) -> syn::Result { + let sig = Signature::new(sig)?; let metadata = fn_metadata(&sig, mod_path)?; - - Ok(ExportItem::Function { - sig: Box::new(sig), - metadata, - }) + Ok(ExportItem::Function { sig, metadata }) } -fn fn_metadata(sig: &syn::Signature, mod_path: &[String]) -> syn::Result { +fn fn_metadata(sig: &Signature, mod_path: &[String]) -> syn::Result { + let (return_type, throws) = match &sig.output { + Some(ret) => { + let ty = convert_type(&ret.ty)?; + (Some(ty), ret.throws.as_ref().map(ToString::to_string)) + } + None => (None, None), + }; + Ok(FnMetadata { module_path: mod_path.to_owned(), name: sig.ident.to_string(), inputs: fn_param_metadata(&sig.inputs)?, - return_type: return_type_metadata(&sig.output)?, + return_type, + throws, }) } diff --git a/uniffi_macros/src/export/metadata/impl_.rs b/uniffi_macros/src/export/metadata/impl_.rs index 85b23d8c10..27c43a2bd9 100644 --- a/uniffi_macros/src/export/metadata/impl_.rs +++ b/uniffi_macros/src/export/metadata/impl_.rs @@ -4,8 +4,8 @@ use uniffi_meta::MethodMetadata; -use super::convert::{fn_param_metadata, return_type_metadata, type_as_type_path}; -use crate::export::{ExportItem, Method}; +use super::convert::{convert_type, fn_param_metadata, type_as_type_path}; +use crate::export::{ExportItem, Method, Signature}; pub(super) fn gen_impl_metadata( item: syn::ItemImpl, @@ -55,7 +55,7 @@ fn gen_method_metadata( mod_path: &[String], ) -> syn::Result { let sig = match it { - syn::ImplItem::Method(m) => m.sig, + syn::ImplItem::Method(m) => Signature::new(m.sig)?, _ => { return Err(syn::Error::new_spanned( it, @@ -71,14 +71,23 @@ fn gen_method_metadata( fn method_metadata( self_name: &str, - sig: &syn::Signature, + sig: &Signature, mod_path: &[String], ) -> syn::Result { + let (return_type, throws) = match &sig.output { + Some(ret) => { + let ty = convert_type(&ret.ty)?; + (Some(ty), ret.throws.as_ref().map(ToString::to_string)) + } + None => (None, None), + }; + Ok(MethodMetadata { module_path: mod_path.to_owned(), self_name: self_name.to_owned(), name: sig.ident.to_string(), inputs: fn_param_metadata(&sig.inputs)?, - return_type: return_type_metadata(&sig.output)?, + return_type, + throws, }) } diff --git a/uniffi_macros/src/export/scaffolding.rs b/uniffi_macros/src/export/scaffolding.rs index 61195612de..dcf9ab166f 100644 --- a/uniffi_macros/src/export/scaffolding.rs +++ b/uniffi_macros/src/export/scaffolding.rs @@ -4,7 +4,9 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; -use syn::{FnArg, Pat, ReturnType, Signature}; +use syn::{FnArg, Pat}; + +use super::{FunctionReturn, Signature}; pub(super) fn gen_fn_scaffolding( sig: &Signature, @@ -31,7 +33,7 @@ pub(super) fn gen_fn_scaffolding( } pub(super) fn gen_method_scaffolding( - sig: &syn::Signature, + sig: &Signature, mod_path: &[String], checksum: u16, self_ident: &Ident, @@ -130,6 +132,8 @@ fn collect_params<'a>( let arg_n = format_ident!("arg{i}"); let param = quote! { #arg_n: <#ty as ::uniffi::FfiConverter>::FfiType }; + // FIXME: With UDL, fallible functions use uniffi::lower_anyhow_error_or_panic instead of + // panicking unconditionally. This seems cleaner though. let panic_fmt = match name { Some(name) => format!("Failed to convert arg '{name}': {{}}"), None => format!("Failed to convert arg #{i}: {{}}"), @@ -145,7 +149,7 @@ fn collect_params<'a>( } fn gen_ffi_function( - sig: &syn::Signature, + sig: &Signature, ffi_ident: Ident, params: &[TokenStream], rust_fn_call: TokenStream, @@ -157,16 +161,34 @@ fn gen_ffi_function( // well as `()` so no different codegen is needed? let (output, return_expr); match &sig.output { - ReturnType::Default => { - output = None; - return_expr = rust_fn_call; - } - ReturnType::Type(_, ty) => { + Some(FunctionReturn { ty, throws }) => { output = Some(quote! { -> <#ty as ::uniffi::FfiConverter>::FfiType }); + + return_expr = if let Some(error_ident) = throws { + quote! { + ::uniffi::call_with_result(call_status, || { + let val = #rust_fn_call.map_err(|e| { + <#error_ident as ::uniffi::FfiConverter>::lower( + ::std::convert::Into::into(e), + ) + })?; + Ok(<#ty as ::uniffi::FfiConverter>::lower(val)) + }) + } + } else { + quote! { + ::uniffi::call_with_output(call_status, || { + <#ty as ::uniffi::FfiConverter>::lower(#rust_fn_call) + }) + } + }; + } + None => { + output = None; return_expr = quote! { - <#ty as ::uniffi::FfiConverter>::lower(#rust_fn_call) + ::uniffi::call_with_output(call_status, || #rust_fn_call) }; } } @@ -179,9 +201,7 @@ fn gen_ffi_function( call_status: &mut ::uniffi::RustCallStatus, ) #output { ::uniffi::deps::log::debug!(#name_s); - ::uniffi::call_with_output(call_status, || { - #return_expr - }) + #return_expr } } } diff --git a/uniffi_meta/src/lib.rs b/uniffi_meta/src/lib.rs index 6029861ee3..06ba0ced05 100644 --- a/uniffi_meta/src/lib.rs +++ b/uniffi_meta/src/lib.rs @@ -15,6 +15,7 @@ pub struct FnMetadata { pub name: String, pub inputs: Vec, pub return_type: Option, + pub throws: Option, } impl FnMetadata { @@ -30,6 +31,7 @@ pub struct MethodMetadata { pub name: String, pub inputs: Vec, pub return_type: Option, + pub throws: Option, } impl MethodMetadata {