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 84426ddf52..36475f38ff 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,11 +1,7 @@ -error[E0063]: missing field `numerator` in initializer of `ArithmeticError` - --> $OUT_DIR[uniffi_uitests]/errors.uniffi.rs - | - | 2 => r#ArithmeticError::r#DivisionByZero{ }, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `numerator` - -error[E0063]: missing field `0` in initializer of `ArithmeticError` - --> $OUT_DIR[uniffi_uitests]/errors.uniffi.rs - | - | 3 => r#ArithmeticError::r#UnexpectedError{ }, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `0` +error[E0533]: expected unit struct, unit variant or constant, found struct variant `Self::DivisionByZero` + --> $OUT_DIR[uniffi_uitests]/errors.uniffi.rs + | + | #[::uniffi::ffi_converter_error(crate::UniFfiTag)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `::uniffi::ffi_converter_error` (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 5fe0b769a7..c6a6e96bdf 100644 --- a/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr +++ b/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr @@ -1,8 +1,8 @@ error[E0277]: `Cell` cannot be shared between threads safely --> $OUT_DIR[uniffi_uitests]/counter.uniffi.rs | - | uniffi::deps::static_assertions::assert_impl_all!(r#Counter: Sync, Send); - | ^^^^^^^^^ `Cell` cannot be shared between threads safely + | #[::uniffi::ffi_converter_interface(crate::UniFfiTag)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Cell` cannot be shared between threads safely | = help: within `Counter`, the trait `Sync` is not implemented for `Cell` note: required because it appears within the type `Counter` @@ -10,39 +10,9 @@ note: required because it appears within the type `Counter` | 9 | pub struct Counter { | ^^^^^^^ -note: required by a bound in `assert_impl_all` - --> $OUT_DIR[uniffi_uitests]/counter.uniffi.rs - | - | uniffi::deps::static_assertions::assert_impl_all!(r#Counter: Sync, Send); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl_all` - = note: this error originates in the macro `uniffi::deps::static_assertions::assert_impl_all` (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 - | - | as uniffi::FfiConverter>::lower(_arc) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Cell` cannot be shared between threads safely +note: required by a bound in `Interface` + --> $WORKSPACE/uniffi_core/src/lib.rs | - = help: within `Counter`, the trait `Sync` is not implemented for `Cell` - = help: the trait `FfiConverter` is implemented for `Arc` -note: required because it appears within the type `Counter` - --> tests/ui/interface_not_sync_and_send.rs:9:12 - | -9 | pub struct Counter { - | ^^^^^^^ - = note: required for `Arc` to implement `FfiConverter` - -error[E0277]: `Cell` cannot be shared between threads safely - --> $OUT_DIR[uniffi_uitests]/counter.uniffi.rs - | - | match as uniffi::FfiConverter>::try_lift(r#ptr) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Cell` cannot be shared between threads safely - | - = help: within `Counter`, the trait `Sync` is not implemented for `Cell` - = help: the trait `FfiConverter` is implemented for `Arc` -note: required because it appears within the type `Counter` - --> tests/ui/interface_not_sync_and_send.rs:9:12 - | -9 | pub struct Counter { - | ^^^^^^^ - = note: required for `Arc` to implement `FfiConverter` + | pub trait Interface: Send + Sync + Sized {} + | ^^^^ required by this bound in `Interface` + = note: this error originates in the attribute macro `::uniffi::ffi_converter_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/uniffi/src/lib.rs b/uniffi/src/lib.rs index bb308c55f2..b719339962 100644 --- a/uniffi/src/lib.rs +++ b/uniffi/src/lib.rs @@ -4,7 +4,7 @@ /// Reexport items from other uniffi creates pub use uniffi_core::*; -pub use uniffi_macros::{export, include_scaffolding, Enum, Error, Object, Record}; +pub use uniffi_macros::*; #[cfg(feature = "cli")] mod cli; #[cfg(feature = "bindgen-tests")] diff --git a/uniffi/tests/ui/proc_macro_arc.stderr b/uniffi/tests/ui/proc_macro_arc.stderr index d25c78f9a0..93ab376f79 100644 --- a/uniffi/tests/ui/proc_macro_arc.stderr +++ b/uniffi/tests/ui/proc_macro_arc.stderr @@ -1,31 +1,19 @@ -error[E0271]: type mismatch resolving ` as child::_::_::{closure#0}::TypeEq>::This == Arc` - --> tests/ui/proc_macro_arc.rs:21:22 +error[E0277]: the trait bound `Foo: Interface` is not satisfied + --> tests/ui/proc_macro_arc.rs:10:1 | -21 | fn take_foo(foo: Arc) { - | ^^^^^^^^ type mismatch resolving ` as child::_::_::{closure#0}::TypeEq>::This == Arc` +10 | #[uniffi::export] + | ^^^^^^^^^^^^^^^^^ the trait `Interface` is not implemented for `Foo` | -note: expected this to be `Arc` - --> tests/ui/proc_macro_arc.rs:21:22 + = help: the trait `FfiConverter` is implemented for `Arc` + = note: required for `Arc` to implement `FfiConverter` + = note: this error originates in the attribute macro `uniffi::export` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `child::Foo: Interface` is not satisfied + --> tests/ui/proc_macro_arc.rs:20:5 | -21 | fn take_foo(foo: Arc) { - | ^^^ - = note: enum `child::Foo` and struct `Foo` have similar names, but are actually distinct types -note: enum `child::Foo` is defined in module `crate::child` of the current crate - --> tests/ui/proc_macro_arc.rs:18:5 +20 | #[uniffi::export] + | ^^^^^^^^^^^^^^^^^ the trait `Interface` is not implemented for `child::Foo` | -18 | enum Foo {} - | ^^^^^^^^ -note: struct `Foo` is defined in module `crate` of the current crate - --> tests/ui/proc_macro_arc.rs:8:1 - | -8 | pub struct Foo; - | ^^^^^^^^^^^^^^ -note: required by a bound in `child::_::_::{closure#0}::assert_type_eq_all` - --> tests/ui/proc_macro_arc.rs:21:22 - | -21 | fn take_foo(foo: Arc) { - | ^^^ - | | - | required by a bound in this - | required by this bound in `child::_::_::{closure#0}::assert_type_eq_all` - = note: this error originates in the macro `::uniffi::deps::static_assertions::assert_type_eq_all` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `FfiConverter` is implemented for `Arc` + = note: required for `Arc` to implement `FfiConverter` + = note: this error originates in the attribute macro `uniffi::export` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs index ac9622f37a..4a9252dbea 100644 --- a/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs @@ -7,37 +7,13 @@ // public so other crates can refer to it via an `[External='crate'] typedef` #} - -#[doc(hidden)] -unsafe impl ::uniffi::FfiConverter for r#{{ e.name() }} { - ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(crate::UniFfiTag); - - fn write(obj: r#{{ e.name() }}, buf: &mut std::vec::Vec) { - use uniffi::deps::bytes::BufMut; - match obj { - {%- for variant in e.variants() %} - r#{{ e.name() }}::r#{{ variant.name() }} { {% for field in variant.fields() %}r#{{ field.name() }}, {%- endfor %} } => { - buf.put_i32({{ loop.index }}); - {% for field in variant.fields() -%} - {{ field.type_().borrow()|ffi_converter }}::write(r#{{ field.name() }}, buf); - {%- endfor %} - }, - {%- endfor %} - }; - } - - fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result { - use uniffi::deps::bytes::Buf; - uniffi::check_remaining(buf, 4)?; - Ok(match buf.get_i32() { - {%- for variant in e.variants() %} - {{ loop.index }} => r#{{ e.name() }}::r#{{ variant.name() }}{% if variant.has_fields() %} { - {% for field in variant.fields() %} - r#{{ field.name() }}: {{ field.type_().borrow()|ffi_converter }}::try_read(buf)?, - {%- endfor %} - }{% endif %}, - {%- endfor %} - v => uniffi::deps::anyhow::bail!("Invalid {{ e.name() }} enum value: {}", v), - }) - } +#[::uniffi::ffi_converter_enum(crate::UniFfiTag)] +enum r#{{ e.name() }} { + {%- for variant in e.variants() %} + r#{{ variant.name() }} { + {%- for field in variant.fields() %} + r#{{ field.name() }}: {{ field.type_()|type_rs }}, + {%- endfor %} + }, + {%- endfor %} } diff --git a/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs index 8927bafd17..88b4e40fe2 100644 --- a/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs @@ -7,84 +7,16 @@ // public so other crates can refer to it via an `[External='crate'] typedef` #} -#[doc(hidden)] -unsafe impl ::uniffi::FfiConverter for r#{{ e.name() }} { - uniffi::ffi_converter_rust_buffer_lift_and_lower!(crate::UniFfiTag); - - {% if e.is_flat() %} - - // For "flat" error enums, we stringify the error on the Rust side and surface that - // as the error message in the foreign language. - - - fn write(obj: r#{{ e.name() }}, buf: &mut std::vec::Vec) { - use uniffi::deps::bytes::BufMut; - let msg = obj.to_string(); - match obj { - {%- for variant in e.variants() %} - r#{{ e.name() }}::r#{{ variant.name() }}{..} => { - buf.put_i32({{ loop.index }}); - {{ Type::String.borrow()|ffi_converter }}::write(msg, buf); - }, - {%- endfor %} - }; - } - - {%- if ci.should_generate_error_read(e) %} - fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result { - use uniffi::deps::bytes::Buf; - uniffi::check_remaining(buf, 4)?; - Ok(match buf.get_i32() { - {%- for variant in e.variants() %} - {{ loop.index }} => r#{{ e.name() }}::r#{{ variant.name() }}{ }, - {%- endfor %} - v => uniffi::deps::anyhow::bail!("Invalid {{ e.name() }} enum value: {}", v), - }) - } - {%- else %} - fn try_read(_buf: &mut &[u8]) -> uniffi::deps::anyhow::Result { - panic!("try_read not supported for flat errors"); - } - {%- endif %} - - {% else %} - - // For rich structured enums, we map individual fields on the Rust side over to - // corresponding fields on the foreign-language side. - // - // If a variant doesn't have fields defined in the UDL, it's currently still possible that - // the Rust enum has fields and they're just not listed. In that case we use the `Variant{..}` - // syntax to match the variant while ignoring its fields. - - fn write(obj: r#{{ e.name() }}, buf: &mut std::vec::Vec) { - use uniffi::deps::bytes::BufMut; - match obj { - {%- for variant in e.variants() %} - r#{{ e.name() }}::r#{{ variant.name() }}{% if variant.has_fields() %} { {% for field in variant.fields() %}r#{{ field.name() }}, {%- endfor %} }{% else %}{..}{% endif %} => { - buf.put_i32({{ loop.index }}); - {% for field in variant.fields() -%} - {{ field.type_().borrow()|ffi_converter }}::write(r#{{ field.name() }}, buf); - {%- endfor %} - }, - {%- endfor %} - }; - } - - fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result { - // Note: no need to call should_generate_error_read here, since it is always true for - // non-flat errors - use uniffi::deps::bytes::Buf; - uniffi::check_remaining(buf, 4)?; - Ok(match buf.get_i32() { - {%- for variant in e.variants() %} - {{ loop.index }} => r#{{ e.name() }}::r#{{ variant.name() }}{% if variant.has_fields() %} { - {% for field in variant.fields() %} - r#{{ field.name() }}: {{ field.type_().borrow()|ffi_converter }}::try_read(buf)?, - {%- endfor %} - }{% endif %}, - {%- endfor %} - v => uniffi::deps::anyhow::bail!("Invalid {{ e.name() }} enum value: {}", v), - }) - } - {% endif %} +#[::uniffi::ffi_converter_error(crate::UniFfiTag)] +{%- if e.is_flat() %} +#[uniffi(flat_error{% if ci.should_generate_error_read(e) %},with_try_read{% endif %})] +{%- endif %} +enum r#{{ e.name() }} { + {%- for variant in e.variants() %} + r#{{ variant.name() }} { + {%- for field in variant.fields() %} + r#{{ field.name() }}: {{ field.type_()|type_rs }}, + {%- endfor %} + }, + {%- endfor %} } diff --git a/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs index 3834ad994e..cc9335788c 100644 --- a/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs @@ -24,6 +24,8 @@ fn uniffi_note_threadsafe_deprecation_{{ obj.name() }}() {} {% endif %} +#[::uniffi::ffi_converter_interface(crate::UniFfiTag)] +struct r#{{ obj.name() }} { } // All Object structs must be `Sync + Send`. The generated scaffolding will fail to compile // if they are not, but unfortunately it fails with an unactionably obscure error message. diff --git a/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs index c725002345..28a7151812 100644 --- a/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs @@ -8,23 +8,9 @@ // public so other crates can refer to it via an `[External='crate'] typedef` #} -#[doc(hidden)] -unsafe impl ::uniffi::FfiConverter for r#{{ rec.name() }} { - ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(crate::UniFfiTag); - - fn write(obj: r#{{ rec.name() }}, buf: &mut std::vec::Vec) { - // If the provided struct doesn't match the fields declared in the UDL, then - // the generated code here will fail to compile with somewhat helpful error. - {%- for field in rec.fields() %} - {{ field.type_().borrow()|ffi_converter }}::write(obj.r#{{ field.name() }}, buf); - {%- endfor %} - } - - fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result { - Ok(r#{{ rec.name() }} { - {%- for field in rec.fields() %} - r#{{ field.name() }}: {{ field.type_().borrow()|ffi_converter }}::try_read(buf)?, - {%- endfor %} - }) - } +#[::uniffi::ffi_converter_record(crate::UniFfiTag)] +struct r#{{ rec.name() }} { + {%- for field in rec.fields() %} + r#{{ field.name() }}: {{ field.type_()|type_rs }}, + {%- endfor %} } diff --git a/uniffi_core/src/ffi_converter_impls.rs b/uniffi_core/src/ffi_converter_impls.rs index ee584040ec..8740c120eb 100644 --- a/uniffi_core/src/ffi_converter_impls.rs +++ b/uniffi_core/src/ffi_converter_impls.rs @@ -3,7 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::{ - check_remaining, ffi_converter_rust_buffer_lift_and_lower, FfiConverter, Result, RustBuffer, + check_remaining, ffi_converter_rust_buffer_lift_and_lower, FfiConverter, Interface, Result, + RustBuffer, }; /// This module contains builtin `FFIConverter` implementations. These cover: /// - Simple privitive types: u8, i32, String, Arc, etc @@ -349,7 +350,7 @@ where /// To avoid dealing with complex lifetime semantics over the FFI, any data passed /// by reference must be encapsulated in an `Arc`, and must be safe to share /// across threads. -unsafe impl FfiConverter for std::sync::Arc { +unsafe impl> FfiConverter for std::sync::Arc { // Don't use a pointer to as that requires a `pub ` type FfiType = *const std::os::raw::c_void; diff --git a/uniffi_core/src/lib.rs b/uniffi_core/src/lib.rs index 330f790a4b..d93bbd7ded 100644 --- a/uniffi_core/src/lib.rs +++ b/uniffi_core/src/lib.rs @@ -198,6 +198,12 @@ pub unsafe trait FfiConverter: Sized { fn try_read(buf: &mut &[u8]) -> Result; } +/// Implemented for exported interface types +/// +/// Like, FfiConverter this has a generic parameter, that's filled in with a type local to the +/// UniFFI consumer crate. +pub trait Interface: Send + Sync + Sized {} + /// Struct to use when we want to lift/lower/serialize types inside the `uniffi` crate. struct UniFfiTag; diff --git a/uniffi_macros/src/enum_.rs b/uniffi_macros/src/enum_.rs index 53ea4d2f42..10babe782b 100644 --- a/uniffi_macros/src/enum_.rs +++ b/uniffi_macros/src/enum_.rs @@ -1,31 +1,35 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{punctuated::Punctuated, Data, DeriveInput, Field, Index, Token, Variant}; +use syn::{ + punctuated::Punctuated, AttributeArgs, Data, DataEnum, DeriveInput, Field, Index, Path, Token, + Variant, +}; use uniffi_meta::{EnumMetadata, FieldMetadata, VariantMetadata}; use crate::{ export::metadata::convert::convert_type, - util::{assert_type_eq, create_metadata_static_var, try_read_field}, + util::{assert_type_eq, create_metadata_static_var, try_read_field, FfiConverterTagHandler}, }; pub fn expand_enum(input: DeriveInput, module_path: Vec) -> TokenStream { - let variants = match input.data { - Data::Enum(e) => Some(e.variants), - _ => None, + let enum_ = match input.data { + Data::Enum(e) => e, + _ => { + return syn::Error::new(Span::call_site(), "This derive must only be used on enums") + .into_compile_error() + } }; let ident = &input.ident; - let ffi_converter_impl = enum_ffi_converter_impl(variants.as_ref(), ident); + let ffi_converter_impl = + enum_ffi_converter_impl(ident, &enum_, FfiConverterTagHandler::generic_impl()); - let meta_static_var = if let Some(variants) = variants { - match enum_metadata(ident, variants, module_path) { + let meta_static_var = { + match enum_metadata(ident, enum_.variants, module_path) { Ok(metadata) => create_metadata_static_var(ident, metadata.into()), Err(e) => e.into_compile_error(), } - } else { - syn::Error::new(Span::call_site(), "This derive must only be used on enums") - .into_compile_error() }; let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident }); @@ -37,60 +41,67 @@ pub fn expand_enum(input: DeriveInput, module_path: Vec) -> TokenStream } } +pub fn expand_enum_ffi_converter(attrs: AttributeArgs, input: DeriveInput) -> TokenStream { + let tag_handler = match FfiConverterTagHandler::try_from(attrs) { + Ok(tag_handler) => tag_handler, + Err(e) => return e.into_compile_error(), + }; + match input.data { + Data::Enum(e) => enum_ffi_converter_impl(&input.ident, &e, tag_handler), + _ => syn::Error::new( + proc_macro2::Span::call_site(), + "This attribute must only be used on enums", + ) + .into_compile_error(), + } +} + pub(crate) fn enum_ffi_converter_impl( - variants: Option<&Punctuated>, ident: &Ident, + enum_: &DataEnum, + tag_handler: FfiConverterTagHandler, ) -> TokenStream { - let (write_impl, try_read_impl) = match variants { - Some(variants) => { - let write_match_arms = variants.iter().enumerate().map(|(i, v)| { - let v_ident = &v.ident; - let fields = v.fields.iter().map(|f| &f.ident); - let idx = Index::from(i + 1); - let write_fields = v.fields.iter().map(write_field); - - quote! { - Self::#v_ident { #(#fields),* } => { - ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx); - #(#write_fields)* - } - } - }); - let write_impl = quote! { - match obj { #(#write_match_arms)* } - }; - - let try_read_match_arms = variants.iter().enumerate().map(|(i, v)| { - let idx = Index::from(i + 1); - let v_ident = &v.ident; - let try_read_fields = v.fields.iter().map(try_read_field); - - quote! { - #idx => Self::#v_ident { #(#try_read_fields)* }, - } - }); - let error_format_string = format!("Invalid {ident} enum value: {{}}"); - let try_read_impl = quote! { - ::uniffi::check_remaining(buf, 4)?; - - Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) { - #(#try_read_match_arms)* - v => ::uniffi::deps::anyhow::bail!(#error_format_string, v), - }) - }; - - (write_impl, try_read_impl) + let (impl_spec, tag) = tag_handler.into_impl_and_tag_path("FfiConverter", ident); + let write_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { + let v_ident = &v.ident; + let fields = v.fields.iter().map(|f| &f.ident); + let idx = Index::from(i + 1); + let write_fields = v.fields.iter().map(|f| write_field(f, &tag)); + + quote! { + Self::#v_ident { #(#fields),* } => { + ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx); + #(#write_fields)* + } } - None => { - let unimplemented = quote! { ::std::unimplemented!() }; - (unimplemented.clone(), unimplemented) + }); + let write_impl = quote! { + match obj { #(#write_match_arms)* } + }; + + let try_read_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { + let idx = Index::from(i + 1); + let v_ident = &v.ident; + let try_read_fields = v.fields.iter().map(|f| try_read_field(f, &tag)); + + quote! { + #idx => Self::#v_ident { #(#try_read_fields)* }, } + }); + let error_format_string = format!("Invalid {ident} enum value: {{}}"); + let try_read_impl = quote! { + ::uniffi::check_remaining(buf, 4)?; + + Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) { + #(#try_read_match_arms)* + v => ::uniffi::deps::anyhow::bail!(#error_format_string, v), + }) }; quote! { #[automatically_derived] - unsafe impl ::uniffi::FfiConverter for #ident { - ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(T); + unsafe #impl_spec { + ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(#tag); fn write(obj: Self, buf: &mut ::std::vec::Vec) { #write_impl @@ -150,11 +161,11 @@ fn field_metadata(f: &Field, v: &Variant) -> syn::Result { }) } -fn write_field(f: &Field) -> TokenStream { +fn write_field(f: &Field, uniffi_tag: &Path) -> TokenStream { let ident = &f.ident; let ty = &f.ty; quote! { - >::write(#ident, buf); + <#ty as ::uniffi::FfiConverter<#uniffi_tag>>::write(#ident, buf); } } diff --git a/uniffi_macros/src/error.rs b/uniffi_macros/src/error.rs index 91f5e0c8b5..43e54bd028 100644 --- a/uniffi_macros/src/error.rs +++ b/uniffi_macros/src/error.rs @@ -3,7 +3,7 @@ use quote::quote; use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, - Data, DeriveInput, Index, Token, Variant, + AttributeArgs, Data, DataEnum, DeriveInput, Index, Token, Variant, }; use uniffi_meta::{ErrorMetadata, VariantMetadata}; @@ -11,51 +11,59 @@ use crate::{ enum_::{enum_ffi_converter_impl, variant_metadata}, util::{ assert_type_eq, chain, create_metadata_static_var, either_attribute_arg, AttributeSliceExt, - UniffiAttribute, + FfiConverterTagHandler, UniffiAttribute, }, }; pub fn expand_error(input: DeriveInput, module_path: Vec) -> TokenStream { - let variants = match input.data { - Data::Enum(e) => Ok(e.variants), - _ => Err(syn::Error::new( - Span::call_site(), - "This derive currently only supports enums", - )), + let enum_ = match input.data { + Data::Enum(e) => e, + _ => { + return syn::Error::new( + Span::call_site(), + "This derive currently only supports enums", + ) + .into_compile_error() + } }; let ident = &input.ident; let attr = input.attrs.parse_uniffi_attributes::(); 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), + Ok(a) if a.flat.is_some() => flat_error_ffi_converter_impl( + ident, + &enum_, + FfiConverterTagHandler::generic_impl(), + a.with_try_read.is_some(), + ), + _ => enum_ffi_converter_impl(ident, &enum_, FfiConverterTagHandler::generic_impl()), }; - 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(), - }), + let meta_static_var = match &attr { + Ok(a) => Some( + match error_metadata(ident, &enum_.variants, module_path, a) { + Ok(metadata) => create_metadata_static_var(ident, metadata.into()), + Err(e) => e.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 variant_errors: TokenStream = enum_ + .variants + .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(); let attr_error = attr.err().map(syn::Error::into_compile_error); quote! { @@ -67,44 +75,90 @@ pub fn expand_error(input: DeriveInput, module_path: Vec) -> TokenStream } } +pub fn expand_ffi_converter_error(attrs: AttributeArgs, input: DeriveInput) -> TokenStream { + let tag_handler = match FfiConverterTagHandler::try_from(attrs) { + Ok(tag_handler) => tag_handler, + Err(e) => return e.into_compile_error(), + }; + let enum_ = match input.data { + Data::Enum(e) => e, + _ => { + return syn::Error::new( + proc_macro2::Span::call_site(), + "This attribute must only be used on enums", + ) + .into_compile_error() + } + }; + + match input.attrs.parse_uniffi_attributes::() { + Ok(a) if a.flat.is_some() => flat_error_ffi_converter_impl( + &input.ident, + &enum_, + tag_handler, + a.with_try_read.is_some(), + ), + _ => enum_ffi_converter_impl(&input.ident, &enum_, tag_handler), + } +} + pub(crate) fn flat_error_ffi_converter_impl( - variants: Option<&Punctuated>, ident: &Ident, + enum_: &DataEnum, + tag_handler: FfiConverterTagHandler, + implement_try_read: bool, ) -> 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 (impl_spec, tag) = tag_handler.into_impl_and_tag_path("FfiConverter", ident); + + let write_impl = { + let match_arms = enum_.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<#tag>>::write(error_msg, buf); } - }); - let write_impl = quote! { - let error_msg = ::std::string::ToString::to_string(&obj); - match obj { #(#write_match_arms)* } - }; + } + }); + + quote! { + let error_msg = ::std::string::ToString::to_string(&obj); + match obj { #(#match_arms)* } + } + }; + + let try_read_impl = if implement_try_read { + let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { + let v_ident = &v.ident; + let idx = Index::from(i + 1); - write_impl + quote! { + #idx => Self::#v_ident, + } + }); + quote! { + Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) { + #(#match_arms)* + v => ::uniffi::deps::anyhow::bail!("Invalid #ident enum value: {}", v), + }) } - None => quote! { ::std::unimplemented!() }, + } else { + quote! { ::std::panic!("try_read not supported for flat errors") } }; quote! { #[automatically_derived] - unsafe impl ::uniffi::FfiConverter for #ident { - ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(T); + unsafe #impl_spec { + ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(#tag); fn write(obj: Self, buf: &mut ::std::vec::Vec) { #write_impl } fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result { - ::std::panic!("try_read not supported for flat errors"); + #try_read_impl } } } @@ -143,17 +197,31 @@ fn error_metadata( mod kw { syn::custom_keyword!(flat_error); + syn::custom_keyword!(with_try_read); } #[derive(Default)] struct ErrorAttr { flat: Option, + with_try_read: Option, } impl Parse for ErrorAttr { fn parse(input: ParseStream<'_>) -> syn::Result { - let flat = input.parse()?; - Ok(ErrorAttr { flat }) + 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 { + Err(lookahead.error()) + } } } @@ -161,6 +229,7 @@ impl UniffiAttribute for ErrorAttr { 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)?, }) } } diff --git a/uniffi_macros/src/lib.rs b/uniffi_macros/src/lib.rs index a38705e57d..e226246db9 100644 --- a/uniffi_macros/src/lib.rs +++ b/uniffi_macros/src/lib.rs @@ -11,7 +11,7 @@ use camino::Utf8Path; use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, LitStr}; +use syn::{parse_macro_input, AttributeArgs, DeriveInput, LitStr}; use util::rewrite_self_type; mod enum_; @@ -116,6 +116,46 @@ pub fn derive_error(input: TokenStream) -> TokenStream { expand_error(input, mod_path).into() } +/// Generate the FfiConverter implementation for a Record +#[proc_macro_attribute] +pub fn ffi_converter_record(attrs: TokenStream, input: TokenStream) -> TokenStream { + record::expand_record_ffi_converter( + syn::parse_macro_input!(attrs as AttributeArgs), + syn::parse_macro_input!(input as DeriveInput), + ) + .into() +} + +/// Generate the FfiConverter implementation for an Enum +#[proc_macro_attribute] +pub fn ffi_converter_enum(attrs: TokenStream, input: TokenStream) -> TokenStream { + enum_::expand_enum_ffi_converter( + syn::parse_macro_input!(attrs as AttributeArgs), + syn::parse_macro_input!(input as DeriveInput), + ) + .into() +} + +/// Generate the FfiConverter implementation for an Enum +#[proc_macro_attribute] +pub fn ffi_converter_error(attrs: TokenStream, input: TokenStream) -> TokenStream { + error::expand_ffi_converter_error( + syn::parse_macro_input!(attrs as AttributeArgs), + syn::parse_macro_input!(input as DeriveInput), + ) + .into() +} + +/// Generate the FfiConverter implementation for an Interface +#[proc_macro_attribute] +pub fn ffi_converter_interface(attrs: TokenStream, input: TokenStream) -> TokenStream { + object::expand_ffi_converter_interface( + syn::parse_macro_input!(attrs as AttributeArgs), + syn::parse_macro_input!(input as DeriveInput), + ) + .into() +} + /// A helper macro to include generated component scaffolding. /// /// This is a simple convenience macro to include the UniFFI component diff --git a/uniffi_macros/src/object.rs b/uniffi_macros/src/object.rs index 2f4988530c..0a6177c6d2 100644 --- a/uniffi_macros/src/object.rs +++ b/uniffi_macros/src/object.rs @@ -1,9 +1,9 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::DeriveInput; +use syn::{AttributeArgs, DeriveInput}; use uniffi_meta::ObjectMetadata; -use crate::util::{assert_type_eq, create_metadata_static_var}; +use crate::util::{assert_type_eq, create_metadata_static_var, FfiConverterTagHandler}; pub fn expand_object(input: DeriveInput, module_path: Vec) -> TokenStream { let ident = &input.ident; @@ -12,6 +12,7 @@ pub fn expand_object(input: DeriveInput, module_path: Vec) -> TokenStrea let free_fn_ident = Ident::new(&metadata.free_ffi_symbol_name(), Span::call_site()); let meta_static_var = create_metadata_static_var(ident, metadata.into()); let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident }); + let interface_impl = interface_impl(ident, FfiConverterTagHandler::generic_impl()); quote! { #[doc(hidden)] @@ -29,7 +30,24 @@ pub fn expand_object(input: DeriveInput, module_path: Vec) -> TokenStrea }); } + #interface_impl #meta_static_var #type_assertion } } + +pub fn expand_ffi_converter_interface(attrs: AttributeArgs, input: DeriveInput) -> TokenStream { + let tag_handler = match FfiConverterTagHandler::try_from(attrs) { + Ok(tag_handler) => tag_handler, + Err(e) => return e.into_compile_error(), + }; + interface_impl(&input.ident, tag_handler) +} + +pub(crate) fn interface_impl(ident: &Ident, tag_handler: FfiConverterTagHandler) -> TokenStream { + let (impl_spec, _) = tag_handler.into_impl_and_tag_path("Interface", ident); + quote! { + #[doc(hidden)] + #impl_spec { } + } +} diff --git a/uniffi_macros/src/record.rs b/uniffi_macros/src/record.rs index e0c13a2dc8..3c8b00caee 100644 --- a/uniffi_macros/src/record.rs +++ b/uniffi_macros/src/record.rs @@ -1,51 +1,73 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{Data, DeriveInput, Field, Fields}; +use syn::{AttributeArgs, Data, DataStruct, DeriveInput, Field, Fields, Path}; use uniffi_meta::{FieldMetadata, RecordMetadata}; use crate::{ export::metadata::convert::convert_type, - util::{assert_type_eq, create_metadata_static_var, try_read_field}, + util::{assert_type_eq, create_metadata_static_var, try_read_field, FfiConverterTagHandler}, }; pub fn expand_record(input: DeriveInput, module_path: Vec) -> TokenStream { - let fields = match input.data { - Data::Struct(s) => Some(s.fields), - _ => None, + let record = match input.data { + Data::Struct(s) => s, + _ => { + return syn::Error::new( + Span::call_site(), + "This derive must only be used on structs", + ) + .into_compile_error() + } }; let ident = &input.ident; - - let (write_impl, try_read_fields) = match &fields { - Some(fields) => ( - fields.iter().map(write_field).collect(), - fields.iter().map(try_read_field).collect(), - ), - None => { - let unimplemented = quote! { ::std::unimplemented!() }; - (unimplemented.clone(), unimplemented) - } + let ffi_converter = + record_ffi_converter_impl(ident, &record, FfiConverterTagHandler::generic_impl()); + let meta_static_var = match record_metadata(ident, record.fields, module_path) { + Ok(metadata) => create_metadata_static_var(ident, metadata.into()), + Err(e) => e.into_compile_error(), }; + let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident }); - let meta_static_var = if let Some(fields) = fields { - match record_metadata(ident, fields, module_path) { - Ok(metadata) => create_metadata_static_var(ident, metadata.into()), - Err(e) => e.into_compile_error(), - } - } else { - syn::Error::new( - Span::call_site(), - "This derive must only be used on structs", - ) - .into_compile_error() + quote! { + #ffi_converter + #meta_static_var + #type_assertion + } +} + +pub fn expand_record_ffi_converter(attrs: AttributeArgs, input: DeriveInput) -> TokenStream { + let tag_handler = match FfiConverterTagHandler::try_from(attrs) { + Ok(tag_handler) => tag_handler, + Err(e) => return e.into_compile_error(), }; + match input.data { + Data::Struct(s) => record_ffi_converter_impl(&input.ident, &s, tag_handler), + _ => syn::Error::new( + proc_macro2::Span::call_site(), + "This attribute must only be used on structs", + ) + .into_compile_error(), + } +} - let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident }); +pub(crate) fn record_ffi_converter_impl( + ident: &Ident, + record: &DataStruct, + tag_handler: FfiConverterTagHandler, +) -> TokenStream { + let (impl_spec, tag) = tag_handler.into_impl_and_tag_path("FfiConverter", ident); + let write_impl: TokenStream = record.fields.iter().map(|f| write_field(f, &tag)).collect(); + let try_read_fields: TokenStream = record + .fields + .iter() + .map(|f| try_read_field(f, &tag)) + .collect(); quote! { #[automatically_derived] - unsafe impl ::uniffi::FfiConverter for #ident { - ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(T); + unsafe #impl_spec { + ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(#tag); fn write(obj: Self, buf: &mut ::std::vec::Vec) { #write_impl @@ -55,9 +77,6 @@ pub fn expand_record(input: DeriveInput, module_path: Vec) -> TokenStrea Ok(Self { #try_read_fields }) } } - - #meta_static_var - #type_assertion } } @@ -98,11 +117,11 @@ fn field_metadata(f: &Field) -> syn::Result { }) } -fn write_field(f: &Field) -> TokenStream { +fn write_field(f: &Field, tag: &Path) -> TokenStream { let ident = &f.ident; let ty = &f.ty; quote! { - <#ty as ::uniffi::FfiConverter>::write(obj.#ident, buf); + <#ty as ::uniffi::FfiConverter<#tag>>::write(obj.#ident, buf); } } diff --git a/uniffi_macros/src/util.rs b/uniffi_macros/src/util.rs index 2a28625cfb..9746faf049 100644 --- a/uniffi_macros/src/util.rs +++ b/uniffi_macros/src/util.rs @@ -4,12 +4,13 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; +use std::convert::TryFrom; use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, visit_mut::VisitMut, - Attribute, Item, Token, Type, + Attribute, AttributeArgs, Item, Meta, NestedMeta, Path, Token, Type, }; use uniffi_meta::Metadata; @@ -129,12 +130,12 @@ pub fn rewrite_self_type(item: &mut Item) { } } -pub fn try_read_field(f: &syn::Field) -> TokenStream { +pub fn try_read_field(f: &syn::Field, tag: &Path) -> TokenStream { let ident = &f.ident; let ty = &f.ty; quote! { - #ident: <#ty as ::uniffi::FfiConverter>::try_read(buf)?, + #ident: <#ty as ::uniffi::FfiConverter<#tag>>::try_read(buf)?, } } @@ -221,3 +222,72 @@ pub fn either_attribute_arg(a: Option, b: Option) -> syn::Res } } } + +pub(crate) struct FfiConverterTagHandler { + tag: Option, +} + +impl FfiConverterTagHandler { + pub(crate) fn generic_impl() -> Self { + Self { tag: None } + } + + pub(crate) fn into_impl_and_tag_path( + self, + trait_name: &str, + ident: &Ident, + ) -> (TokenStream, Path) { + let trait_name = Ident::new(trait_name, Span::call_site()); + match self.tag { + Some(tag) => (quote! { impl ::uniffi::#trait_name<#tag> for #ident }, tag), + None => ( + quote! { impl ::uniffi::#trait_name for #ident }, + Ident::new("T", Span::call_site()).into(), + ), + } + } +} + +impl TryFrom for FfiConverterTagHandler { + type Error = syn::Error; + + fn try_from(args: AttributeArgs) -> syn::Result { + let mut result = Self { tag: None }; + for arg in args { + match arg { + NestedMeta::Meta(meta) => match meta { + Meta::Path(path) => match result.tag { + None => { + result.tag = Some(path); + } + Some(_) => { + return Err(syn::Error::new( + Span::call_site(), + "multiple tags specified", + )); + } + }, + Meta::List(_) => { + return Err(syn::Error::new( + Span::call_site(), + "List args not supported", + )); + } + Meta::NameValue(_) => { + return Err(syn::Error::new( + Span::call_site(), + "name/value pairs not supported", + )); + } + }, + NestedMeta::Lit(_) => { + return Err(syn::Error::new( + Span::call_site(), + "Literal args not supported", + )); + } + } + } + Ok(result) + } +}