From c4df99e205bb0139490e6440b18548a4175b2279 Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Thu, 2 May 2024 11:57:38 -0400 Subject: [PATCH] Refactor the derive macro code This is prep work for implementing the system for remote / custom / external types as described in #2087. The generated code should stay exactly the same, this just refactors how it's generated. Consolidated the `derive_*_for_udl` macros into a single `udl_derive` macro and added the `DeriveOptions` struct. I think that we can update UDL-generation to create blanket FFI trait impls by just tweaking which flags we set. We can also re-use the same pattern to implement remote types (which will have `generate_metadata: true, local_tag: true`). Changed the specific derive code to use `DeriveOptions` plus an item struct. For example RecordItem, which represents the parsed DeriveInput for the record. This will make future changes easier since we only need to modify one of these structs, not update all the function signatures. Changed the UDL derives to parse attributes exactly like regular derives. For example, we now wrap enums with `#[uniffi(flat_error)]` rather than putting `flat_error` in the `derive_error_for_udl` attribute macro. This is prep work for remote types -- in that case I think we should tell the user to define the item exactly like it was a regular derive and not special case the attributes. Removed the `#[uniffi(non_exhaustive)]` attribute and parse the real `#[non_exhaustive]` attribute instead. This is easy to do now with the new system and also will be good for remote types. Made EnumItem also parse error attributes. I think this is simpler than creating a separate EnumItem vs ErrorItem, at least for now. --- ..._used_in_callbacks_cant_have_fields.stderr | 9 +- .../ui/interface_not_sync_and_send.stderr | 6 +- .../src/scaffolding/templates/EnumTemplate.rs | 9 +- .../scaffolding/templates/ErrorTemplate.rs | 21 +- .../scaffolding/templates/ObjectTemplate.rs | 2 +- .../scaffolding/templates/RecordTemplate.rs | 2 +- uniffi_macros/src/derive.rs | 119 ++++++++ uniffi_macros/src/enum_.rs | 262 +++++++++++------- uniffi_macros/src/error.rs | 182 +++--------- uniffi_macros/src/export/trait_interface.rs | 2 +- uniffi_macros/src/lib.rs | 80 ++---- uniffi_macros/src/object.rs | 68 +++-- uniffi_macros/src/record.rs | 107 ++++--- uniffi_macros/src/util.rs | 4 + 14 files changed, 482 insertions(+), 391 deletions(-) create mode 100644 uniffi_macros/src/derive.rs 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);