Skip to content

Commit

Permalink
proc-macro: Add support for fallible functions
Browse files Browse the repository at this point in the history
  • Loading branch information
jplatte committed Nov 23, 2022
1 parent bb783e4 commit bdd9532
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 60 deletions.
6 changes: 6 additions & 0 deletions uniffi_bindgen/src/interface/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ impl FunctionAttributes {
}
}

impl FromIterator<Attribute> for FunctionAttributes {
fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self {
Self(Vec::from_iter(iter))
}
}

impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for FunctionAttributes {
type Error = anyhow::Error;
fn try_from(
Expand Down
9 changes: 3 additions & 6 deletions uniffi_bindgen/src/interface/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -137,7 +134,7 @@ impl From<uniffi_meta::FnMetadata> for Function {
arguments,
return_type,
ffi_func,
attributes: Default::default(),
attributes: meta.throws.map(Attribute::Throws).into_iter().collect(),
}
}
}
Expand Down
71 changes: 59 additions & 12 deletions uniffi_macros/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<syn::Signature>,
sig: Signature,
metadata: FnMetadata,
},
Impl {
Expand All @@ -33,10 +33,48 @@ pub enum ExportItem {
}

pub struct Method {
sig: syn::Signature,
sig: Signature,
metadata: MethodMetadata,
}

pub struct Signature {
ident: Ident,
inputs: Vec<syn::FnArg>,
output: Option<FunctionReturn>,
}

impl Signature {
fn new(item: syn::Signature) -> syn::Result<Self> {
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<syn::Type>,
throws: Option<Ident>,
}

impl FunctionReturn {
fn new(ty: Box<syn::Type>) -> syn::Result<Self> {
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 } => {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
}
68 changes: 52 additions & 16 deletions uniffi_macros/src/export/metadata/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::FnArg, Token![,]>,
) -> syn::Result<Vec<FnParamMetadata>> {
pub(super) fn fn_param_metadata(params: &[syn::FnArg]) -> syn::Result<Vec<FnParamMetadata>> {
params
.iter()
.filter_map(|a| {
Expand Down Expand Up @@ -37,13 +34,6 @@ pub(super) fn fn_param_metadata(
.collect()
}

pub(super) fn return_type_metadata(ty: &syn::ReturnType) -> syn::Result<Option<Type>> {
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<Type> {
let type_path = type_as_type_path(ty)?;

Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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),
Expand All @@ -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<Option<(&syn::Type, Ident)>> {
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)
}
24 changes: 15 additions & 9 deletions uniffi_macros/src/export/metadata/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ExportItem> {
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<FnMetadata> {
fn fn_metadata(sig: &Signature, mod_path: &[String]) -> syn::Result<FnMetadata> {
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,
})
}
19 changes: 14 additions & 5 deletions uniffi_macros/src/export/metadata/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -55,7 +55,7 @@ fn gen_method_metadata(
mod_path: &[String],
) -> syn::Result<Method> {
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,
Expand All @@ -71,14 +71,23 @@ fn gen_method_metadata(

fn method_metadata(
self_name: &str,
sig: &syn::Signature,
sig: &Signature,
mod_path: &[String],
) -> syn::Result<MethodMetadata> {
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,
})
}
Loading

0 comments on commit bdd9532

Please sign in to comment.