From 4925ba900d37ac62018c0449ae85b9e92fff982e Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Tue, 31 Jan 2023 10:48:29 -0500 Subject: [PATCH] Make the proc-macro FfiConverter impls canonical * Split out the proc-macro code to generate `FfiConverter` implementations and added standalone macros for that. * Removed the `FfiConverter` implementations from the Askama template code. Instead, the templates now invoke the macros. * Removed the blanket `Arc` impl from `uniffi_core` and made it so we invoke a macro for each interface. The reason for this is: * I want to add some more functionality to `FfiConverter`, but don't want to implement it twice and test it two different ways. * Removing the blanket `Arc` implementation allows us to customize the `FfiConverter` code for each interface. I think this makes #1457 easier. * Everything goes through one code path, which should give us some more confidence as we migrate to proc-macros. This change caused me to fix some bugs and add some features to them. --- ..._used_in_callbacks_cant_have_fields.stderr | 24 ++--- .../ui/interface_not_sync_and_send.stderr | 30 ------ uniffi/src/lib.rs | 2 +- uniffi/tests/ui/proc_macro_arc.stderr | 58 +++++------ .../src/scaffolding/templates/EnumTemplate.rs | 41 ++------ .../scaffolding/templates/ErrorTemplate.rs | 95 ++++--------------- .../scaffolding/templates/ObjectTemplate.rs | 1 + .../scaffolding/templates/RecordTemplate.rs | 21 +--- uniffi_core/src/ffi_converter_impls.rs | 60 ------------ uniffi_macros/src/enum_.rs | 6 +- uniffi_macros/src/error.rs | 41 ++++++-- uniffi_macros/src/lib.rs | 84 ++++++++++++++++ uniffi_macros/src/object.rs | 69 ++++++++++++++ uniffi_macros/src/record.rs | 55 +++++------ 14 files changed, 289 insertions(+), 298 deletions(-) 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..f50ebe81ed 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,13 @@ -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_flat_error_with_read!( + | | r#ArithmeticError + | | { + | | r#IntegerOverflow { +... | + | | } + | | ); + | |_^ + | + = note: this error originates in the macro `::uniffi::ffi_converter_flat_error_with_read` (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..5faa85b374 100644 --- a/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr +++ b/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr @@ -16,33 +16,3 @@ note: required by a bound in `assert_impl_all` | 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 - | - = 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` 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..4046ae40ef 100644 --- a/uniffi/tests/ui/proc_macro_arc.stderr +++ b/uniffi/tests/ui/proc_macro_arc.stderr @@ -1,31 +1,35 @@ -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 `Arc: FfiConverter` 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 `FfiConverter` is not implemented for `Arc` | -note: expected this to be `Arc` - --> tests/ui/proc_macro_arc.rs:21:22 + = help: the following other types implement trait `FfiConverter`: + () + Duration + HashMap + Option + String + SystemTime + Vec + bool + and $N others + = 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 `Arc: FfiConverter` 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 `FfiConverter` is not implemented for `Arc` | -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 following other types implement trait `FfiConverter`: + () + Duration + HashMap + Option + String + SystemTime + Vec + bool + and $N others + = 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..76be99ea41 100644 --- a/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs @@ -7,37 +7,14 @@ // 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 %}, +::uniffi::ffi_converter_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 %} - v => uniffi::deps::anyhow::bail!("Invalid {{ e.name() }} enum value: {}", v), - }) + }, + {%- endfor %} } -} +); diff --git a/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs index 8927bafd17..ec99f26347 100644 --- a/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs @@ -7,84 +7,21 @@ // 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 %}, +{%- if e.is_flat() && ci.should_generate_error_read(e) %} +::uniffi::ffi_converter_flat_error_with_read!( +{%- else if e.is_flat() && !ci.should_generate_error_read(e) %} +::uniffi::ffi_converter_flat_error!( +{%- else %} +::uniffi::ffi_converter_enum!( +{%- endif %} + r#{{ e.name() }} + { + {%- for variant in e.variants() %} + r#{{ variant.name() }} { + {%- for field in variant.fields() %} + r#{{ field.name() }}: {{ field.type_()|type_rs }}, {%- endfor %} - v => uniffi::deps::anyhow::bail!("Invalid {{ e.name() }} enum value: {}", v), - }) + }, + {%- endfor %} } - {% endif %} -} +); diff --git a/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs index 3834ad994e..33579c7cec 100644 --- a/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs @@ -24,6 +24,7 @@ fn uniffi_note_threadsafe_deprecation_{{ obj.name() }}() {} {% endif %} +::uniffi::ffi_converter_interface!(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..3e026d90ce 100644 --- a/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs @@ -8,23 +8,10 @@ // 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. +::uniffi::ffi_converter_record!( + r#{{ rec.name() }} { {%- for field in rec.fields() %} - {{ field.type_().borrow()|ffi_converter }}::write(obj.r#{{ field.name() }}, buf); + r#{{ field.name() }}: {{ field.type_()|type_rs }}, {%- 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 %} - }) - } -} +); diff --git a/uniffi_core/src/ffi_converter_impls.rs b/uniffi_core/src/ffi_converter_impls.rs index ee584040ec..1569da95a3 100644 --- a/uniffi_core/src/ffi_converter_impls.rs +++ b/uniffi_core/src/ffi_converter_impls.rs @@ -343,63 +343,3 @@ where Ok(map) } } - -/// Support for passing reference-counted shared objects via the FFI. -/// -/// 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 { - // Don't use a pointer to as that requires a `pub ` - type FfiType = *const std::os::raw::c_void; - - /// When lowering, we have an owned `Arc` and we transfer that ownership - /// to the foreign-language code, "leaking" it out of Rust's ownership system - /// as a raw pointer. This works safely because we have unique ownership of `self`. - /// The foreign-language code is responsible for freeing this by calling the - /// `ffi_object_free` FFI function provided by the corresponding UniFFI type. - /// - /// Safety: when freeing the resulting pointer, the foreign-language code must - /// call the destructor function specific to the type `T`. Calling the destructor - /// function for other types may lead to undefined behaviour. - fn lower(obj: std::sync::Arc) -> Self::FfiType { - std::sync::Arc::into_raw(obj) as Self::FfiType - } - - /// When lifting, we receive a "borrow" of the `Arc` that is owned by - /// the foreign-language code, and make a clone of it for our own use. - /// - /// Safety: the provided value must be a pointer previously obtained by calling - /// the `lower()` or `write()` method of this impl. - fn try_lift(v: Self::FfiType) -> Result> { - let v = v as *const T; - // We musn't drop the `Arc` that is owned by the foreign-language code. - let foreign_arc = std::mem::ManuallyDrop::new(unsafe { std::sync::Arc::::from_raw(v) }); - // Take a clone for our own use. - Ok(std::sync::Arc::clone(&*foreign_arc)) - } - - /// When writing as a field of a complex structure, make a clone and transfer ownership - /// of it to the foreign-language code by writing its pointer into the buffer. - /// The foreign-language code is responsible for freeing this by calling the - /// `ffi_object_free` FFI function provided by the corresponding UniFFI type. - /// - /// Safety: when freeing the resulting pointer, the foreign-language code must - /// call the destructor function specific to the type `T`. Calling the destructor - /// function for other types may lead to undefined behaviour. - fn write(obj: std::sync::Arc, buf: &mut Vec) { - static_assertions::const_assert!(std::mem::size_of::<*const std::ffi::c_void>() <= 8); - buf.put_u64(>::lower(obj) as u64); - } - - /// When reading as a field of a complex structure, we receive a "borrow" of the `Arc` - /// that is owned by the foreign-language code, and make a clone for our own use. - /// - /// Safety: the buffer must contain a pointer previously obtained by calling - /// the `lower()` or `write()` method of this impl. - fn try_read(buf: &mut &[u8]) -> Result> { - static_assertions::const_assert!(std::mem::size_of::<*const std::ffi::c_void>() <= 8); - check_remaining(buf, 8)?; - >::try_lift(buf.get_u64() as Self::FfiType) - } -} diff --git a/uniffi_macros/src/enum_.rs b/uniffi_macros/src/enum_.rs index 53ea4d2f42..ba9e75578e 100644 --- a/uniffi_macros/src/enum_.rs +++ b/uniffi_macros/src/enum_.rs @@ -89,8 +89,8 @@ pub(crate) fn enum_ffi_converter_impl( quote! { #[automatically_derived] - unsafe impl ::uniffi::FfiConverter for #ident { - ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(T); + unsafe impl ::uniffi::FfiConverter for #ident { + ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(crate::UniFfiTag); fn write(obj: Self, buf: &mut ::std::vec::Vec) { #write_impl @@ -155,6 +155,6 @@ fn write_field(f: &Field) -> TokenStream { let ty = &f.ty; quote! { - >::write(#ident, buf); + <#ty as ::uniffi::FfiConverter>::write(#ident, buf); } } diff --git a/uniffi_macros/src/error.rs b/uniffi_macros/src/error.rs index 91f5e0c8b5..0fde5b6902 100644 --- a/uniffi_macros/src/error.rs +++ b/uniffi_macros/src/error.rs @@ -27,7 +27,9 @@ pub fn expand_error(input: DeriveInput, module_path: Vec) -> TokenStream 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), + Ok(a) if a.flat.is_some() => { + flat_error_ffi_converter_impl(variants.as_ref().ok(), ident, false) + } _ => enum_ffi_converter_impl(variants.as_ref().ok(), ident), }; @@ -70,6 +72,7 @@ pub fn expand_error(input: DeriveInput, module_path: Vec) -> TokenStream pub(crate) fn flat_error_ffi_converter_impl( variants: Option<&Punctuated>, ident: &Ident, + implement_try_read: bool, ) -> TokenStream { let write_impl = match variants { Some(variants) => { @@ -84,27 +87,49 @@ pub(crate) fn flat_error_ffi_converter_impl( } } }); - let write_impl = quote! { + quote! { let error_msg = ::std::string::ToString::to_string(&obj); match obj { #(#write_match_arms)* } - }; - - write_impl + } } None => quote! { ::std::unimplemented!() }, }; + let try_read_impl = if implement_try_read { + match variants { + Some(variants) => { + let match_arms = variants.iter().enumerate().map(|(i, v)| { + let v_ident = &v.ident; + let idx = Index::from(i + 1); + + 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 ::uniffi::FfiConverter for #ident { + ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(crate::UniFfiTag); 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 } } } diff --git a/uniffi_macros/src/lib.rs b/uniffi_macros/src/lib.rs index a38705e57d..e6cf9cfa74 100644 --- a/uniffi_macros/src/lib.rs +++ b/uniffi_macros/src/lib.rs @@ -144,6 +144,90 @@ pub fn include_scaffolding(component_name: TokenStream) -> TokenStream { }.into() } +struct FfiConverterRecordInput { + ident: syn::Ident, + fields: syn::Fields, +} + +impl syn::parse::Parse for FfiConverterRecordInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ident = input.parse()?; + let fields: syn::FieldsNamed = input.parse()?; + Ok(FfiConverterRecordInput { + ident, + fields: fields.into(), + }) + } +} + +/// Generate the FfiConverter implementation for a Record +#[proc_macro] +pub fn ffi_converter_record(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as FfiConverterRecordInput); + record::record_ffi_converter_impl(&input.fields, &input.ident).into() +} + +struct FfiConverterEnumInput { + ident: syn::Ident, + variants: syn::punctuated::Punctuated, +} + +impl syn::parse::Parse for FfiConverterEnumInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ident = input.parse()?; + let content; + syn::braced!(content in input); + let variants = + >::parse_terminated( + &content, + )?; + Ok(FfiConverterEnumInput { ident, variants }) + } +} + +/// Generate the FfiConverter implementation for an Enum +#[proc_macro] +pub fn ffi_converter_enum(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as FfiConverterEnumInput); + enum_::enum_ffi_converter_impl(Some(&input.variants), &input.ident).into() +} + +/// Generate the FfiConverter implementation for an Enum +#[proc_macro] +pub fn ffi_converter_flat_error(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as FfiConverterEnumInput); + error::flat_error_ffi_converter_impl(Some(&input.variants), &input.ident, false).into() +} + +/// Generate the FfiConverter implementation for an Enum with a `try_read` function. +/// +/// For compatability reasons, we currently only implement `try_read()` for callback interfaces +/// errors. +#[proc_macro] +pub fn ffi_converter_flat_error_with_read(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as FfiConverterEnumInput); + error::flat_error_ffi_converter_impl(Some(&input.variants), &input.ident, true).into() +} + +struct FfiConverterInterfaceInput { + ident: syn::Ident, +} + +impl syn::parse::Parse for FfiConverterInterfaceInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + Ok(FfiConverterInterfaceInput { + ident: input.parse()?, + }) + } +} + +/// Generate the FfiConverter implementation for an Interface +#[proc_macro] +pub fn ffi_converter_interface(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as FfiConverterInterfaceInput); + object::object_ffi_converter_impl(&input.ident).into() +} + /// A helper macro to generate and include component scaffolding. /// /// This is a convenience macro designed for writing `trybuild`-style tests and diff --git a/uniffi_macros/src/object.rs b/uniffi_macros/src/object.rs index 2f4988530c..136e9b48df 100644 --- a/uniffi_macros/src/object.rs +++ b/uniffi_macros/src/object.rs @@ -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 ffi_converter_impl = object_ffi_converter_impl(ident); quote! { #[doc(hidden)] @@ -29,7 +30,75 @@ pub fn expand_object(input: DeriveInput, module_path: Vec) -> TokenStrea }); } + #ffi_converter_impl #meta_static_var #type_assertion } } + +pub(crate) fn object_ffi_converter_impl(ident: &Ident) -> TokenStream { + quote! { + // Support for passing reference-counted shared objects via the FFI. + // + // 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 ::uniffi::FfiConverter for ::std::sync::Arc<#ident> { + // Don't use a pointer to #ident as that requires a `pub <#ident>` + type FfiType = *const ::std::os::raw::c_void; + + // When lowering, we have an owned `Arc<#ident>` and we transfer that ownership + // to the foreign-language code, "leaking" it out of Rust's ownership system + // as a raw pointer. This works safely because we have unique ownership of `self`. + // The foreign-language code is responsible for freeing this by calling the + // `ffi_object_free` FFI function provided by the corresponding UniFFI type. + // + // Safety: when freeing the resulting pointer, the foreign-language code must + // call the destructor function specific to the type `#ident`. Calling the destructor + // function for other types may lead to undefined behaviour. + fn lower(obj: Self) -> Self::FfiType { + ::std::sync::Arc::into_raw(obj) as Self::FfiType + } + + // When lifting, we receive a "borrow" of the `Arc<#ident>` that is owned by + // the foreign-language code, and make a clone of it for our own use. + // + // Safety: the provided value must be a pointer previously obtained by calling + // the `lower()` or `write()` method of this impl. + fn try_lift(v: Self::FfiType) -> ::uniffi::Result { + let v = v as *const #ident; + // We musn't drop the `Arc<#ident>` that is owned by the foreign-language code. + let foreign_arc = ::std::mem::ManuallyDrop::new(unsafe { Self::from_raw(v) }); + // Take a clone for our own use. + Ok(::std::sync::Arc::clone(&*foreign_arc)) + } + + // When writing as a field of a complex structure, make a clone and transfer ownership + // of it to the foreign-language code by writing its pointer into the buffer. + // The foreign-language code is responsible for freeing this by calling the + // `ffi_object_free` FFI function provided by the corresponding UniFFI type. + // + // Safety: when freeing the resulting pointer, the foreign-language code must + // call the destructor function specific to the type `#ident`. Calling the destructor + // function for other types may lead to undefined behaviour. + fn write(obj: Self, buf: &mut Vec) { + ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); + ::uniffi::deps::bytes::BufMut::put_u64(buf, >::lower(obj) as u64); + } + + // When reading as a field of a complex structure, we receive a "borrow" of the + // `Arc<#ident>` that is owned by the foreign-language code, and make a clone for our + // own use. + // + // Safety: the buffer must contain a pointer previously obtained by calling + // the `lower()` or `write()` method of this impl. + fn try_read(buf: &mut &[u8]) -> ::uniffi::Result { + ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); + ::uniffi::check_remaining(buf, 8)?; + >::try_lift( + ::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType + ) + } + } + } +} diff --git a/uniffi_macros/src/record.rs b/uniffi_macros/src/record.rs index e0c13a2dc8..7594e1adb2 100644 --- a/uniffi_macros/src/record.rs +++ b/uniffi_macros/src/record.rs @@ -10,42 +10,40 @@ use crate::{ pub fn expand_record(input: DeriveInput, module_path: Vec) -> TokenStream { let fields = match input.data { - Data::Struct(s) => Some(s.fields), - _ => None, + Data::Struct(s) => s.fields, + _ => { + 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_impl = record_ffi_converter_impl(&fields, ident); + let meta_static_var = match record_metadata(ident, 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_impl - let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident }); + #meta_static_var + #type_assertion + } +} + +pub(crate) fn record_ffi_converter_impl(fields: &Fields, ident: &Ident) -> TokenStream { + let write_impl: TokenStream = fields.iter().map(write_field).collect(); + let try_read_fields: TokenStream = fields.iter().map(try_read_field).collect(); quote! { #[automatically_derived] - unsafe impl ::uniffi::FfiConverter for #ident { - ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(T); + unsafe impl ::uniffi::FfiConverter for #ident { + ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(crate::UniFfiTag); fn write(obj: Self, buf: &mut ::std::vec::Vec) { #write_impl @@ -55,9 +53,6 @@ pub fn expand_record(input: DeriveInput, module_path: Vec) -> TokenStrea Ok(Self { #try_read_fields }) } } - - #meta_static_var - #type_assertion } }