Skip to content

Commit

Permalink
proc-macro: Properly support flat errors
Browse files Browse the repository at this point in the history
  • Loading branch information
jplatte committed Dec 13, 2022
1 parent ff54efa commit 7a24e53
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 37 deletions.
20 changes: 16 additions & 4 deletions fixtures/proc-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,31 @@ fn enum_identity(value: MaybeBool) -> MaybeBool {
pub enum BasicError {
InvalidInput,
OsError,
ThisIsNotAFlatErrorType { field: u32 },
}

#[uniffi::export]
fn always_fails() -> Result<(), BasicError> {
Err(BasicError::OsError)
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
#[non_exhaustive]
pub enum FlatError {
#[error("Invalid input")]
InvalidInput,

// Inner types that aren't FFI-convertible, as well as unnamed fields,
// are allowed for flat errors
#[error("OS error: {0}")]
OsError(std::io::Error),
}

#[uniffi::export]
impl Object {
fn do_stuff(&self, times: u32) -> Result<(), BasicError> {
fn do_stuff(&self, times: u32) -> Result<(), FlatError> {
match times {
0 => Err(BasicError::InvalidInput),
0 => Err(FlatError::InvalidInput),
_ => {
// do stuff
Ok(())
Expand All @@ -91,5 +103,5 @@ impl Object {
include!(concat!(env!("OUT_DIR"), "/proc-macro.uniffi.rs"));

mod uniffi_types {
pub use crate::{BasicError, MaybeBool, NestedRecord, Object, One, Three, Two};
pub use crate::{BasicError, FlatError, MaybeBool, NestedRecord, Object, One, Three, Two};
}
2 changes: 1 addition & 1 deletion fixtures/proc-macro/tests/bindings/test_proc_macro.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ obj.doStuff(5u)
try {
obj.doStuff(0u)
throw RuntimeException("doStuff should throw if its argument is 0")
} catch (e: BasicException) {
} catch (e: FlatException) {
}
2 changes: 1 addition & 1 deletion fixtures/proc-macro/tests/bindings/test_proc_macro.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

try:
obj.do_stuff(0)
except BasicError.InvalidInput:
except FlatError.InvalidInput:
pass
else:
raise Exception("do_stuff should throw if its argument is 0")
2 changes: 1 addition & 1 deletion fixtures/proc-macro/tests/bindings/test_proc_macro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ try! obj.doStuff(times: 5)
do {
try obj.doStuff(times: 0)
fatalError("doStuff should throw if its argument is 0")
} catch BasicError.InvalidInput {
} catch FlatError.InvalidInput {
}
3 changes: 1 addition & 2 deletions uniffi_bindgen/src/interface/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,12 @@ impl Error {

impl From<uniffi_meta::ErrorMetadata> for Error {
fn from(meta: uniffi_meta::ErrorMetadata) -> Self {
let flat = meta.variants.iter().all(|v| v.fields.is_empty());
Self {
name: meta.name.clone(),
enum_: Enum {
name: meta.name,
variants: meta.variants.into_iter().map(Into::into).collect(),
flat,
flat: meta.flat,
},
}
}
Expand Down
6 changes: 3 additions & 3 deletions uniffi_macros/src/enum_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub fn expand_enum(input: DeriveInput, module_path: Vec<String>) -> TokenStream

let ident = &input.ident;

let ffi_converter_impl = enum_ffi_converter_impl(&variants, ident);
let ffi_converter_impl = enum_ffi_converter_impl(variants.as_ref(), ident);

let meta_static_var = if let Some(variants) = variants {
match enum_metadata(ident, variants, module_path) {
Expand All @@ -38,7 +38,7 @@ pub fn expand_enum(input: DeriveInput, module_path: Vec<String>) -> TokenStream
}

pub(crate) fn enum_ffi_converter_impl(
variants: &Option<Punctuated<Variant, Token![,]>>,
variants: Option<&Punctuated<Variant, Token![,]>>,
ident: &Ident,
) -> TokenStream {
let (write_impl, try_read_impl) = match variants {
Expand Down Expand Up @@ -150,7 +150,7 @@ fn field_metadata(f: &Field, v: &Variant) -> syn::Result<FieldMetadata> {
})
}

pub fn write_field(f: &Field) -> TokenStream {
fn write_field(f: &Field) -> TokenStream {
let ident = &f.ident;
let ty = &f.ty;

Expand Down
149 changes: 128 additions & 21 deletions uniffi_macros/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,62 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{punctuated::Punctuated, Data, DeriveInput, Token, Variant};
use uniffi_meta::ErrorMetadata;
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Data, DeriveInput, Index, Token, Variant,
};
use uniffi_meta::{ErrorMetadata, VariantMetadata};

use crate::{
enum_::{enum_ffi_converter_impl, variant_metadata},
util::{assert_type_eq, create_metadata_static_var},
util::{
assert_type_eq, chain, create_metadata_static_var, either_attribute_arg, AttributeSliceExt,
UniffiAttribute,
},
};

pub fn expand_error(input: DeriveInput, module_path: Vec<String>) -> TokenStream {
let variants = match input.data {
Data::Enum(e) => Some(e.variants),
_ => None,
Data::Enum(e) => Ok(e.variants),
_ => Err(syn::Error::new(
Span::call_site(),
"This derive currently only supports enums",
)),
};

let ident = &input.ident;
let attr = input.attrs.parse_uniffi_attributes::<ErrorAttr>();
let ffi_converter_impl = match &attr {
Ok(a) if a.flat.is_some() => flat_error_ffi_converter_impl(variants.as_ref().ok(), ident),
_ => enum_ffi_converter_impl(variants.as_ref().ok(), ident),
};

let ffi_converter_impl = enum_ffi_converter_impl(&variants, ident);

let meta_static_var = if let Some(variants) = variants {
match error_metadata(ident, variants, module_path) {
let meta_static_var = match (&variants, &attr) {
(Ok(vs), Ok(a)) => Some(match error_metadata(ident, vs, module_path, a) {
Ok(metadata) => create_metadata_static_var(ident, metadata.into()),
Err(e) => e.into_compile_error(),
}
} else {
syn::Error::new(
Span::call_site(),
"This derive currently only supports enums",
)
.into_compile_error()
}),
_ => None,
};

let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident });
let variant_errors: TokenStream = match variants {
Ok(vs) => vs
.iter()
.flat_map(|variant| {
chain(
variant.attrs.attributes_not_allowed_here(),
variant
.fields
.iter()
.flat_map(|field| field.attrs.attributes_not_allowed_here()),
)
})
.map(syn::Error::into_compile_error)
.collect(),
Err(e) => e.into_compile_error(),
};
let attr_error = attr.err().map(syn::Error::into_compile_error);

quote! {
#ffi_converter_impl
Expand All @@ -41,23 +66,105 @@ pub fn expand_error(input: DeriveInput, module_path: Vec<String>) -> TokenStream

#meta_static_var
#type_assertion
#variant_errors
#attr_error
}
}

pub(crate) fn flat_error_ffi_converter_impl(
variants: Option<&Punctuated<Variant, Token![,]>>,
ident: &Ident,
) -> TokenStream {
let write_impl = match variants {
Some(variants) => {
let write_match_arms = variants.iter().enumerate().map(|(i, v)| {
let v_ident = &v.ident;
let idx = Index::from(i + 1);

quote! {
Self::#v_ident { .. } => {
::uniffi::deps::bytes::BufMut::put_i32(buf, #idx);
<::std::string::String as ::uniffi::FfiConverter>::write(error_msg, buf);
}
}
});
let write_impl = quote! {
let error_msg = ::std::string::ToString::to_string(&obj);
match obj { #(#write_match_arms)* }
};

write_impl
}
None => quote! { ::std::unimplemented!() },
};

quote! {
#[automatically_derived]
impl ::uniffi::RustBufferFfiConverter for #ident {
type RustType = Self;

fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) {
#write_impl
}

fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> {
::std::panic!("try_read not supported for flat errors");
}
}
}
}

fn error_metadata(
ident: &Ident,
variants: Punctuated<Variant, Token![,]>,
variants: &Punctuated<Variant, Token![,]>,
module_path: Vec<String>,
attr: &ErrorAttr,
) -> syn::Result<ErrorMetadata> {
let name = ident.to_string();
let variants = variants
.iter()
.map(variant_metadata)
.collect::<syn::Result<_>>()?;
let flat = attr.flat.is_some();
let variants = if flat {
variants
.iter()
.map(|v| VariantMetadata {
name: v.ident.to_string(),
fields: vec![],
})
.collect()
} else {
variants
.iter()
.map(variant_metadata)
.collect::<syn::Result<_>>()?
};

Ok(ErrorMetadata {
module_path,
name,
variants,
flat,
})
}

mod kw {
syn::custom_keyword!(flat_error);
}

#[derive(Default)]
struct ErrorAttr {
flat: Option<kw::flat_error>,
}

impl Parse for ErrorAttr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let flat = input.parse()?;
Ok(ErrorAttr { flat })
}
}

impl UniffiAttribute for ErrorAttr {
fn merge(self, other: Self) -> syn::Result<Self> {
Ok(Self {
flat: either_attribute_arg(self.flat, other.flat)?,
})
}
}
2 changes: 1 addition & 1 deletion uniffi_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ pub fn derive_object(input: TokenStream) -> TokenStream {
expand_object(input, mod_path).into()
}

#[proc_macro_derive(Error)]
#[proc_macro_derive(Error, attributes(uniffi))]
pub fn derive_error(input: TokenStream) -> TokenStream {
let mod_path = match util::mod_path() {
Ok(p) => p,
Expand Down
2 changes: 1 addition & 1 deletion uniffi_macros/src/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ fn field_metadata(f: &Field) -> syn::Result<FieldMetadata> {
})
}

pub fn write_field(f: &Field) -> TokenStream {
fn write_field(f: &Field) -> TokenStream {
let ident = &f.ident;
let ty = &f.ty;

Expand Down
71 changes: 70 additions & 1 deletion uniffi_macros/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@

use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, visit_mut::VisitMut, Item, Type};
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
visit_mut::VisitMut,
Attribute, Item, Token, Type,
};
use uniffi_meta::Metadata;

#[cfg(not(feature = "nightly"))]
Expand Down Expand Up @@ -152,3 +158,66 @@ pub fn assert_type_eq(a: impl ToTokens + Spanned, b: impl ToTokens) -> TokenStre
};
}
}

pub fn chain<T>(
a: impl IntoIterator<Item = T>,
b: impl IntoIterator<Item = T>,
) -> impl Iterator<Item = T> {
a.into_iter().chain(b)
}

pub trait UniffiAttribute: Default + Parse {
fn merge(self, other: Self) -> syn::Result<Self>;
}

#[derive(Default)]
struct AttributeNotAllowedHere;

impl Parse for AttributeNotAllowedHere {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
Err(syn::Error::new(
input.span(),
"UniFFI attributes are not currently recognized in this position",
))
}
}

impl UniffiAttribute for AttributeNotAllowedHere {
fn merge(self, _other: Self) -> syn::Result<Self> {
Ok(Self)
}
}

pub trait AttributeSliceExt {
fn parse_uniffi_attributes<T: UniffiAttribute>(&self) -> syn::Result<T>;
fn attributes_not_allowed_here(&self) -> Option<syn::Error>;
}

impl AttributeSliceExt for [Attribute] {
fn parse_uniffi_attributes<T: UniffiAttribute>(&self) -> syn::Result<T> {
self.iter()
.filter(|attr| attr.path.is_ident("uniffi"))
.try_fold(T::default(), |res, attr| {
let list: Punctuated<T, Token![,]> =
attr.parse_args_with(Punctuated::parse_terminated)?;
list.into_iter().try_fold(res, T::merge)
})
}

fn attributes_not_allowed_here(&self) -> Option<syn::Error> {
self.parse_uniffi_attributes::<AttributeNotAllowedHere>()
.err()
}
}

pub fn either_attribute_arg<T: ToTokens>(a: Option<T>, b: Option<T>) -> syn::Result<Option<T>> {
match (a, b) {
(None, None) => Ok(None),
(Some(val), None) | (None, Some(val)) => Ok(Some(val)),
(Some(a), Some(b)) => {
let mut error = syn::Error::new_spanned(a, "redundant attribute argument");
error.combine(syn::Error::new_spanned(b, "note: first one here"));
Err(error)
}
}
}
Loading

0 comments on commit 7a24e53

Please sign in to comment.