diff --git a/fixtures/uitests/tests/ui/fieldless_errors_used_in_callbacks_cant_have_fields.stderr b/fixtures/uitests/tests/ui/fieldless_errors_used_in_callbacks_cant_have_fields.stderr index fdb3b15fb3..79211e6568 100644 --- a/fixtures/uitests/tests/ui/fieldless_errors_used_in_callbacks_cant_have_fields.stderr +++ b/fixtures/uitests/tests/ui/fieldless_errors_used_in_callbacks_cant_have_fields.stderr @@ -1,10 +1,7 @@ error[E0533]: expected value, found struct variant `Self::DivisionByZero` --> $OUT_DIR[uniffi_uitests]/errors.uniffi.rs | - | / #[::uniffi::derive_error_for_udl( - | | flat_error, - | | with_try_read, - | | )] - | |__^ not a value + | #[::uniffi::udl_derive(Error)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not a value | - = note: this error originates in the attribute macro `::uniffi::derive_error_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `::uniffi::udl_derive` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr b/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr index 46a747f9bb..f32a1e4a25 100644 --- a/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr +++ b/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr @@ -14,9 +14,9 @@ note: required because it appears within the type `Counter` note: required by a bound in `_::{closure#0}::assert_impl_all` --> $OUT_DIR[uniffi_uitests]/counter.uniffi.rs | - | #[::uniffi::derive_object_for_udl] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl_all` - = note: this error originates in the macro `uniffi::deps::static_assertions::assert_impl_all` which comes from the expansion of the attribute macro `::uniffi::derive_object_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info) + | #[::uniffi::udl_derive(Object)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl_all` + = note: this error originates in the macro `uniffi::deps::static_assertions::assert_impl_all` which comes from the expansion of the attribute macro `::uniffi::udl_derive` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `Cell` cannot be shared between threads safely --> $OUT_DIR[uniffi_uitests]/counter.uniffi.rs diff --git a/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs index f918ef2f3a..20b845d515 100644 --- a/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs @@ -2,11 +2,10 @@ // Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent. #} -#[::uniffi::derive_enum_for_udl( - {%- if e.is_non_exhaustive() -%} - non_exhaustive, - {%- endif %} -)] +#[::uniffi::udl_derive(Enum)] +{%- if e.is_non_exhaustive() %} +#[non_exhaustive] +{%- endif %} enum r#{{ e.name() }} { {%- for variant in e.variants() %} r#{{ variant.name() }} { diff --git a/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs index 64f48e2334..2529c73421 100644 --- a/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs @@ -2,17 +2,16 @@ // Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent. #} -#[::uniffi::derive_error_for_udl( - {% if e.is_flat() -%} - flat_error, - {% if ci.should_generate_error_read(e) -%} - with_try_read, - {%- endif %} - {%- endif %} - {%- if e.is_non_exhaustive() -%} - non_exhaustive, - {%- endif %} -)] +#[::uniffi::udl_derive(Error)] +{% if e.is_flat() -%} +#[uniffi(flat_error)] +{% if ci.should_generate_error_read(e) -%} +#[uniffi(with_try_read)] +{%- endif %} +{%- endif %} +{%- if e.is_non_exhaustive() -%} +#[non_exhaustive] +{%- endif %} enum r#{{ e.name() }} { {%- for variant in e.variants() %} r#{{ variant.name() }} { diff --git a/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs index e752878af5..f307f243a6 100644 --- a/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs @@ -33,7 +33,7 @@ pub trait r#{{ obj.name() }} { #[uniffi::export(Eq)] {% endmatch %} {% endfor %} -#[::uniffi::derive_object_for_udl] +#[::uniffi::udl_derive(Object)] struct {{ obj.rust_name() }} { } {%- for cons in obj.constructors() %} diff --git a/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs index a7affdf7b8..c612dafda9 100644 --- a/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs @@ -2,7 +2,7 @@ // Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent. #} -#[::uniffi::derive_record_for_udl] +#[::uniffi::udl_derive(Record)] struct r#{{ rec.name() }} { {%- for field in rec.fields() %} r#{{ field.name() }}: {{ field.as_type().borrow()|type_rs }}, diff --git a/uniffi_macros/src/derive.rs b/uniffi_macros/src/derive.rs new file mode 100644 index 0000000000..571b5e88a5 --- /dev/null +++ b/uniffi_macros/src/derive.rs @@ -0,0 +1,119 @@ +/* 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/. */ + +//! General handling for the derive and udl_derive macros + +use crate::util::kw; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + DeriveInput, +}; + +pub fn expand_derive( + kind: DeriveKind, + input: DeriveInput, + options: DeriveOptions, +) -> syn::Result { + match kind { + DeriveKind::Record(_) => crate::record::expand_record(input, options), + DeriveKind::Object(_) => crate::object::expand_object(input, options), + DeriveKind::Enum(_) => crate::enum_::expand_enum(input, options), + DeriveKind::Error(_) => crate::error::expand_error(input, options), + } +} + +pub enum DeriveKind { + Record(kw::Record), + Enum(kw::Enum), + Error(kw::Error), + Object(kw::Object), +} + +impl Parse for DeriveKind { + fn parse(input: ParseStream<'_>) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::Record) { + Ok(Self::Record(input.parse()?)) + } else if lookahead.peek(kw::Enum) { + Ok(Self::Enum(input.parse()?)) + } else if lookahead.peek(kw::Error) { + Ok(Self::Error(input.parse()?)) + } else if lookahead.peek(kw::Object) { + Ok(Self::Object(input.parse()?)) + } else { + Err(lookahead.error()) + } + } +} + +pub struct DeriveOptions { + /// Should we implement FFI traits for the local UniFfiTag only? + pub local_tag: bool, + /// Should we generate metadata symbols? + pub generate_metadata: bool, +} + +/// default() is used to construct a DeriveOptions for a regular `derive` invocation +impl Default for DeriveOptions { + fn default() -> Self { + Self { + local_tag: false, + generate_metadata: true, + } + } +} + +impl DeriveOptions { + /// Construct DeriveOptions for `udl_derive` + pub fn udl_derive() -> Self { + Self { + local_tag: true, + generate_metadata: false, + } + } + + /// Generate the impl header for a FFI trait + /// + /// This will output something like `impl FfiConverter for #type`. The caller is + /// responsible for providing the body if the impl block. + pub fn ffi_impl_header(&self, trait_name: &str, ident: &impl ToTokens) -> TokenStream { + let trait_name = Ident::new(trait_name, Span::call_site()); + if self.local_tag { + quote! { impl ::uniffi::#trait_name for #ident } + } else { + quote! { impl ::uniffi::#trait_name for #ident } + } + } + + /// Generate a call to `derive_ffi_traits!` that will derive all the FFI traits + pub fn derive_all_ffi_traits(&self, ty: &Ident) -> TokenStream { + if self.local_tag { + quote! { ::uniffi::derive_ffi_traits!(local #ty); } + } else { + quote! { ::uniffi::derive_ffi_traits!(blanket #ty); } + } + } + + /// Generate a call to `derive_ffi_traits!` that will derive some of the FFI traits + pub fn derive_ffi_traits(&self, ty: impl ToTokens, trait_names: &[&str]) -> TokenStream { + let trait_idents = trait_names + .iter() + .map(|name| Ident::new(name, Span::call_site())); + if self.local_tag { + quote! { + #( + ::uniffi::derive_ffi_traits!(impl #trait_idents for #ty); + )* + } + } else { + quote! { + #( + ::uniffi::derive_ffi_traits!(impl #trait_idents for #ty); + )* + } + } + } +} diff --git a/uniffi_macros/src/enum_.rs b/uniffi_macros/src/enum_.rs index b1644ec0b5..4908035d65 100644 --- a/uniffi_macros/src/enum_.rs +++ b/uniffi_macros/src/enum_.rs @@ -1,72 +1,136 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{ - parse::{Parse, ParseStream}, - spanned::Spanned, - Attribute, Data, DataEnum, DeriveInput, Expr, Index, Lit, Variant, + parse::ParseStream, spanned::Spanned, Attribute, Data, DataEnum, DeriveInput, Expr, Index, Lit, + Variant, }; use crate::{ ffiops, util::{ - create_metadata_items, derive_all_ffi_traits, either_attribute_arg, extract_docstring, - ident_to_string, kw, mod_path, parse_comma_separated, tagged_impl_header, - try_metadata_value_from_usize, try_read_field, AttributeSliceExt, UniffiAttributeArgs, + create_metadata_items, either_attribute_arg, extract_docstring, ident_to_string, kw, + mod_path, try_metadata_value_from_usize, try_read_field, AttributeSliceExt, + UniffiAttributeArgs, }, + DeriveOptions, }; -fn extract_repr(attrs: &[Attribute]) -> syn::Result> { - let mut result = None; - for attr in attrs { - if attr.path().is_ident("repr") { - attr.parse_nested_meta(|meta| { - result = match meta.path.get_ident() { - Some(i) => { - let s = i.to_string(); - match s.as_str() { - "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" - | "i64" | "isize" => Some(i.clone()), - // while the default repr for an enum is `isize` we don't apply that default here. - _ => None, +/// Stores parsed data from the Derive Input for the enum. +pub struct EnumItem { + ident: Ident, + enum_: DataEnum, + docstring: String, + discr_type: Option, + non_exhaustive: bool, + attr: EnumAttr, +} + +impl EnumItem { + pub fn new(input: DeriveInput) -> syn::Result { + let enum_ = match input.data { + Data::Enum(e) => e, + _ => { + return Err(syn::Error::new( + Span::call_site(), + "This derive must only be used on enums", + )) + } + }; + Ok(Self { + enum_, + ident: input.ident, + docstring: extract_docstring(&input.attrs)?, + discr_type: Self::extract_repr(&input.attrs)?, + non_exhaustive: Self::extract_non_exhaustive(&input.attrs), + attr: input.attrs.parse_uniffi_attr_args()?, + }) + } + + pub fn extract_repr(attrs: &[Attribute]) -> syn::Result> { + let mut result = None; + for attr in attrs { + if attr.path().is_ident("repr") { + attr.parse_nested_meta(|meta| { + result = match meta.path.get_ident() { + Some(i) => { + let s = i.to_string(); + match s.as_str() { + "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" + | "i64" | "isize" => Some(i.clone()), + // while the default repr for an enum is `isize` we don't apply that default here. + _ => None, + } } - } - _ => None, - }; - Ok(()) - })? + _ => None, + }; + Ok(()) + })? + } } + Ok(result) } - Ok(result) -} -pub fn expand_enum( - input: DeriveInput, - // Attributes from #[derive_error_for_udl()], if we are in udl mode - attr_from_udl_mode: Option, - udl_mode: bool, -) -> syn::Result { - let enum_ = match input.data { - Data::Enum(e) => e, - _ => { + pub fn extract_non_exhaustive(attrs: &[Attribute]) -> bool { + attrs.iter().any(|a| a.path().is_ident("non_exhaustive")) + } + + pub fn check_attributes_valid_for_enum(&self) -> syn::Result<()> { + if let Some(flat_error) = &self.attr.flat_error { return Err(syn::Error::new( - Span::call_site(), - "This derive must only be used on enums", - )) + flat_error.span(), + "flat_error not allowed for non-error enums", + )); } - }; - let ident = &input.ident; - let docstring = extract_docstring(&input.attrs)?; - let discr_type = extract_repr(&input.attrs)?; - let mut attr: EnumAttr = input.attrs.parse_uniffi_attr_args()?; - if let Some(attr_from_udl_mode) = attr_from_udl_mode { - attr = attr.merge(attr_from_udl_mode)?; + if let Some(with_try_read) = &self.attr.with_try_read { + return Err(syn::Error::new( + with_try_read.span(), + "with_try_read not allowed for non-error enums", + )); + } + Ok(()) } - let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, udl_mode, &attr); - let meta_static_var = (!udl_mode).then(|| { - enum_meta_static_var(ident, docstring, discr_type, &enum_, &attr) - .unwrap_or_else(syn::Error::into_compile_error) - }); + pub fn ident(&self) -> &Ident { + &self.ident + } + + pub fn enum_(&self) -> &DataEnum { + &self.enum_ + } + + pub fn is_non_exhaustive(&self) -> bool { + self.non_exhaustive + } + + pub fn docstring(&self) -> &str { + self.docstring.as_str() + } + + pub fn discr_type(&self) -> Option<&Ident> { + self.discr_type.as_ref() + } + + pub fn name(&self) -> String { + ident_to_string(&self.ident) + } + + pub fn is_flat_error(&self) -> bool { + self.attr.flat_error.is_some() + } + + pub fn generate_error_try_read(&self) -> bool { + self.attr.with_try_read.is_some() + } +} + +pub fn expand_enum(input: DeriveInput, options: DeriveOptions) -> syn::Result { + let item = EnumItem::new(input)?; + item.check_attributes_valid_for_enum()?; + let ffi_converter_impl = enum_ffi_converter_impl(&item, &options); + + let meta_static_var = options + .generate_metadata + .then(|| enum_meta_static_var(&item).unwrap_or_else(syn::Error::into_compile_error)); Ok(quote! { #ffi_converter_impl @@ -74,51 +138,40 @@ pub fn expand_enum( }) } -pub(crate) fn enum_ffi_converter_impl( - ident: &Ident, - enum_: &DataEnum, - udl_mode: bool, - attr: &EnumAttr, -) -> TokenStream { +pub(crate) fn enum_ffi_converter_impl(item: &EnumItem, options: &DeriveOptions) -> TokenStream { enum_or_error_ffi_converter_impl( - ident, - enum_, - udl_mode, - attr, + item, + options, quote! { ::uniffi::metadata::codes::TYPE_ENUM }, ) } pub(crate) fn rich_error_ffi_converter_impl( - ident: &Ident, - enum_: &DataEnum, - udl_mode: bool, - attr: &EnumAttr, + item: &EnumItem, + options: &DeriveOptions, ) -> TokenStream { enum_or_error_ffi_converter_impl( - ident, - enum_, - udl_mode, - attr, + item, + options, quote! { ::uniffi::metadata::codes::TYPE_ENUM }, ) } fn enum_or_error_ffi_converter_impl( - ident: &Ident, - enum_: &DataEnum, - udl_mode: bool, - attr: &EnumAttr, + item: &EnumItem, + options: &DeriveOptions, metadata_type_code: TokenStream, ) -> TokenStream { - let name = ident_to_string(ident); - let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode); - let derive_ffi_traits = derive_all_ffi_traits(ident, udl_mode); + let name = item.name(); + let ident = item.ident(); + let impl_spec = options.ffi_impl_header("FfiConverter", &ident); + let derive_ffi_traits = options.derive_all_ffi_traits(&ident); let mod_path = match mod_path() { Ok(p) => p, Err(e) => return e.into_compile_error(), }; - let mut write_match_arms: Vec<_> = enum_ + let mut write_match_arms: Vec<_> = item + .enum_() .variants .iter() .enumerate() @@ -155,7 +208,7 @@ fn enum_or_error_ffi_converter_impl( } }) .collect(); - if attr.non_exhaustive.is_some() { + if item.is_non_exhaustive() { write_match_arms.push(quote! { _ => panic!("Unexpected variant in non-exhaustive enum"), }) @@ -164,7 +217,7 @@ fn enum_or_error_ffi_converter_impl( match obj { #(#write_match_arms)* } }; - let try_read_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { + let try_read_match_arms = item.enum_().variants.iter().enumerate().map(|(i, v)| { let idx = Index::from(i + 1); let v_ident = &v.ident; let is_tuple = v.fields.iter().any(|f| f.ident.is_none()); @@ -180,7 +233,7 @@ fn enum_or_error_ffi_converter_impl( } } }); - let error_format_string = format!("Invalid {ident} enum value: {{}}"); + let error_format_string = format!("Invalid {name} enum value: {{}}"); let try_read_impl = quote! { ::uniffi::check_remaining(buf, 4)?; @@ -212,16 +265,11 @@ fn enum_or_error_ffi_converter_impl( } } -pub(crate) fn enum_meta_static_var( - ident: &Ident, - docstring: String, - discr_type: Option, - enum_: &DataEnum, - attr: &EnumAttr, -) -> syn::Result { - let name = ident_to_string(ident); +pub(crate) fn enum_meta_static_var(item: &EnumItem) -> syn::Result { + let name = item.name(); let module_path = mod_path()?; - let non_exhaustive = attr.non_exhaustive.is_some(); + let non_exhaustive = item.is_non_exhaustive(); + let docstring = item.docstring(); let mut metadata_expr = quote! { ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM) @@ -229,14 +277,14 @@ pub(crate) fn enum_meta_static_var( .concat_str(#name) .concat_option_bool(None) // forced_flatness }; - metadata_expr.extend(match discr_type { + metadata_expr.extend(match item.discr_type() { None => quote! { .concat_bool(false) }, Some(t) => { let type_id_meta = ffiops::type_id_meta(t); quote! { .concat_bool(true).concat(#type_id_meta) } } }); - metadata_expr.extend(variant_metadata(enum_)?); + metadata_expr.extend(variant_metadata(item)?); metadata_expr.extend(quote! { .concat_bool(#non_exhaustive) .concat_long_str(#docstring) @@ -300,7 +348,8 @@ fn variant_value(v: &Variant) -> syn::Result { }) } -pub fn variant_metadata(enum_: &DataEnum) -> syn::Result> { +pub fn variant_metadata(item: &EnumItem) -> syn::Result> { + let enum_ = item.enum_(); let variants_len = try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?; std::iter::once(Ok(quote! { .concat_value(#variants_len) })) @@ -343,25 +392,31 @@ pub fn variant_metadata(enum_: &DataEnum) -> syn::Result> { .collect() } -#[derive(Default)] +/// Handle #[uniffi(...)] attributes for enums +#[derive(Clone, Default)] pub struct EnumAttr { - pub non_exhaustive: Option, -} - -// So ErrorAttr can be used with `parse_macro_input!` -impl Parse for EnumAttr { - fn parse(input: ParseStream<'_>) -> syn::Result { - parse_comma_separated(input) - } + // All of these attributes are only relevant for errors, but they're defined here so that we + // can reuse EnumItem for errors. + pub flat_error: Option, + pub with_try_read: Option, } impl UniffiAttributeArgs for EnumAttr { fn parse_one(input: ParseStream<'_>) -> syn::Result { let lookahead = input.lookahead1(); - if lookahead.peek(kw::non_exhaustive) { + if lookahead.peek(kw::flat_error) { + Ok(Self { + flat_error: input.parse()?, + ..Self::default() + }) + } else if lookahead.peek(kw::with_try_read) { Ok(Self { - non_exhaustive: input.parse()?, + with_try_read: input.parse()?, + ..Self::default() }) + } else if lookahead.peek(kw::handle_unknown_callback_error) { + // Not used anymore, but still allowed + Ok(Self::default()) } else { Err(lookahead.error()) } @@ -369,7 +424,8 @@ impl UniffiAttributeArgs for EnumAttr { fn merge(self, other: Self) -> syn::Result { Ok(Self { - non_exhaustive: either_attribute_arg(self.non_exhaustive, other.non_exhaustive)?, + flat_error: either_attribute_arg(self.flat_error, other.flat_error)?, + with_try_read: either_attribute_arg(self.with_try_read, other.with_try_read)?, }) } } diff --git a/uniffi_macros/src/error.rs b/uniffi_macros/src/error.rs index 87ebcf0eaf..0ef023423a 100644 --- a/uniffi_macros/src/error.rs +++ b/uniffi_macros/src/error.rs @@ -1,48 +1,26 @@ -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::TokenStream; use quote::quote; -use syn::{ - parse::{Parse, ParseStream}, - Data, DataEnum, DeriveInput, Index, -}; +use syn::{DeriveInput, Index}; use crate::{ - enum_::{rich_error_ffi_converter_impl, variant_metadata, EnumAttr}, + enum_::{rich_error_ffi_converter_impl, variant_metadata, EnumItem}, ffiops, util::{ - chain, create_metadata_items, derive_ffi_traits, either_attribute_arg, extract_docstring, - ident_to_string, kw, mod_path, parse_comma_separated, tagged_impl_header, - try_metadata_value_from_usize, AttributeSliceExt, UniffiAttributeArgs, + chain, create_metadata_items, extract_docstring, ident_to_string, mod_path, + try_metadata_value_from_usize, AttributeSliceExt, }, + DeriveOptions, }; -pub fn expand_error( - input: DeriveInput, - // Attributes from #[derive_error_for_udl()], if we are in udl mode - attr_from_udl_mode: Option, - udl_mode: bool, -) -> syn::Result { - let enum_ = match input.data { - Data::Enum(e) => e, - _ => { - return Err(syn::Error::new( - Span::call_site(), - "This derive currently only supports enums", - )); - } - }; - let ident = &input.ident; - let docstring = extract_docstring(&input.attrs)?; - let mut attr: ErrorAttr = input.attrs.parse_uniffi_attr_args()?; - if let Some(attr_from_udl_mode) = attr_from_udl_mode { - attr = attr.merge(attr_from_udl_mode)?; - } - let ffi_converter_impl = error_ffi_converter_impl(ident, &enum_, &attr, udl_mode)?; - let meta_static_var = (!udl_mode).then(|| { - error_meta_static_var(ident, docstring, &enum_, &attr) - .unwrap_or_else(syn::Error::into_compile_error) - }); +pub fn expand_error(input: DeriveInput, options: DeriveOptions) -> syn::Result { + let enum_item = EnumItem::new(input)?; + let ffi_converter_impl = error_ffi_converter_impl(&enum_item, &options)?; + let meta_static_var = options + .generate_metadata + .then(|| error_meta_static_var(&enum_item).unwrap_or_else(syn::Error::into_compile_error)); - let variant_errors: TokenStream = enum_ + let variant_errors: TokenStream = enum_item + .enum_() .variants .iter() .flat_map(|variant| { @@ -64,16 +42,11 @@ pub fn expand_error( }) } -fn error_ffi_converter_impl( - ident: &Ident, - enum_: &DataEnum, - attr: &ErrorAttr, - udl_mode: bool, -) -> syn::Result { - Ok(if attr.flat.is_some() { - flat_error_ffi_converter_impl(ident, enum_, udl_mode, attr) +fn error_ffi_converter_impl(item: &EnumItem, options: &DeriveOptions) -> syn::Result { + Ok(if item.is_flat_error() { + flat_error_ffi_converter_impl(item, options) } else { - rich_error_ffi_converter_impl(ident, enum_, udl_mode, &attr.clone().try_into()?) + rich_error_ffi_converter_impl(item, options) }) } @@ -81,24 +54,21 @@ fn error_ffi_converter_impl( // // These are errors where we only lower the to_string() value, rather than any associated data. // We lower the to_string() value unconditionally, whether the enum has associated data or not. -fn flat_error_ffi_converter_impl( - ident: &Ident, - enum_: &DataEnum, - udl_mode: bool, - attr: &ErrorAttr, -) -> TokenStream { - let name = ident_to_string(ident); - let lower_impl_spec = tagged_impl_header("Lower", ident, udl_mode); - let lift_impl_spec = tagged_impl_header("Lift", ident, udl_mode); - let type_id_impl_spec = tagged_impl_header("TypeId", ident, udl_mode); - let derive_ffi_traits = derive_ffi_traits(ident, udl_mode, &["ConvertError"]); +fn flat_error_ffi_converter_impl(item: &EnumItem, options: &DeriveOptions) -> TokenStream { + let name = item.name(); + let ident = item.ident(); + let lower_impl_spec = options.ffi_impl_header("Lower", ident); + let lift_impl_spec = options.ffi_impl_header("Lift", ident); + let type_id_impl_spec = options.ffi_impl_header("TypeId", ident); + let derive_ffi_traits = options.derive_ffi_traits(ident, &["ConvertError"]); let mod_path = match mod_path() { Ok(p) => p, Err(e) => return e.into_compile_error(), }; let lower_impl = { - let mut match_arms: Vec<_> = enum_ + let mut match_arms: Vec<_> = item + .enum_() .variants .iter() .enumerate() @@ -115,7 +85,7 @@ fn flat_error_ffi_converter_impl( } }) .collect(); - if attr.non_exhaustive.is_some() { + if item.is_non_exhaustive() { match_arms.push(quote! { _ => panic!("Unexpected variant in non-exhaustive enum"), }) @@ -140,8 +110,8 @@ fn flat_error_ffi_converter_impl( } }; - let lift_impl = if attr.with_try_read.is_some() { - let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { + let lift_impl = if item.generate_error_try_read() { + let match_arms = item.enum_().variants.iter().enumerate().map(|(i, v)| { let v_ident = &v.ident; let idx = Index::from(i + 1); @@ -206,16 +176,12 @@ fn flat_error_ffi_converter_impl( } } -pub(crate) fn error_meta_static_var( - ident: &Ident, - docstring: String, - enum_: &DataEnum, - attr: &ErrorAttr, -) -> syn::Result { - let name = ident_to_string(ident); +pub(crate) fn error_meta_static_var(item: &EnumItem) -> syn::Result { + let name = item.name(); let module_path = mod_path()?; - let flat = attr.flat.is_some(); - let non_exhaustive = attr.non_exhaustive.is_some(); + let flat = item.is_flat_error(); + let non_exhaustive = item.is_non_exhaustive(); + let docstring = item.docstring(); let mut metadata_expr = quote! { ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM) .concat_str(#module_path) @@ -224,9 +190,9 @@ pub(crate) fn error_meta_static_var( .concat_bool(false) // discr_type: None }; if flat { - metadata_expr.extend(flat_error_variant_metadata(enum_)?) + metadata_expr.extend(flat_error_variant_metadata(item)?) } else { - metadata_expr.extend(variant_metadata(enum_)?); + metadata_expr.extend(variant_metadata(item)?); } metadata_expr.extend(quote! { .concat_bool(#non_exhaustive) @@ -235,7 +201,8 @@ pub(crate) fn error_meta_static_var( Ok(create_metadata_items("error", &name, metadata_expr, None)) } -pub fn flat_error_variant_metadata(enum_: &DataEnum) -> syn::Result> { +pub fn flat_error_variant_metadata(item: &EnumItem) -> syn::Result> { + let enum_ = item.enum_(); let variants_len = try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?; std::iter::once(Ok(quote! { .concat_value(#variants_len) })) @@ -249,74 +216,3 @@ pub fn flat_error_variant_metadata(enum_: &DataEnum) -> syn::Result, - pub with_try_read: Option, - pub non_exhaustive: Option, -} - -impl UniffiAttributeArgs for ErrorAttr { - fn parse_one(input: ParseStream<'_>) -> syn::Result { - let lookahead = input.lookahead1(); - if lookahead.peek(kw::flat_error) { - Ok(Self { - flat: input.parse()?, - ..Self::default() - }) - } else if lookahead.peek(kw::with_try_read) { - Ok(Self { - with_try_read: input.parse()?, - ..Self::default() - }) - } else if lookahead.peek(kw::non_exhaustive) { - Ok(Self { - non_exhaustive: input.parse()?, - ..Self::default() - }) - } else if lookahead.peek(kw::handle_unknown_callback_error) { - // Not used anymore, but still allowed - Ok(Self::default()) - } else { - Err(lookahead.error()) - } - } - - fn merge(self, other: Self) -> syn::Result { - Ok(Self { - flat: either_attribute_arg(self.flat, other.flat)?, - with_try_read: either_attribute_arg(self.with_try_read, other.with_try_read)?, - non_exhaustive: either_attribute_arg(self.non_exhaustive, other.non_exhaustive)?, - }) - } -} - -// So ErrorAttr can be used with `parse_macro_input!` -impl Parse for ErrorAttr { - fn parse(input: ParseStream<'_>) -> syn::Result { - parse_comma_separated(input) - } -} - -impl TryFrom for EnumAttr { - type Error = syn::Error; - - fn try_from(error_attr: ErrorAttr) -> Result { - if error_attr.flat.is_some() { - Err(syn::Error::new( - Span::call_site(), - "flat attribute not valid for rich enum errors", - )) - } else if error_attr.with_try_read.is_some() { - Err(syn::Error::new( - Span::call_site(), - "with_try_read attribute not valid for rich enum errors", - )) - } else { - Ok(EnumAttr { - non_exhaustive: error_attr.non_exhaustive, - }) - } - } -} diff --git a/uniffi_macros/src/export/trait_interface.rs b/uniffi_macros/src/export/trait_interface.rs index ed709fc212..51eb94d031 100644 --- a/uniffi_macros/src/export/trait_interface.rs +++ b/uniffi_macros/src/export/trait_interface.rs @@ -96,7 +96,7 @@ pub(super) fn gen_trait_scaffolding( } else { ObjectImpl::Trait }; - interface_meta_static_var(&self_ident, imp, mod_path, docstring) + interface_meta_static_var(&self_ident, imp, mod_path, docstring.as_str()) .unwrap_or_else(syn::Error::into_compile_error) }); let ffi_converter_tokens = ffi_converter(mod_path, &self_ident, udl_mode, with_foreign); diff --git a/uniffi_macros/src/lib.rs b/uniffi_macros/src/lib.rs index a30d8cbc27..289740b1c8 100644 --- a/uniffi_macros/src/lib.rs +++ b/uniffi_macros/src/lib.rs @@ -17,6 +17,7 @@ use syn::{ mod custom; mod default; +mod derive; mod enum_; mod error; mod export; @@ -29,8 +30,8 @@ mod test; mod util; use self::{ - enum_::expand_enum, error::expand_error, export::expand_export, object::expand_object, - record::expand_record, + derive::DeriveOptions, enum_::expand_enum, error::expand_error, export::expand_export, + object::expand_object, record::expand_record, }; struct CustomTypeInfo { @@ -107,28 +108,28 @@ fn do_export(attr_args: TokenStream, input: TokenStream, udl_mode: bool) -> Toke #[proc_macro_derive(Record, attributes(uniffi))] pub fn derive_record(input: TokenStream) -> TokenStream { - expand_record(parse_macro_input!(input), false) + expand_record(parse_macro_input!(input), DeriveOptions::default()) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro_derive(Enum)] pub fn derive_enum(input: TokenStream) -> TokenStream { - expand_enum(parse_macro_input!(input), None, false) + expand_enum(parse_macro_input!(input), DeriveOptions::default()) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro_derive(Object)] pub fn derive_object(input: TokenStream) -> TokenStream { - expand_object(parse_macro_input!(input), false) + expand_object(parse_macro_input!(input), DeriveOptions::default()) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro_derive(Error, attributes(uniffi))] pub fn derive_error(input: TokenStream) -> TokenStream { - expand_error(parse_macro_input!(input), None, false) + expand_error(parse_macro_input!(input), DeriveOptions::default()) .unwrap_or_else(syn::Error::into_compile_error) .into() } @@ -154,28 +155,11 @@ pub fn custom_newtype(tokens: TokenStream) -> TokenStream { .into() } -// == derive_for_udl and export_for_udl == +// Derive items for UDL mode // -// The Askama templates generate placeholder items wrapped with these attributes. The goal is to -// have all scaffolding generation go through the same code path. -// -// The one difference is that derive-style attributes are not allowed inside attribute macro -// inputs. Instead, we take the attributes from the macro invocation itself. -// -// Instead of: -// -// ``` -// #[derive(Error) -// #[uniffi(flat_error]) -// enum { .. } -// ``` -// -// We have: -// -// ``` -// #[derive_error_for_udl(flat_error)] -// enum { ... } -// ``` +// The Askama templates generate placeholder items wrapped with the `#[udl_derive()]` +// attribute. The macro code then generates derived items based on the input. This system ensures +// that the same code path is used for UDL-based code and proc-macros. // // # Differences between UDL-mode and normal mode // @@ -199,47 +183,21 @@ pub fn custom_newtype(tokens: TokenStream) -> TokenStream { // // With proc-macros this system isn't so natural. Instead, we create a blanket implementation // for all UT and support for remote types is still TODO. - -#[doc(hidden)] -#[proc_macro_attribute] -pub fn derive_record_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream { - expand_record(syn::parse_macro_input!(input), true) - .unwrap_or_else(syn::Error::into_compile_error) - .into() -} - -#[doc(hidden)] -#[proc_macro_attribute] -pub fn derive_enum_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream { - expand_enum( - syn::parse_macro_input!(input), - Some(syn::parse_macro_input!(attrs)), - true, - ) - .unwrap_or_else(syn::Error::into_compile_error) - .into() -} - #[doc(hidden)] #[proc_macro_attribute] -pub fn derive_error_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream { - expand_error( - syn::parse_macro_input!(input), - Some(syn::parse_macro_input!(attrs)), - true, +pub fn udl_derive(attrs: TokenStream, input: TokenStream) -> TokenStream { + derive::expand_derive( + parse_macro_input!(attrs), + parse_macro_input!(input), + DeriveOptions::udl_derive(), ) .unwrap_or_else(syn::Error::into_compile_error) .into() } -#[doc(hidden)] -#[proc_macro_attribute] -pub fn derive_object_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream { - expand_object(syn::parse_macro_input!(input), true) - .unwrap_or_else(syn::Error::into_compile_error) - .into() -} - +// Generate export items for UDL mode +// +// This works similarly to `udl_derive`, but for #[export]. #[doc(hidden)] #[proc_macro_attribute] pub fn export_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream { diff --git a/uniffi_macros/src/object.rs b/uniffi_macros/src/object.rs index 059def14cd..95476ee1da 100644 --- a/uniffi_macros/src/object.rs +++ b/uniffi_macros/src/object.rs @@ -4,17 +4,43 @@ use syn::DeriveInput; use crate::{ ffiops, - util::{ - create_metadata_items, extract_docstring, ident_to_string, mod_path, tagged_impl_header, - }, + util::{create_metadata_items, extract_docstring, ident_to_string, mod_path}, + DeriveOptions, }; use uniffi_meta::ObjectImpl; -pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result { +/// Stores parsed data from the Derive Input for the struct/enum. +struct ObjectItem { + ident: Ident, + docstring: String, +} + +impl ObjectItem { + fn new(input: DeriveInput) -> syn::Result { + Ok(Self { + ident: input.ident, + docstring: extract_docstring(&input.attrs)?, + }) + } + + fn ident(&self) -> &Ident { + &self.ident + } + + fn name(&self) -> String { + ident_to_string(&self.ident) + } + + fn docstring(&self) -> &str { + self.docstring.as_str() + } +} + +pub fn expand_object(input: DeriveInput, options: DeriveOptions) -> syn::Result { let module_path = mod_path()?; - let ident = &input.ident; - let docstring = extract_docstring(&input.attrs)?; - let name = ident_to_string(ident); + let object = ObjectItem::new(input)?; + let name = object.name(); + let ident = object.ident(); let clone_fn_ident = Ident::new( &uniffi_meta::clone_fn_symbol_name(&module_path, &name), Span::call_site(), @@ -23,11 +49,16 @@ pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result syn::Result TokenStream { - let name = ident_to_string(ident); - let impl_spec = tagged_impl_header("FfiConverterArc", ident, udl_mode); - let lower_return_impl_spec = tagged_impl_header("LowerReturn", ident, udl_mode); - let type_id_impl_spec = tagged_impl_header("TypeId", ident, udl_mode); - let lift_ref_impl_spec = tagged_impl_header("LiftRef", ident, udl_mode); +fn interface_impl(object: &ObjectItem, options: &DeriveOptions) -> TokenStream { + let name = object.name(); + let ident = object.ident(); + let impl_spec = options.ffi_impl_header("FfiConverterArc", ident); + let lower_return_impl_spec = options.ffi_impl_header("LowerReturn", ident); + let type_id_impl_spec = options.ffi_impl_header("TypeId", ident); + let lift_ref_impl_spec = options.ffi_impl_header("LiftRef", ident); let mod_path = match mod_path() { Ok(p) => p, Err(e) => return e.into_compile_error(), @@ -168,7 +200,7 @@ pub(crate) fn interface_meta_static_var( ident: &Ident, imp: ObjectImpl, module_path: &str, - docstring: String, + docstring: &str, ) -> syn::Result { let name = ident_to_string(ident); let code = match imp { diff --git a/uniffi_macros/src/record.rs b/uniffi_macros/src/record.rs index 3a1bdf23af..ec4b46303b 100644 --- a/uniffi_macros/src/record.rs +++ b/uniffi_macros/src/record.rs @@ -6,34 +6,65 @@ use crate::{ default::{default_value_metadata_calls, DefaultValue}, ffiops, util::{ - create_metadata_items, derive_all_ffi_traits, either_attribute_arg, extract_docstring, - ident_to_string, kw, mod_path, tagged_impl_header, try_metadata_value_from_usize, - try_read_field, AttributeSliceExt, UniffiAttributeArgs, + create_metadata_items, either_attribute_arg, extract_docstring, ident_to_string, kw, + mod_path, try_metadata_value_from_usize, try_read_field, AttributeSliceExt, + UniffiAttributeArgs, }, + DeriveOptions, }; -pub fn expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result { +/// Stores parsed data from the Derive Input for the struct. +struct RecordItem { + ident: Ident, + record: DataStruct, + docstring: String, +} + +impl RecordItem { + fn new(input: DeriveInput) -> syn::Result { + let record = match input.data { + Data::Struct(s) => s, + _ => { + return Err(syn::Error::new( + Span::call_site(), + "This derive must only be used on structs", + )); + } + }; + Ok(Self { + ident: input.ident, + record, + docstring: extract_docstring(&input.attrs)?, + }) + } + + fn ident(&self) -> &Ident { + &self.ident + } + + fn name(&self) -> String { + ident_to_string(&self.ident) + } + + fn struct_(&self) -> &DataStruct { + &self.record + } + + fn docstring(&self) -> &str { + self.docstring.as_str() + } +} + +pub fn expand_record(input: DeriveInput, options: DeriveOptions) -> syn::Result { if let Some(e) = input.attrs.uniffi_attr_args_not_allowed_here() { return Err(e); } - let record = match input.data { - Data::Struct(s) => s, - _ => { - return Err(syn::Error::new( - Span::call_site(), - "This derive must only be used on structs", - )); - } - }; - - let ident = &input.ident; - let docstring = extract_docstring(&input.attrs)?; - let ffi_converter = record_ffi_converter_impl(ident, &record, udl_mode) - .unwrap_or_else(syn::Error::into_compile_error); - let meta_static_var = (!udl_mode).then(|| { - record_meta_static_var(ident, docstring, &record) - .unwrap_or_else(syn::Error::into_compile_error) - }); + let record = RecordItem::new(input)?; + let ffi_converter = + record_ffi_converter_impl(&record, &options).unwrap_or_else(syn::Error::into_compile_error); + let meta_static_var = options + .generate_metadata + .then(|| record_meta_static_var(&record).unwrap_or_else(syn::Error::into_compile_error)); Ok(quote! { #ffi_converter @@ -41,17 +72,17 @@ pub fn expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result syn::Result { - let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode); - let derive_ffi_traits = derive_all_ffi_traits(ident, udl_mode); + let ident = record.ident(); + let impl_spec = options.ffi_impl_header("FfiConverter", ident); + let derive_ffi_traits = options.derive_all_ffi_traits(ident); let name = ident_to_string(ident); let mod_path = mod_path()?; - let write_impl: TokenStream = record.fields.iter().map(write_field).collect(); - let try_read_fields: TokenStream = record.fields.iter().map(try_read_field).collect(); + let write_impl: TokenStream = record.struct_().fields.iter().map(write_field).collect(); + let try_read_fields: TokenStream = record.struct_().fields.iter().map(try_read_field).collect(); Ok(quote! { #[automatically_derived] @@ -105,17 +136,17 @@ impl UniffiAttributeArgs for FieldAttributeArguments { } } -pub(crate) fn record_meta_static_var( - ident: &Ident, - docstring: String, - record: &DataStruct, -) -> syn::Result { - let name = ident_to_string(ident); +fn record_meta_static_var(record: &RecordItem) -> syn::Result { + let name = record.name(); + let docstring = record.docstring(); let module_path = mod_path()?; - let fields_len = - try_metadata_value_from_usize(record.fields.len(), "UniFFI limits structs to 256 fields")?; + let fields_len = try_metadata_value_from_usize( + record.struct_().fields.len(), + "UniFFI limits structs to 256 fields", + )?; let concat_fields: TokenStream = record + .struct_() .fields .iter() .map(|f| { diff --git a/uniffi_macros/src/util.rs b/uniffi_macros/src/util.rs index 84f0052351..d4c2c14d2f 100644 --- a/uniffi_macros/src/util.rs +++ b/uniffi_macros/src/util.rs @@ -259,6 +259,10 @@ pub mod kw { syn::custom_keyword!(with_try_read); syn::custom_keyword!(name); syn::custom_keyword!(non_exhaustive); + syn::custom_keyword!(Record); + syn::custom_keyword!(Enum); + syn::custom_keyword!(Error); + syn::custom_keyword!(Object); syn::custom_keyword!(Debug); syn::custom_keyword!(Display); syn::custom_keyword!(Eq);