From e10a7f95d6b5bd8b4788d8f0eb77aea20dfb655b Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Mon, 11 Sep 2023 13:59:30 -0400 Subject: [PATCH] Use proc-macros for all exports Reworked UDL-based scaffolding generation so that it forwards the work to the proc-macro code. This means that almost all scaffolding generation now flows through the proc-macros and we no longer need to keep 2 implementations of it. The only major thing left is built-in traits. The scaffolding templates now generate stub interfaces and wrap them with a `[uniffi::export_for_udl]` macro. - Added proc-macro support for several features: - [ByArc] in callback interfaces - [ByRef] arguments in any position and for any type. - Handling custom type conversion errors like we do with UDL. If the custom type error is the same as the Err side of the result, we should throw in the foreign code rather than something like UnexpectedPanic. - Moved all keywords into a single module. - Renamed the existing macros that did similar work to `derive_[thing]_for_udl`. - Added the `LiftRef` trait. I'm hoping that this can be the start of defining smaller traits for particular purposes rather than using `FfiConverter` for everything. - Require that proc-macro error types implement Error + Send + Sync + 'static like the UDL ones do. Fixed a bunch of unit tests where this wasn't true. --- CHANGELOG.md | 3 +- Cargo.lock | 3 + docs/manual/src/proc_macro/index.md | 57 ++++-- fixtures/coverall/src/lib.rs | 2 + fixtures/futures/Cargo.toml | 1 + fixtures/futures/src/lib.rs | 6 +- fixtures/metadata/Cargo.toml | 1 + fixtures/metadata/src/tests.rs | 33 +--- fixtures/proc-macro/src/lib.rs | 10 +- .../tests/bindings/test_proc_macro.kts | 1 + .../tests/bindings/test_proc_macro.py | 1 + .../tests/bindings/test_proc_macro.swift | 1 + fixtures/uitests/Cargo.toml | 1 + ...rors_used_in_callbacks_cant_have_fields.rs | 23 +-- ..._used_in_callbacks_cant_have_fields.stderr | 5 +- .../ui/interface_cannot_use_mut_self.stderr | 10 +- .../interface_trait_not_sync_and_send.stderr | 12 +- .../tests/ui/non_hashable_record_key.stderr | 14 +- .../src/scaffolding/templates/EnumTemplate.rs | 2 +- .../scaffolding/templates/ErrorTemplate.rs | 3 +- .../templates/ExternalTypesTemplate.rs | 2 +- .../scaffolding/templates/ObjectTemplate.rs | 98 +++++----- .../scaffolding/templates/RecordTemplate.rs | 2 +- .../templates/TopLevelFunctionTemplate.rs | 32 ++-- uniffi_core/src/ffi/rustcalls.rs | 25 +-- uniffi_core/src/ffi/rustfuture.rs | 10 +- uniffi_core/src/ffi_converter_impls.rs | 47 ++++- uniffi_core/src/ffi_converter_traits.rs | 23 ++- uniffi_core/src/lib.rs | 47 ++++- uniffi_macros/src/custom.rs | 13 +- uniffi_macros/src/enum_.rs | 59 +++--- uniffi_macros/src/error.rs | 76 ++++---- uniffi_macros/src/export.rs | 34 ++-- uniffi_macros/src/export/attributes.rs | 14 +- .../src/export/callback_interface.rs | 44 +++-- uniffi_macros/src/export/item.rs | 6 +- uniffi_macros/src/export/scaffolding.rs | 97 +++++++--- uniffi_macros/src/fnsig.rs | 114 ++++++++---- uniffi_macros/src/lib.rs | 175 ++++++++++-------- uniffi_macros/src/object.rs | 41 ++-- uniffi_macros/src/record.rs | 57 ++---- uniffi_macros/src/util.rs | 82 ++------ 42 files changed, 726 insertions(+), 561 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 398844871f..713e2d4f54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,10 +28,11 @@ be used to avoid this and use the specified value. - Crates can now use proc-macros without UDL files to export their interface. See the "Procedural Macros: Attributes and Derives" manual section for details. - - [Custom Types](https://mozilla.github.io/uniffi-rs/proc_macro/index.html#the-unifficustomtype-derive) are now supported for proc-macros, including a very low-friction way of exposing types implementing the new-type idiom. - Proc-macros: Added support for ByRef arguments +- Proc-macros: Implemented custom type conversion error handling (https://mozilla.github.io/uniffi-rs/udl/custom_types.html#error-handling-during-conversion) +- Error types must now implement `Error + Send + Sync + 'static`. ### What's Fixed diff --git a/Cargo.lock b/Cargo.lock index fe54b8b1eb..f34a129aa8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1749,6 +1749,7 @@ name = "uniffi-fixture-futures" version = "0.21.0" dependencies = [ "once_cell", + "thiserror", "tokio", "uniffi", ] @@ -1979,6 +1980,7 @@ dependencies = [ name = "uniffi_fixture_metadata" version = "0.1.0" dependencies = [ + "thiserror", "uniffi", "uniffi_core", "uniffi_meta", @@ -2036,6 +2038,7 @@ dependencies = [ name = "uniffi_uitests" version = "0.22.0" dependencies = [ + "thiserror", "trybuild", "uniffi", "uniffi_macros", diff --git a/docs/manual/src/proc_macro/index.md b/docs/manual/src/proc_macro/index.md index 4f68154786..cafc1b4c1e 100644 --- a/docs/manual/src/proc_macro/index.md +++ b/docs/manual/src/proc_macro/index.md @@ -91,13 +91,45 @@ trait MyTrait { ``` -Most UniFFI [builtin types](../udl/builtin_types.md) can be used as parameter and return types. -When a type is not supported, you will get a clear compiler error about it. -User-defined types are also supported in a limited manner: records (structs with named fields, -`dictionary` in UDL) and enums can be used when the corresponding derive macro is used at -their definition. Opaque objects (`interface` in UDL) can always be used regardless of whether they -are defined in UDL and / or via derive macro; they just need to be put inside an `Arc` as always. +All owned [builtin types](../udl/builtin_types.md) and user-defined types can be used as arguments +and return types. + +Arguments and receivers can also be references to these types, for example: + +```rust +// Input data types as references +#[uniffi::export] +fn process_data(a: &MyRecord, b: &MyEnum, c: Option<&MyRecord>) { + ... +} + +#[uniffi::export] +impl Foo { + // Methods can take a `&self`, which will be borrowed from `Arc` + fn some_method(&self) { + ... + } +} + +// Input foo as an Arc and bar as a reference +fn call_both(foo: Arc, bar: &Foo) { + foo.some_method(); + bar.some_method(); +} +``` + +The one restriction is that the reference must be visible in the function signature. This wouldn't +work: + +```rust +type MyFooRef = &'static Foo; + +// ERROR: UniFFI won't recognize that the `foo` argument is a reference. +#[uniffi::export] +fn do_something(foo: MyFooRef) { +} +``` ## The `uniffi::Record` derive @@ -186,19 +218,6 @@ impl Foo { } ``` -Exported functions can input object arguments as either an `Arc<>` or reference. -```rust -// Input foo as an Arc and bar as a reference -fn call_both(foo: Arc, bar: &Foo) { - foo.method_a(); - bar.method_rba(); -``` - -There are a couple limitations when using references for arguments: - - They can only be used with objects and trait interfaces - - The reference must be visible in the function signature. - If you have a type alias `type MyFooRef<'a> = &'a Foo`, then `fn do_something(foo: MyFooRef<'_>)` would not work. - ## The `uniffi::custom_type` and `uniffi::custom_newtype` macros There are 2 macros available which allow procmacros to support "custom types" as described in the diff --git a/fixtures/coverall/src/lib.rs b/fixtures/coverall/src/lib.rs index ea56c279e0..e591c0db79 100644 --- a/fixtures/coverall/src/lib.rs +++ b/fixtures/coverall/src/lib.rs @@ -54,7 +54,9 @@ fn throw_flat_macro_error() -> Result<(), CoverallFlatMacroError> { Err(CoverallFlatMacroError::TooManyVariants { num: 88 }) } +#[derive(Debug, thiserror::Error)] pub enum CoverallRichErrorNoVariantData { + #[error("TooManyPlainVariants")] TooManyPlainVariants, } diff --git a/fixtures/futures/Cargo.toml b/fixtures/futures/Cargo.toml index be298afeb8..9b566db5f4 100644 --- a/fixtures/futures/Cargo.toml +++ b/fixtures/futures/Cargo.toml @@ -16,6 +16,7 @@ path = "src/bin.rs" [dependencies] uniffi = { path = "../../uniffi", version = "0.24", features = ["tokio", "cli"] } +thiserror = "1.0" tokio = { version = "1.24.1", features = ["time", "sync"] } once_cell = "1.18.0" diff --git a/fixtures/futures/src/lib.rs b/fixtures/futures/src/lib.rs index cd0ae2780a..f077ed4299 100644 --- a/fixtures/futures/src/lib.rs +++ b/fixtures/futures/src/lib.rs @@ -101,8 +101,9 @@ pub async fn sleep(ms: u16) -> bool { } // Our error. -#[derive(uniffi::Error, Debug)] +#[derive(thiserror::Error, uniffi::Error, Debug)] pub enum MyError { + #[error("Foo")] Foo, } @@ -283,8 +284,9 @@ pub struct SharedResourceOptions { } // Our error. -#[derive(uniffi::Error, Debug)] +#[derive(thiserror::Error, uniffi::Error, Debug)] pub enum AsyncError { + #[error("Timeout")] Timeout, } diff --git a/fixtures/metadata/Cargo.toml b/fixtures/metadata/Cargo.toml index 8807874989..b4980799df 100644 --- a/fixtures/metadata/Cargo.toml +++ b/fixtures/metadata/Cargo.toml @@ -9,6 +9,7 @@ publish = false name = "uniffi_fixture_metadata" [dependencies] +thiserror = "1.0" uniffi = { path = "../../uniffi", version = "0.24" } uniffi_meta = { path = "../../uniffi_meta" } uniffi_core = { path = "../../uniffi_core" } diff --git a/fixtures/metadata/src/tests.rs b/fixtures/metadata/src/tests.rs index d83ec2f156..8833aa5360 100644 --- a/fixtures/metadata/src/tests.rs +++ b/fixtures/metadata/src/tests.rs @@ -39,45 +39,26 @@ mod state { mod error { use super::Weapon; - use std::fmt; - #[derive(uniffi::Error)] + #[derive(Debug, thiserror::Error, uniffi::Error)] #[uniffi(flat_error)] #[allow(dead_code)] pub enum FlatError { + #[error("Overflow")] Overflow(String), // UniFFI should ignore this field, since `flat_error` was specified + #[error("DivideByZero")] DivideByZero, } - #[derive(uniffi::Error)] + #[derive(Debug, thiserror::Error, uniffi::Error)] pub enum ComplexError { + #[error("NotFound")] NotFound, + #[error("PermissionDenied")] PermissionDenied { reason: String }, + #[error("InvalidWeapon")] InvalidWeapon { weapon: Weapon }, } - - impl fmt::Display for FlatError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Overflow(s) => write!(f, "FlatError::Overflow({s})"), - Self::DivideByZero => write!(f, "FlatError::DivideByZero"), - } - } - } - - impl fmt::Display for ComplexError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::NotFound => write!(f, "ComplexError::NotFound()"), - Self::PermissionDenied { reason } => { - write!(f, "ComplexError::PermissionDenied({reason})") - } - Self::InvalidWeapon { weapon } => { - write!(f, "ComplexError::InvalidWeapon({weapon:?})") - } - } - } - } } mod calc { diff --git a/fixtures/proc-macro/src/lib.rs b/fixtures/proc-macro/src/lib.rs index a20decd37e..56666d32bc 100644 --- a/fixtures/proc-macro/src/lib.rs +++ b/fixtures/proc-macro/src/lib.rs @@ -13,6 +13,11 @@ pub struct One { inner: i32, } +#[uniffi::export] +pub fn one_inner_by_ref(one: &One) -> i32 { + one.inner +} + #[derive(uniffi::Record)] pub struct Two { a: String, @@ -171,11 +176,14 @@ fn enum_identity(value: MaybeBool) -> MaybeBool { value } -#[derive(uniffi::Error, Debug, PartialEq, Eq)] +#[derive(thiserror::Error, uniffi::Error, Debug, PartialEq, Eq)] #[uniffi(handle_unknown_callback_error)] pub enum BasicError { + #[error("InvalidInput")] InvalidInput, + #[error("OsError")] OsError, + #[error("UnexpectedError")] UnexpectedError { reason: String }, } diff --git a/fixtures/proc-macro/tests/bindings/test_proc_macro.kts b/fixtures/proc-macro/tests/bindings/test_proc_macro.kts index 5a414b94ce..1a5493731a 100644 --- a/fixtures/proc-macro/tests/bindings/test_proc_macro.kts +++ b/fixtures/proc-macro/tests/bindings/test_proc_macro.kts @@ -6,6 +6,7 @@ import uniffi.fixture.proc_macro.*; val one = makeOne(123) assert(one.inner == 123) +assert(oneInnerByRef(one) == 123) val two = Two("a") assert(takeTwo(two) == "a") diff --git a/fixtures/proc-macro/tests/bindings/test_proc_macro.py b/fixtures/proc-macro/tests/bindings/test_proc_macro.py index 9bf5bb5a05..087c1caa93 100644 --- a/fixtures/proc-macro/tests/bindings/test_proc_macro.py +++ b/fixtures/proc-macro/tests/bindings/test_proc_macro.py @@ -6,6 +6,7 @@ one = make_one(123) assert one.inner == 123 +assert one_inner_by_ref(one) == 123 two = Two("a") assert take_two(two) == "a" diff --git a/fixtures/proc-macro/tests/bindings/test_proc_macro.swift b/fixtures/proc-macro/tests/bindings/test_proc_macro.swift index 4e671cc5d5..388324cde3 100644 --- a/fixtures/proc-macro/tests/bindings/test_proc_macro.swift +++ b/fixtures/proc-macro/tests/bindings/test_proc_macro.swift @@ -7,6 +7,7 @@ import proc_macro let one = makeOne(inner: 123) assert(one.inner == 123) +assert(oneInnerByRef(one: one) == 123) let two = Two(a: "a") assert(takeTwo(two: two) == "a") diff --git a/fixtures/uitests/Cargo.toml b/fixtures/uitests/Cargo.toml index 3f536ce5ca..d00ff46095 100644 --- a/fixtures/uitests/Cargo.toml +++ b/fixtures/uitests/Cargo.toml @@ -12,6 +12,7 @@ name = "uniffi_uitests" [dependencies] uniffi = {path = "../../uniffi", version = "0.24" } uniffi_macros = {path = "../../uniffi_macros"} +thiserror = "1.0" [dev-dependencies] trybuild = "1.0.76" diff --git a/fixtures/uitests/tests/ui/fieldless_errors_used_in_callbacks_cant_have_fields.rs b/fixtures/uitests/tests/ui/fieldless_errors_used_in_callbacks_cant_have_fields.rs index 958136687d..6107d50325 100644 --- a/fixtures/uitests/tests/ui/fieldless_errors_used_in_callbacks_cant_have_fields.rs +++ b/fixtures/uitests/tests/ui/fieldless_errors_used_in_callbacks_cant_have_fields.rs @@ -3,20 +3,17 @@ uniffi_macros::generate_and_include_scaffolding!("../../../../fixtures/uitests/s fn main() { /* empty main required by `trybuild` */} -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] enum ArithmeticError { - IntegerOverflow, - // Since this is listed in the UDL as not having fields and is used in a callback interface, it - // really needs to have no fields. - DivisionByZero { numerator: u64 }, - // Tuple-style fields are also invalid - UnexpectedError(String), -} - -impl std::fmt::Display for ArithmeticError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - ::fmt(self, f) - } + #[error("IntegerOverflow")] + IntegerOverflow, + // Since this is listed in the UDL as not having fields and is used in a callback interface, it + // really needs to have no fields. + #[error("DivisionByZero")] + DivisionByZero { numerator: u64 }, + // Tuple-style fields are also invalid + #[error("UnexpectedError: {0}")] + UnexpectedError(String), } impl From for ArithmeticError { 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 606409f293..6b8f8c4271 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,12 +1,11 @@ error[E0533]: expected value, found struct variant `Self::DivisionByZero` --> $OUT_DIR[uniffi_uitests]/errors.uniffi.rs | - | / #[::uniffi::ffi_converter_error( - | | tag = crate::UniFfiTag, + | / #[::uniffi::derive_error_for_udl( | | flat_error, | | with_try_read, | | handle_unknown_callback_error, | | )] | |__^ not a value | - = note: this error originates in the attribute macro `::uniffi::ffi_converter_error` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `::uniffi::derive_error_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/fixtures/uitests/tests/ui/interface_cannot_use_mut_self.stderr b/fixtures/uitests/tests/ui/interface_cannot_use_mut_self.stderr index e6e52d739f..8df10b0ceb 100644 --- a/fixtures/uitests/tests/ui/interface_cannot_use_mut_self.stderr +++ b/fixtures/uitests/tests/ui/interface_cannot_use_mut_self.stderr @@ -1,8 +1,8 @@ -error[E0308]: mismatched types +error[E0596]: cannot borrow data in an `Arc` as mutable --> $OUT_DIR[uniffi_uitests]/counter.uniffi.rs | - | Ok(ref val) => val, - | ^^^ types differ in mutability + | #[::uniffi::export_for_udl] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable | - = note: expected mutable reference `&mut Counter` - found reference `&Arc` + = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc` + = note: this error originates in the attribute macro `::uniffi::export_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.stderr b/fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.stderr index 80aa7b13d0..a8e7dc90a1 100644 --- a/fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.stderr +++ b/fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.stderr @@ -1,8 +1,8 @@ error[E0277]: `(dyn Trait + 'static)` cannot be shared between threads safely --> $OUT_DIR[uniffi_uitests]/trait.uniffi.rs | - | ::uniffi::expand_trait_interface_support!(r#Trait); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `(dyn Trait + 'static)` cannot be shared between threads safely + | #[::uniffi::export_for_udl] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `(dyn Trait + 'static)` cannot be shared between threads safely | = help: the trait `Sync` is not implemented for `(dyn Trait + 'static)` note: required by a bound in `FfiConverterArc` @@ -10,13 +10,13 @@ note: required by a bound in `FfiConverterArc` | | pub unsafe trait FfiConverterArc: Send + Sync { | ^^^^ required by this bound in `FfiConverterArc` - = note: this error originates in the macro `::uniffi::expand_trait_interface_support` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `::uniffi::export_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `(dyn Trait + 'static)` cannot be sent between threads safely --> $OUT_DIR[uniffi_uitests]/trait.uniffi.rs | - | ::uniffi::expand_trait_interface_support!(r#Trait); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `(dyn Trait + 'static)` cannot be sent between threads safely + | #[::uniffi::export_for_udl] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `(dyn Trait + 'static)` cannot be sent between threads safely | = help: the trait `Send` is not implemented for `(dyn Trait + 'static)` note: required by a bound in `FfiConverterArc` @@ -24,7 +24,7 @@ note: required by a bound in `FfiConverterArc` | | pub unsafe trait FfiConverterArc: Send + Sync { | ^^^^ required by this bound in `FfiConverterArc` - = note: this error originates in the macro `::uniffi::expand_trait_interface_support` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `::uniffi::export_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `(dyn ProcMacroTrait + 'static)` cannot be shared between threads safely --> tests/ui/interface_trait_not_sync_and_send.rs:11:1 diff --git a/fixtures/uitests/tests/ui/non_hashable_record_key.stderr b/fixtures/uitests/tests/ui/non_hashable_record_key.stderr index e7ad39c37d..98a1f99d3e 100644 --- a/fixtures/uitests/tests/ui/non_hashable_record_key.stderr +++ b/fixtures/uitests/tests/ui/non_hashable_record_key.stderr @@ -1,8 +1,8 @@ error[E0277]: the trait bound `f32: Hash` is not satisfied --> $OUT_DIR[uniffi_uitests]/records.uniffi.rs | - | ) -> as ::uniffi::FfiConverter>::ReturnType { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Hash` is not implemented for `f32` + | #[::uniffi::export_for_udl] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Hash` is not implemented for `f32` | = help: the following other types implement trait `Hash`: i128 @@ -15,12 +15,13 @@ error[E0277]: the trait bound `f32: Hash` is not satisfied u16 and $N others = note: required for `HashMap` to implement `FfiConverter` + = note: this error originates in the attribute macro `::uniffi::export_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `f32: std::cmp::Eq` is not satisfied --> $OUT_DIR[uniffi_uitests]/records.uniffi.rs | - | ) -> as ::uniffi::FfiConverter>::ReturnType { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::cmp::Eq` is not implemented for `f32` + | #[::uniffi::export_for_udl] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::cmp::Eq` is not implemented for `f32` | = help: the following other types implement trait `std::cmp::Eq`: i128 @@ -33,9 +34,10 @@ error[E0277]: the trait bound `f32: std::cmp::Eq` is not satisfied u16 and $N others = note: required for `HashMap` to implement `FfiConverter` + = note: this error originates in the attribute macro `::uniffi::export_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0425]: cannot find function `get_dict` in this scope --> $OUT_DIR[uniffi_uitests]/records.uniffi.rs | - | r#get_dict()) - | ^^^^^^^^^^ not found in this scope + | pub fn r#get_dict( + | ^^^^^^^^^^ not found in this scope diff --git a/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs index 2a0a80e556..6b9f96f224 100644 --- a/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs @@ -7,7 +7,7 @@ // public so other crates can refer to it via an `[External='crate'] typedef` #} -#[::uniffi::ffi_converter_enum(tag = crate::UniFfiTag)] +#[::uniffi::derive_enum_for_udl] 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 bfe2a4c607..7cd9ba2ec5 100644 --- a/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs @@ -7,8 +7,7 @@ // public so other crates can refer to it via an `[External='crate'] typedef` #} -#[::uniffi::ffi_converter_error( - tag = crate::UniFfiTag, +#[::uniffi::derive_error_for_udl( {% if e.is_flat() -%} flat_error, {% if ci.should_generate_error_read(e) -%} diff --git a/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs index 12f133df1c..0a851be04e 100644 --- a/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs @@ -7,7 +7,7 @@ {%- when ExternalKind::DataClass %} ::uniffi::ffi_converter_forward!(r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag); {%- when ExternalKind::Interface %} -::uniffi::ffi_converter_forward!(::std::sync::Arc, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag); +::uniffi::ffi_converter_arc_forward!(r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag); {%- endmatch %} {%- endfor %} diff --git a/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs index 9857b0375f..1ad3d34bba 100644 --- a/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs @@ -12,57 +12,67 @@ {%- match obj.imp() -%} {%- when ObjectImpl::Trait %} -::uniffi::expand_trait_interface_support!(r#{{ obj.name() }}); -{% else %} -#[::uniffi::expand_interface_support(tag = crate::UniFfiTag)] -struct {{ obj.rust_name() }} { } -{% endmatch %} - -{% let ffi_free = obj.ffi_object_free() -%} -#[doc(hidden)] -#[no_mangle] -pub extern "C" fn {{ ffi_free.name() }}(ptr: *const std::os::raw::c_void, call_status: &mut uniffi::RustCallStatus) { - uniffi::rust_call(call_status, || { - assert!(!ptr.is_null()); - {%- match obj.imp() -%} - {%- when ObjectImpl::Trait %} - {#- turn it into a Box and explicitly drop it. #} - drop(unsafe { Box::from_raw(ptr as *mut std::sync::Arc<{{ obj.rust_name() }}>) }); - {%- when ObjectImpl::Struct %} - {#- turn it into an Arc and explicitly drop it. #} - drop(unsafe { ::std::sync::Arc::from_raw(ptr as *const {{ obj.rust_name() }}) }); - {% endmatch %} - Ok(()) - }) +#[::uniffi::export_for_udl] +pub trait r#{{ obj.name() }} { + {%- for meth in obj.methods() %} + fn {{ meth.name() }}( + {% if meth.takes_self_by_arc()%}self: Arc{% else %}&self{% endif %}, + {%- for arg in meth.arguments() %} + {{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, + {%- endfor %} + ) + {%- match meth.return_type() %} + {%- when Some(return_type) %} -> {{ return_type|type_rs }}; + {%- when None %}; + {%- endmatch %} + {% endfor %} } +{% when ObjectImpl::Struct %} +#[::uniffi::derive_object_for_udl] +struct {{ obj.rust_name() }} { } {%- for cons in obj.constructors() %} - #[doc(hidden)] - #[no_mangle] - pub extern "C" fn r#{{ cons.ffi_func().name() }}( - {%- call rs::arg_list_ffi_decl(cons.ffi_func()) %} - ) -> *const std::os::raw::c_void /* *const {{ obj.name() }} */ { - uniffi::deps::log::debug!("{{ cons.ffi_func().name() }}"); - - // If the constructor does not have the same signature as declared in the UDL, then - // this attempt to call it will fail with a (somewhat) helpful compiler error. - uniffi::rust_call(call_status, || { - {{ cons|return_ffi_converter }}::lower_return( - {%- if cons.throws() %} - {{ obj.rust_name() }}::{% call rs::to_rs_call(cons) %}.map(::std::sync::Arc::new).map_err(Into::into) - {%- else %} - ::std::sync::Arc::new({{ obj.rust_name() }}::{% call rs::to_rs_call(cons) %}) - {%- endif %} - ) - }) +#[::uniffi::export_for_udl(constructor)] +impl {{ obj.rust_name() }} { + pub fn r#{{ cons.name() }}( + {%- for arg in cons.arguments() %} + r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, + {%- endfor %} + ) + {%- match (cons.return_type(), cons.throws_type()) %} + {%- when (Some(return_type), None) %} -> {{ return_type|type_rs }} + {%- when (Some(return_type), Some(error_type)) %} -> ::std::result::Result::<{{ return_type|type_rs }}, {{ error_type|type_rs }}> + {%- when (None, Some(error_type)) %} -> ::std::result::Result::<(), {{ error_type|type_rs }}> + {%- when (None, None) %} + {%- endmatch %} + { + unreachable!() } +} {%- endfor %} {%- for meth in obj.methods() %} - {% call rs::method_decl_prelude(meth) %} - <{{ obj.rust_name() }}>::{% call rs::to_rs_call(meth) %} - {% call rs::method_decl_postscript(meth) %} -{% endfor %} +#[::uniffi::export_for_udl] +impl {{ obj.rust_name() }} { + pub fn r#{{ meth.name() }}( + {% if meth.takes_self_by_arc()%}self: Arc{% else %}&self{% endif %}, + {%- for arg in meth.arguments() %} + r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, + {%- endfor %} + ) + {%- match (meth.return_type(), meth.throws_type()) %} + {%- when (Some(return_type), None) %} -> {{ return_type|type_rs }} + {%- when (Some(return_type), Some(error_type)) %} -> ::std::result::Result::<{{ return_type|type_rs }}, {{ error_type|type_rs }}> + {%- when (None, Some(error_type)) %} -> ::std::result::Result::<(), {{ error_type|type_rs }}> + {%- when (None, None) %} + {%- endmatch %} + { + unreachable!() + } +} +{%- endfor %} + +{% endmatch %} {%- for tm in obj.uniffi_traits() %} {# All magic methods get an explicit shim #} diff --git a/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs index a85affa163..85e131dd8c 100644 --- a/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs @@ -8,7 +8,7 @@ // public so other crates can refer to it via an `[External='crate'] typedef` #} -#[::uniffi::ffi_converter_record(tag = crate::UniFfiTag)] +#[::uniffi::derive_record_for_udl] struct r#{{ rec.name() }} { {%- for field in rec.fields() %} r#{{ field.name() }}: {{ field.as_type().borrow()|type_rs }}, diff --git a/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs index 5a0cdcefcd..eeee0f5ee2 100644 --- a/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs @@ -1,19 +1,15 @@ -{# -// For each top-level function declared in the UDL, we assume the caller has provided a corresponding -// rust function of the same name. We provide a `pub extern "C"` wrapper that does type conversions to -// send data across the FFI, which will fail to compile if the provided function does not match what's -// specified in the UDL. -#} -#[doc(hidden)] -#[no_mangle] -#[allow(clippy::let_unit_value,clippy::unit_arg)] // The generated code uses the unit type like other types to keep things uniform -pub extern "C" fn r#{{ func.ffi_func().name() }}( - {% call rs::arg_list_ffi_decl(func.ffi_func()) %} -) {% call rs::return_signature(func) %} { - // If the provided function does not match the signature specified in the UDL - // then this attempt to call it will not compile, and will give guidance as to why. - uniffi::deps::log::debug!("{{ func.ffi_func().name() }}"); - uniffi::rust_call(call_status, || {{ func|return_ffi_converter }}::lower_return( - {% call rs::to_rs_call(func) %}){% if func.throws() %}.map_err(Into::into){% endif %} - ) +#[::uniffi::export_for_udl] +pub fn r#{{ func.name() }}( + {%- for arg in func.arguments() %} + r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, + {%- endfor %} +) +{%- match (func.return_type(), func.throws_type()) %} +{%- when (Some(return_type), None) %} -> {{ return_type|type_rs }} +{%- when (Some(return_type), Some(error_type)) %} -> ::std::result::Result::<{{ return_type|type_rs }}, {{ error_type|type_rs }}> +{%- when (None, Some(error_type)) %} -> ::std::result::Result::<(), {{ error_type|type_rs }}> +{%- when (None, None) %} +{%- endmatch %} +{ + unreachable!() } diff --git a/uniffi_core/src/ffi/rustcalls.rs b/uniffi_core/src/ffi/rustcalls.rs index edaf480273..5a84a05209 100644 --- a/uniffi_core/src/ffi/rustcalls.rs +++ b/uniffi_core/src/ffi/rustcalls.rs @@ -172,10 +172,7 @@ where #[cfg(test)] mod test { use super::*; - use crate::{ - ffi_converter_default_return, ffi_converter_rust_buffer_lift_and_lower, MetadataBuffer, - UniFfiTag, - }; + use crate::test_util::TestError; fn create_call_status() -> RustCallStatus { RustCallStatus { @@ -184,26 +181,6 @@ mod test { } } - #[derive(Debug, PartialEq)] - struct TestError(String); - - // Use FfiConverter to simplify lifting TestError out of RustBuffer to check it - unsafe impl FfiConverter for TestError { - ffi_converter_rust_buffer_lift_and_lower!(UniFfiTag); - ffi_converter_default_return!(UniFfiTag); - - fn write(obj: TestError, buf: &mut Vec) { - >::write(obj.0, buf); - } - - fn try_read(buf: &mut &[u8]) -> anyhow::Result { - >::try_read(buf).map(TestError) - } - - // Use a dummy value here since we don't actually need TYPE_ID_META - const TYPE_ID_META: MetadataBuffer = MetadataBuffer::new(); - } - fn test_callback(a: u8) -> Result { match a { 0 => Ok(100), diff --git a/uniffi_core/src/ffi/rustfuture.rs b/uniffi_core/src/ffi/rustfuture.rs index 6ad0001a8a..27e1b2a14b 100644 --- a/uniffi_core/src/ffi/rustfuture.rs +++ b/uniffi_core/src/ffi/rustfuture.rs @@ -374,14 +374,14 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{try_lift_from_rust_buffer, MockEventLoop}; + use crate::{test_util::TestError, try_lift_from_rust_buffer, MockEventLoop}; use std::sync::Weak; // Mock future that we can manually control using an Option<> - struct MockFuture(Option>); + struct MockFuture(Option>); impl Future for MockFuture { - type Output = Result; + type Output = Result; fn poll(self: Pin<&mut Self>, _context: &mut Context<'_>) -> Poll { match &self.0 { @@ -392,7 +392,7 @@ mod tests { } // Type alias for the RustFuture we'll use in our tests - type TestRustFuture = RustFuture, crate::UniFfiTag>; + type TestRustFuture = RustFuture, crate::UniFfiTag>; // Stores the result that we send to the foreign code #[derive(Default)] @@ -441,7 +441,7 @@ mod tests { Arc::downgrade(&Pin::into_inner(Clone::clone(&self.rust_future))) } - fn complete_future(&self, value: Result) { + fn complete_future(&self, value: Result) { unsafe { (*self.rust_future.future.get()).0 = Some(value); } diff --git a/uniffi_core/src/ffi_converter_impls.rs b/uniffi_core/src/ffi_converter_impls.rs index 789014c9f3..44707399bf 100644 --- a/uniffi_core/src/ffi_converter_impls.rs +++ b/uniffi_core/src/ffi_converter_impls.rs @@ -24,7 +24,7 @@ use crate::{ check_remaining, ffi_converter_default_return, ffi_converter_rust_buffer_lift_and_lower, lower_into_rust_buffer, metadata, try_lift_from_rust_buffer, FfiConverter, FutureCallback, - MetadataBuffer, Result, RustBuffer, RustCallStatus, UnexpectedUniFFICallbackError, + LiftRef, MetadataBuffer, Result, RustBuffer, RustCallStatus, UnexpectedUniFFICallbackError, }; use anyhow::bail; use bytes::buf::{Buf, BufMut}; @@ -32,6 +32,7 @@ use paste::paste; use std::{ collections::HashMap, convert::{Infallible, TryFrom}, + error::Error, time::{Duration, SystemTime}, }; @@ -477,7 +478,7 @@ unsafe impl FfiConverter for crate::ForeignExecutor { unsafe impl FfiConverter for Result where R: FfiConverter, - E: FfiConverter, + E: FfiConverter + Error + Send + Sync + 'static, { type FfiType = (); // Placeholder while lower/lift/serializing is unimplemented type ReturnType = R::ReturnType; @@ -506,6 +507,13 @@ where } } + fn handle_failed_lift(arg_name: &str, err: anyhow::Error) -> RustBuffer { + match err.downcast::() { + Ok(actual_error) => lower_into_rust_buffer(actual_error), + Err(ohno) => panic!("Failed to convert arg '{arg_name}': {ohno}"), + } + } + fn lift_callback_return(buf: RustBuffer) -> Self { Ok(try_lift_from_rust_buffer::(buf) .expect("Error reading callback interface result")) @@ -533,3 +541,38 @@ where .concat(R::TYPE_ID_META) .concat(E::TYPE_ID_META); } + +macro_rules! simple_lift_ref_impl { + ($($t:ty),* $(,)?) => { + $( + impl LiftRef for $t { + type LiftType = $t; + } + )* + } +} + +simple_lift_ref_impl!( + u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, bool, String, Duration, SystemTime +); + +impl LiftRef for Option +where + Option: FfiConverter, +{ + type LiftType = Self; +} + +impl LiftRef for Vec +where + Vec: FfiConverter, +{ + type LiftType = Self; +} + +impl LiftRef for HashMap +where + HashMap: FfiConverter, +{ + type LiftType = Self; +} diff --git a/uniffi_core/src/ffi_converter_traits.rs b/uniffi_core/src/ffi_converter_traits.rs index 068484f069..a08d249c85 100644 --- a/uniffi_core/src/ffi_converter_traits.rs +++ b/uniffi_core/src/ffi_converter_traits.rs @@ -2,7 +2,7 @@ * 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/. */ -use std::sync::Arc; +use std::{borrow::Borrow, sync::Arc}; use crate::{ try_lift_from_rust_buffer, FfiDefault, MetadataBuffer, Result, RustBuffer, RustCallStatus, @@ -91,6 +91,16 @@ pub unsafe trait FfiConverter: Sized { /// `Err(buf)` fn lower_return(obj: Self) -> Result; + /// If possible, get a serialized error for failed argument lifts + /// + /// By default, we just panic and let `rust_call` handle things. However, for `Result<_, E>` + /// returns, if the anyhow error can be downcast to `E`, then serialize that and return it. + /// This results in the foreign code throwing a "normal" exception, rather than an unexpected + /// exception. + fn handle_failed_lift(arg_name: &str, e: anyhow::Error) -> RustBuffer { + panic!("Failed to convert arg '{arg_name}': {e}") + } + /// Lift a rust value of the target type, from an FFI value of type Self::FfiType. /// /// This trait method is used for receiving data from the foreign language code in rust, @@ -253,3 +263,14 @@ where const TYPE_ID_META: MetadataBuffer = T::TYPE_ID_META; } + +/// Trait used to lift references +/// +/// This is needed because if we see a `&T` parameter, it's not clear which FfiConverter to use to +/// lift it. Normally it's just `T`, but for interfaces it's `Arc` and for callback interfaces +/// it's `Box`. +/// +/// This trait provides the proc-macros with a way to name the correct type. +pub trait LiftRef { + type LiftType: FfiConverter + Borrow; +} diff --git a/uniffi_core/src/lib.rs b/uniffi_core/src/lib.rs index f5e4a26cbb..7444ccca88 100644 --- a/uniffi_core/src/lib.rs +++ b/uniffi_core/src/lib.rs @@ -44,7 +44,7 @@ mod ffi_converter_traits; pub mod metadata; pub use ffi::*; -pub use ffi_converter_traits::{FfiConverter, FfiConverterArc}; +pub use ffi_converter_traits::{FfiConverter, FfiConverterArc, LiftRef}; pub use metadata::*; // Re-export the libs that we use in the generated code, @@ -305,6 +305,10 @@ macro_rules! do_ffi_converter_forward { const TYPE_ID_META: ::uniffi::MetadataBuffer = <$T as $crate::$trait<$existing_impl_tag>>::TYPE_ID_META; } + + impl $crate::LiftRef<$new_impl_tag> for $T { + type LiftType = <$T as $crate::LiftRef<$existing_impl_tag>>::LiftType; + } }; } @@ -338,3 +342,44 @@ mod test { ) } } + +#[cfg(test)] +pub mod test_util { + use std::{error::Error, fmt}; + + use super::*; + + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct TestError(pub String); + + // Use FfiConverter to simplify lifting TestError out of RustBuffer to check it + unsafe impl FfiConverter for TestError { + ffi_converter_rust_buffer_lift_and_lower!(UniFfiTag); + ffi_converter_default_return!(UniFfiTag); + + fn write(obj: TestError, buf: &mut Vec) { + >::write(obj.0, buf); + } + + fn try_read(buf: &mut &[u8]) -> Result { + >::try_read(buf).map(TestError) + } + + // Use a dummy value here since we don't actually need TYPE_ID_META + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::new(); + } + + impl fmt::Display for TestError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } + } + + impl Error for TestError {} + + impl> From for TestError { + fn from(v: T) -> Self { + Self(v.into()) + } + } +} diff --git a/uniffi_macros/src/custom.rs b/uniffi_macros/src/custom.rs index 001d262afd..d3274ca5ac 100644 --- a/uniffi_macros/src/custom.rs +++ b/uniffi_macros/src/custom.rs @@ -12,9 +12,10 @@ use syn::Path; pub(crate) fn expand_ffi_converter_custom_type( ident: &Ident, builtin: &Path, - tag: Option<&Path>, + udl_mode: bool, ) -> syn::Result { - let impl_spec = tagged_impl_header("FfiConverter", ident, tag); + let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode); + let lift_ref_impl_spec = tagged_impl_header("LiftRef", ident, udl_mode); let name = ident_to_string(ident); let mod_path = mod_path()?; @@ -45,6 +46,10 @@ pub(crate) fn expand_ffi_converter_custom_type( .concat_str(#name) .concat(<#builtin as ::uniffi::FfiConverter>::TYPE_ID_META); } + + #lift_ref_impl_spec { + type LiftType = Self; + } }) } @@ -52,9 +57,9 @@ pub(crate) fn expand_ffi_converter_custom_type( pub(crate) fn expand_ffi_converter_custom_newtype( ident: &Ident, builtin: &Path, - tag: Option<&Path>, + udl_mode: bool, ) -> syn::Result { - let ffi_converter = expand_ffi_converter_custom_type(ident, builtin, tag)?; + let ffi_converter = expand_ffi_converter_custom_type(ident, builtin, udl_mode)?; let type_converter = custom_ffi_type_converter(ident, builtin)?; Ok(quote! { diff --git a/uniffi_macros/src/enum_.rs b/uniffi_macros/src/enum_.rs index 919a48f270..9034a9540a 100644 --- a/uniffi_macros/src/enum_.rs +++ b/uniffi_macros/src/enum_.rs @@ -1,60 +1,44 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{Data, DataEnum, DeriveInput, Field, Index, Path}; +use syn::{Data, DataEnum, DeriveInput, Field, Index}; use crate::util::{ create_metadata_items, ident_to_string, mod_path, tagged_impl_header, - try_metadata_value_from_usize, try_read_field, ArgumentNotAllowedHere, AttributeSliceExt, - CommonAttr, + try_metadata_value_from_usize, try_read_field, }; -pub fn expand_enum(input: DeriveInput) -> TokenStream { +pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result { 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(); + return Err(syn::Error::new( + Span::call_site(), + "This derive must only be used on enums", + )) } }; - let ident = &input.ident; - let attr_error = input - .attrs - .parse_uniffi_attr_args::() - .err() - .map(syn::Error::into_compile_error); - let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, None); + let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, udl_mode); - let meta_static_var = - enum_meta_static_var(ident, &enum_).unwrap_or_else(syn::Error::into_compile_error); + let meta_static_var = (!udl_mode).then(|| { + enum_meta_static_var(ident, &enum_).unwrap_or_else(syn::Error::into_compile_error) + }); - quote! { - #attr_error + Ok(quote! { #ffi_converter_impl #meta_static_var - } -} - -pub(crate) fn expand_enum_ffi_converter(attr: CommonAttr, input: DeriveInput) -> TokenStream { - match input.data { - Data::Enum(e) => enum_ffi_converter_impl(&input.ident, &e, attr.tag.as_ref()), - _ => 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( ident: &Ident, enum_: &DataEnum, - tag: Option<&Path>, + udl_mode: bool, ) -> TokenStream { enum_or_error_ffi_converter_impl( ident, enum_, - tag, + udl_mode, false, quote! { ::uniffi::metadata::codes::TYPE_ENUM }, ) @@ -63,13 +47,13 @@ pub(crate) fn enum_ffi_converter_impl( pub(crate) fn rich_error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, - tag: Option<&Path>, + udl_mode: bool, handle_unknown_callback_error: bool, ) -> TokenStream { enum_or_error_ffi_converter_impl( ident, enum_, - tag, + udl_mode, handle_unknown_callback_error, quote! { ::uniffi::metadata::codes::TYPE_ENUM }, ) @@ -78,12 +62,13 @@ pub(crate) fn rich_error_ffi_converter_impl( fn enum_or_error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, - tag: Option<&Path>, + udl_mode: bool, handle_unknown_callback_error: bool, metadata_type_code: TokenStream, ) -> TokenStream { let name = ident_to_string(ident); - let impl_spec = tagged_impl_header("FfiConverter", ident, tag); + let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode); + let lift_ref_impl_spec = tagged_impl_header("LiftRef", ident, udl_mode); let mod_path = match mod_path() { Ok(p) => p, Err(e) => return e.into_compile_error(), @@ -147,6 +132,10 @@ fn enum_or_error_ffi_converter_impl( .concat_str(#mod_path) .concat_str(#name); } + + #lift_ref_impl_spec { + type LiftType = Self; + } } } diff --git a/uniffi_macros/src/error.rs b/uniffi_macros/src/error.rs index ad4f65c444..a60db41753 100644 --- a/uniffi_macros/src/error.rs +++ b/uniffi_macros/src/error.rs @@ -2,19 +2,24 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{ parse::{Parse, ParseStream}, - Data, DataEnum, DeriveInput, Index, Path, Token, + Data, DataEnum, DeriveInput, Index, }; use crate::{ enum_::{handle_callback_unexpected_error_fn, rich_error_ffi_converter_impl, variant_metadata}, util::{ - chain, create_metadata_items, either_attribute_arg, ident_to_string, mod_path, + chain, create_metadata_items, either_attribute_arg, ident_to_string, kw, mod_path, parse_comma_separated, tagged_impl_header, try_metadata_value_from_usize, AttributeSliceExt, UniffiAttributeArgs, }, }; -pub fn expand_error(input: DeriveInput) -> syn::Result { +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, _ => { @@ -24,12 +29,16 @@ pub fn expand_error(input: DeriveInput) -> syn::Result { )); } }; - let ident = &input.ident; - let attr = input.attrs.parse_uniffi_attr_args::()?; - let ffi_converter_impl = error_ffi_converter_impl(ident, &enum_, &attr); - let meta_static_var = error_meta_static_var(ident, &enum_, attr.flat.is_some()) - .unwrap_or_else(syn::Error::into_compile_error); + 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, &enum_, attr.flat.is_some()) + .unwrap_or_else(syn::Error::into_compile_error) + }); let variant_errors: TokenStream = enum_ .variants @@ -53,23 +62,17 @@ pub fn expand_error(input: DeriveInput) -> syn::Result { }) } -pub(crate) fn expand_ffi_converter_error(attr: ErrorAttr, input: DeriveInput) -> TokenStream { - match input.data { - Data::Enum(e) => error_ffi_converter_impl(&input.ident, &e, &attr), - _ => syn::Error::new( - proc_macro2::Span::call_site(), - "This attribute must only be used on enums", - ) - .into_compile_error(), - } -} - -fn error_ffi_converter_impl(ident: &Ident, enum_: &DataEnum, attr: &ErrorAttr) -> TokenStream { +fn error_ffi_converter_impl( + ident: &Ident, + enum_: &DataEnum, + attr: &ErrorAttr, + udl_mode: bool, +) -> TokenStream { if attr.flat.is_some() { flat_error_ffi_converter_impl( ident, enum_, - attr.tag.as_ref(), + udl_mode, attr.with_try_read.is_some(), attr.handle_unknown_callback_error.is_some(), ) @@ -77,7 +80,7 @@ fn error_ffi_converter_impl(ident: &Ident, enum_: &DataEnum, attr: &ErrorAttr) - rich_error_ffi_converter_impl( ident, enum_, - attr.tag.as_ref(), + udl_mode, attr.handle_unknown_callback_error.is_some(), ) } @@ -90,12 +93,13 @@ fn error_ffi_converter_impl(ident: &Ident, enum_: &DataEnum, attr: &ErrorAttr) - fn flat_error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, - tag: Option<&Path>, + udl_mode: bool, implement_try_read: bool, handle_unknown_callback_error: bool, ) -> TokenStream { let name = ident_to_string(ident); - let impl_spec = tagged_impl_header("FfiConverter", ident, tag); + let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode); + let lift_ref_impl_spec = tagged_impl_header("LiftRef", ident, udl_mode); let mod_path = match mod_path() { Ok(p) => p, Err(e) => return e.into_compile_error(), @@ -162,6 +166,10 @@ fn flat_error_ffi_converter_impl( .concat_str(#mod_path) .concat_str(#name); } + + #lift_ref_impl_spec { + type LiftType = Self; + } } } @@ -199,16 +207,8 @@ pub fn flat_error_variant_metadata(enum_: &DataEnum) -> syn::Result, +pub struct ErrorAttr { flat: Option, with_try_read: Option, /// Can this error be used in a callback interface? @@ -218,14 +218,7 @@ pub(crate) struct ErrorAttr { impl UniffiAttributeArgs for ErrorAttr { fn parse_one(input: ParseStream<'_>) -> syn::Result { let lookahead = input.lookahead1(); - if lookahead.peek(kw::tag) { - let _: kw::tag = input.parse()?; - let _: Token![=] = input.parse()?; - Ok(Self { - tag: Some(input.parse()?), - ..Self::default() - }) - } else if lookahead.peek(kw::flat_error) { + if lookahead.peek(kw::flat_error) { Ok(Self { flat: input.parse()?, ..Self::default() @@ -247,7 +240,6 @@ impl UniffiAttributeArgs for ErrorAttr { fn merge(self, other: Self) -> syn::Result { Ok(Self { - tag: either_attribute_arg(self.tag, other.tag)?, flat: either_attribute_arg(self.flat, other.flat)?, with_try_read: either_attribute_arg(self.with_try_read, other.with_try_read)?, handle_unknown_callback_error: either_attribute_arg( diff --git a/uniffi_macros/src/export.rs b/uniffi_macros/src/export.rs index c021e85968..9a77beb700 100644 --- a/uniffi_macros/src/export.rs +++ b/uniffi_macros/src/export.rs @@ -4,7 +4,7 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, quote_spanned}; -use syn::{visit_mut::VisitMut, Item, Path, Type}; +use syn::{visit_mut::VisitMut, Item, Type}; mod attributes; mod callback_interface; @@ -12,7 +12,6 @@ mod item; mod scaffolding; use self::{ - attributes::ExportAttributeArguments, item::{ExportItem, ImplItem}, scaffolding::{gen_constructor_scaffolding, gen_fn_scaffolding, gen_method_scaffolding}, }; @@ -20,6 +19,7 @@ use crate::{ object::interface_meta_static_var, util::{ident_to_string, mod_path, tagged_impl_header}, }; +pub use attributes::ExportAttributeArguments; pub use callback_interface::ffi_converter_callback_interface_impl; use uniffi_meta::free_fn_symbol_name; @@ -29,8 +29,9 @@ use uniffi_meta::free_fn_symbol_name; pub(crate) fn expand_export( mut item: Item, args: ExportAttributeArguments, - mod_path: String, + udl_mode: bool, ) -> syn::Result { + let mod_path = mod_path()?; // If the input is an `impl` block, rewrite any uses of the `Self` type // alias to the actual type, so we don't have to special-case it in the // metadata collection or scaffolding code generation (which generates @@ -40,7 +41,7 @@ pub(crate) fn expand_export( let metadata = ExportItem::new(item, &args)?; match metadata { - ExportItem::Function { sig } => gen_fn_scaffolding(sig, &args), + ExportItem::Function { sig } => gen_fn_scaffolding(sig, &args, udl_mode), ExportItem::Impl { items, self_ident } => { if let Some(rt) = &args.async_runtime { if items @@ -57,8 +58,8 @@ pub(crate) fn expand_export( let item_tokens: TokenStream = items .into_iter() .map(|item| match item { - ImplItem::Constructor(sig) => gen_constructor_scaffolding(sig, &args), - ImplItem::Method(sig) => gen_method_scaffolding(sig, &args), + ImplItem::Constructor(sig) => gen_constructor_scaffolding(sig, &args, udl_mode), + ImplItem::Method(sig) => gen_method_scaffolding(sig, &args, udl_mode), }) .collect::>()?; Ok(quote_spanned! { self_ident.span() => #item_tokens }) @@ -101,15 +102,17 @@ pub(crate) fn expand_export( "async trait methods are not supported", )); } - gen_method_scaffolding(sig, &args) + gen_method_scaffolding(sig, &args, udl_mode) } _ => unreachable!("traits have no constructors"), }) .collect::>()?; - let meta_static_var = interface_meta_static_var(&self_ident, true, &mod_path) - .unwrap_or_else(syn::Error::into_compile_error); - let ffi_converter_tokens = ffi_converter_trait_impl(&self_ident, None); + let meta_static_var = (!udl_mode).then(|| { + interface_meta_static_var(&self_ident, true, &mod_path) + .unwrap_or_else(syn::Error::into_compile_error) + }); + let ffi_converter_tokens = ffi_converter_trait_impl(&self_ident, false); Ok(quote_spanned! { self_ident.span() => #meta_static_var @@ -163,14 +166,15 @@ pub(crate) fn expand_export( #trait_impl - #(#metadata_items)* + #(#metadata_items)* }) } } } -pub(crate) fn ffi_converter_trait_impl(trait_ident: &Ident, tag: Option<&Path>) -> TokenStream { - let impl_spec = tagged_impl_header("FfiConverterArc", "e! { dyn #trait_ident }, tag); +pub(crate) fn ffi_converter_trait_impl(trait_ident: &Ident, udl_mode: bool) -> TokenStream { + let impl_spec = tagged_impl_header("FfiConverterArc", "e! { dyn #trait_ident }, udl_mode); + let lift_ref_impl_spec = tagged_impl_header("LiftRef", "e! { dyn #trait_ident }, udl_mode); let name = ident_to_string(trait_ident); let mod_path = match mod_path() { Ok(p) => p, @@ -232,6 +236,10 @@ pub(crate) fn ffi_converter_trait_impl(trait_ident: &Ident, tag: Option<&Path>) .concat_str(#name) .concat_bool(true); } + + #lift_ref_impl_spec { + type LiftType = ::std::sync::Arc; + } } } diff --git a/uniffi_macros/src/export/attributes.rs b/uniffi_macros/src/export/attributes.rs index fbb69a18f7..92b79cfe3b 100644 --- a/uniffi_macros/src/export/attributes.rs +++ b/uniffi_macros/src/export/attributes.rs @@ -1,4 +1,4 @@ -use crate::util::{either_attribute_arg, parse_comma_separated, UniffiAttributeArgs}; +use crate::util::{either_attribute_arg, kw, parse_comma_separated, UniffiAttributeArgs}; use proc_macro2::TokenStream; use quote::ToTokens; @@ -7,15 +7,11 @@ use syn::{ Attribute, LitStr, Meta, PathArguments, PathSegment, Token, }; -pub(crate) mod kw { - syn::custom_keyword!(async_runtime); - syn::custom_keyword!(callback_interface); -} - #[derive(Default)] pub struct ExportAttributeArguments { pub(crate) async_runtime: Option, pub(crate) callback_interface: Option, + pub(crate) constructor: Option, } impl Parse for ExportAttributeArguments { @@ -39,6 +35,11 @@ impl UniffiAttributeArgs for ExportAttributeArguments { callback_interface: input.parse()?, ..Self::default() }) + } else if lookahead.peek(kw::constructor) { + Ok(Self { + constructor: input.parse()?, + ..Self::default() + }) } else { Ok(Self::default()) } @@ -51,6 +52,7 @@ impl UniffiAttributeArgs for ExportAttributeArguments { self.callback_interface, other.callback_interface, )?, + constructor: either_attribute_arg(self.constructor, other.constructor)?, }) } } diff --git a/uniffi_macros/src/export/callback_interface.rs b/uniffi_macros/src/export/callback_interface.rs index 67007d7c89..4c8fc360f3 100644 --- a/uniffi_macros/src/export/callback_interface.rs +++ b/uniffi_macros/src/export/callback_interface.rs @@ -4,13 +4,13 @@ use crate::{ export::ImplItem, - fnsig::{FnKind, FnSignature}, + fnsig::{FnKind, FnSignature, ReceiverArg}, util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header}, }; use proc_macro2::{Span, TokenStream}; use quote::quote; use std::iter; -use syn::{Ident, Path}; +use syn::Ident; pub(super) fn trait_impl( ident: &Ident, @@ -25,7 +25,7 @@ pub(super) fn trait_impl( _ => unreachable!("traits have no constructors"), }) .collect::>()?; - let ffi_converter_tokens = ffi_converter_callback_interface_impl(trait_ident, ident, None); + let ffi_converter_tokens = ffi_converter_callback_interface_impl(trait_ident, ident, false); Ok(quote! { #[doc(hidden)] @@ -61,13 +61,17 @@ pub(super) fn trait_impl( pub fn ffi_converter_callback_interface_impl( trait_ident: &Ident, trait_impl_ident: &Ident, - tag: Option<&Path>, + udl_mode: bool, ) -> TokenStream { let name = ident_to_string(trait_ident); - let impl_spec = tagged_impl_header("FfiConverter", "e! { Box }, tag); - let tag = match tag { - Some(t) => quote! { #t }, - None => quote! { T }, + let dyn_trait = quote! { dyn #trait_ident }; + let box_dyn_trait = quote! { ::std::boxed::Box<#dyn_trait> }; + let impl_spec = tagged_impl_header("FfiConverter", &box_dyn_trait, udl_mode); + let lift_ref_impl_spec = tagged_impl_header("LiftRef", &dyn_trait, udl_mode); + let tag = if udl_mode { + quote! { crate::UniFfiTag } + } else { + quote! { T } }; let mod_path = match mod_path() { Ok(p) => p, @@ -98,7 +102,7 @@ pub fn ffi_converter_callback_interface_impl( } fn try_lift(v: Self::FfiType) -> uniffi::deps::anyhow::Result { - Ok(Box::new(<#trait_impl_ident>::new(v))) + Ok(::std::boxed::Box::new(<#trait_impl_ident>::new(v))) } fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result { @@ -115,6 +119,10 @@ pub fn ffi_converter_callback_interface_impl( .concat_str(#mod_path) .concat_str(#name); } + + #lift_ref_impl_spec { + type LiftType = #box_dyn_trait; + } } } @@ -139,18 +147,22 @@ fn gen_method_impl(sig: &FnSignature, internals_ident: &Ident) -> syn::Result { + return Err(syn::Error::new( + sig.span, + "callback interface methods must take &self as their first argument", + )); + } + Some(ReceiverArg::Ref) => quote! { &self }, + Some(ReceiverArg::Arc) => quote! { self: Arc }, + }; let params = sig.params(); let buf_ident = Ident::new("uniffi_args_buf", Span::call_site()); let write_exprs = sig.write_exprs(&buf_ident); Ok(quote! { - fn #ident(&self, #(#params),*) -> #return_ty { + fn #ident(#self_param, #(#params),*) -> #return_ty { #[allow(unused_mut)] let mut #buf_ident = ::std::vec::Vec::new(); #(#write_exprs;)* diff --git a/uniffi_macros/src/export/item.rs b/uniffi_macros/src/export/item.rs index e4cd60e788..f0d68048a2 100644 --- a/uniffi_macros/src/export/item.rs +++ b/uniffi_macros/src/export/item.rs @@ -30,7 +30,7 @@ impl ExportItem { let sig = FnSignature::new_function(item.sig)?; Ok(Self::Function { sig }) } - syn::Item::Impl(item) => Self::from_impl(item), + syn::Item::Impl(item) => Self::from_impl(item, args.constructor.is_some()), syn::Item::Trait(item) => Self::from_trait(item, args.callback_interface.is_some()), // FIXME: Support const / static? _ => Err(syn::Error::new( @@ -41,7 +41,7 @@ impl ExportItem { } } - fn from_impl(item: syn::ItemImpl) -> syn::Result { + pub fn from_impl(item: syn::ItemImpl, force_constructor: bool) -> syn::Result { if !item.generics.params.is_empty() || item.generics.where_clause.is_some() { return Err(syn::Error::new_spanned( &item.generics, @@ -83,7 +83,7 @@ impl ExportItem { }; let attrs = ExportedImplFnAttributes::new(&impl_fn.attrs)?; - let item = if attrs.constructor { + let item = if force_constructor || attrs.constructor { ImplItem::Constructor(FnSignature::new_constructor( self_ident.clone(), impl_fn.sig, diff --git a/uniffi_macros/src/export/scaffolding.rs b/uniffi_macros/src/export/scaffolding.rs index addc76752c..3f25d4ed0d 100644 --- a/uniffi_macros/src/export/scaffolding.rs +++ b/uniffi_macros/src/export/scaffolding.rs @@ -12,6 +12,7 @@ use crate::fnsig::{FnKind, FnSignature, NamedArg}; pub(super) fn gen_fn_scaffolding( sig: FnSignature, arguments: &ExportAttributeArguments, + udl_mode: bool, ) -> syn::Result { if sig.receiver.is_some() { return Err(syn::Error::new( @@ -27,8 +28,11 @@ pub(super) fn gen_fn_scaffolding( )); } } - let metadata_items = sig.metadata_items()?; - let scaffolding_func = gen_ffi_function(&sig, arguments)?; + let metadata_items = (!udl_mode).then(|| { + sig.metadata_items() + .unwrap_or_else(syn::Error::into_compile_error) + }); + let scaffolding_func = gen_ffi_function(&sig, arguments, udl_mode)?; Ok(quote! { #scaffolding_func #metadata_items @@ -38,6 +42,7 @@ pub(super) fn gen_fn_scaffolding( pub(super) fn gen_constructor_scaffolding( sig: FnSignature, arguments: &ExportAttributeArguments, + udl_mode: bool, ) -> syn::Result { if sig.receiver.is_some() { return Err(syn::Error::new( @@ -48,8 +53,11 @@ pub(super) fn gen_constructor_scaffolding( if sig.is_async { return Err(syn::Error::new(sig.span, "constructors can't be async")); } - let metadata_items = sig.metadata_items()?; - let scaffolding_func = gen_ffi_function(&sig, arguments)?; + let metadata_items = (!udl_mode).then(|| { + sig.metadata_items() + .unwrap_or_else(syn::Error::into_compile_error) + }); + let scaffolding_func = gen_ffi_function(&sig, arguments, udl_mode)?; Ok(quote! { #scaffolding_func #metadata_items @@ -59,6 +67,7 @@ pub(super) fn gen_constructor_scaffolding( pub(super) fn gen_method_scaffolding( sig: FnSignature, arguments: &ExportAttributeArguments, + udl_mode: bool, ) -> syn::Result { let scaffolding_func = if sig.receiver.is_none() { return Err(syn::Error::new( @@ -66,10 +75,13 @@ pub(super) fn gen_method_scaffolding( "associated functions are not currently supported", )); } else { - gen_ffi_function(&sig, arguments)? + gen_ffi_function(&sig, arguments, udl_mode)? }; - let metadata_items = sig.metadata_items()?; + let metadata_items = (!udl_mode).then(|| { + sig.metadata_items() + .unwrap_or_else(syn::Error::into_compile_error) + }); Ok(quote! { #scaffolding_func #metadata_items @@ -87,19 +99,30 @@ struct ScaffoldingBits { } impl ScaffoldingBits { - fn new_for_function(sig: &FnSignature) -> Self { + fn new_for_function(sig: &FnSignature, udl_mode: bool) -> Self { let ident = &sig.ident; let params: Vec<_> = sig.args.iter().map(NamedArg::scaffolding_param).collect(); let param_lifts = sig.lift_exprs(); + let simple_rust_fn_call = quote! { #ident(#(#param_lifts,)*) }; + let rust_fn_call = if udl_mode && sig.looks_like_result { + quote! { #simple_rust_fn_call.map_err(::std::convert::Into::into) } + } else { + simple_rust_fn_call + }; Self { params, pre_fn_call: quote! {}, - rust_fn_call: quote! { #ident(#(#param_lifts,)*) }, + rust_fn_call, } } - fn new_for_method(sig: &FnSignature, self_ident: &Ident, is_trait: bool) -> Self { + fn new_for_method( + sig: &FnSignature, + self_ident: &Ident, + is_trait: bool, + udl_mode: bool, + ) -> Self { let ident = &sig.ident; let ffi_converter = if is_trait { quote! { @@ -114,27 +137,44 @@ impl ScaffoldingBits { .chain(sig.scaffolding_params()) .collect(); let param_lifts = sig.lift_exprs(); + let simple_rust_fn_call = quote! { uniffi_self.#ident(#(#param_lifts,)*) }; + let rust_fn_call = if udl_mode && sig.looks_like_result { + quote! { #simple_rust_fn_call.map_err(::std::convert::Into::into) } + } else { + simple_rust_fn_call + }; + let return_ffi_converter = sig.return_ffi_converter(); Self { params, pre_fn_call: quote! { - let uniffi_self = #ffi_converter::try_lift(uniffi_self_lowered).unwrap_or_else(|err| { - ::std::panic!("Failed to convert arg 'self': {}", err) - }); + let uniffi_self = match #ffi_converter::try_lift(uniffi_self_lowered) { + Ok(v) => v, + Err(e) => return Err(#return_ffi_converter::handle_failed_lift("self", e)), + }; }, - rust_fn_call: quote! { uniffi_self.#ident(#(#param_lifts,)*) }, + rust_fn_call, } } - fn new_for_constructor(sig: &FnSignature, self_ident: &Ident) -> Self { + fn new_for_constructor(sig: &FnSignature, self_ident: &Ident, udl_mode: bool) -> Self { let ident = &sig.ident; let params: Vec<_> = sig.args.iter().map(NamedArg::scaffolding_param).collect(); let param_lifts = sig.lift_exprs(); + let simple_rust_fn_call = quote! { #self_ident::#ident(#(#param_lifts,)*) }; + let rust_fn_call = match (udl_mode, sig.looks_like_result) { + // For UDL + (true, false) => quote! { ::std::sync::Arc::new(#simple_rust_fn_call) }, + (true, true) => { + quote! { #simple_rust_fn_call.map(::std::sync::Arc::new).map_err(::std::convert::Into::into) } + } + (false, _) => simple_rust_fn_call, + }; Self { params, pre_fn_call: quote! {}, - rust_fn_call: quote! { #self_ident::#ident(#(#param_lifts,)*) }, + rust_fn_call, } } } @@ -146,18 +186,29 @@ impl ScaffoldingBits { fn gen_ffi_function( sig: &FnSignature, arguments: &ExportAttributeArguments, + udl_mode: bool, ) -> syn::Result { let ScaffoldingBits { params, pre_fn_call, rust_fn_call, } = match &sig.kind { - FnKind::Function => ScaffoldingBits::new_for_function(sig), - FnKind::Method { self_ident } => ScaffoldingBits::new_for_method(sig, self_ident, false), + FnKind::Function => ScaffoldingBits::new_for_function(sig, udl_mode), + FnKind::Method { self_ident } => { + ScaffoldingBits::new_for_method(sig, self_ident, false, udl_mode) + } FnKind::TraitMethod { self_ident, .. } => { - ScaffoldingBits::new_for_method(sig, self_ident, true) + ScaffoldingBits::new_for_method(sig, self_ident, true, udl_mode) } - FnKind::Constructor { self_ident } => ScaffoldingBits::new_for_constructor(sig, self_ident), + FnKind::Constructor { self_ident } => { + ScaffoldingBits::new_for_constructor(sig, self_ident, udl_mode) + } + }; + // Scaffolding functions are logically `pub`, but we don't use that in UDL mode since UDL has + // historically not required types to be `pub` + let vis = match udl_mode { + false => quote! { pub }, + true => quote! {}, }; let ffi_ident = sig.scaffolding_fn_ident()?; @@ -168,14 +219,16 @@ fn gen_ffi_function( quote! { #[doc(hidden)] #[no_mangle] - pub extern "C" fn #ffi_ident( + #vis extern "C" fn #ffi_ident( #(#params,)* call_status: &mut ::uniffi::RustCallStatus, ) -> <#return_ty as ::uniffi::FfiConverter>::ReturnType { ::uniffi::deps::log::debug!(#name); ::uniffi::rust_call(call_status, || { #pre_fn_call - <#return_ty as ::uniffi::FfiConverter>::lower_return(#rust_fn_call) + <#return_ty as ::uniffi::FfiConverter>::lower_return( + #rust_fn_call + ) }) } } @@ -188,7 +241,7 @@ fn gen_ffi_function( quote! { #[doc(hidden)] #[no_mangle] - pub extern "C" fn #ffi_ident( + #vis extern "C" fn #ffi_ident( #(#params,)* uniffi_executor_handle: ::uniffi::ForeignExecutorHandle, uniffi_callback: <#return_ty as ::uniffi::FfiConverter>::FutureCallback, diff --git a/uniffi_macros/src/fnsig.rs b/uniffi_macros/src/fnsig.rs index 901cd510be..5ee86866ab 100644 --- a/uniffi_macros/src/fnsig.rs +++ b/uniffi_macros/src/fnsig.rs @@ -19,6 +19,10 @@ pub(crate) struct FnSignature { pub receiver: Option, pub args: Vec, pub return_ty: TokenStream, + // Does this the return type look like a result? + // Only use this in UDL mode. + // In general, it's not reliable because it fails for type aliases. + pub looks_like_result: bool, } impl FnSignature { @@ -45,6 +49,7 @@ impl FnSignature { pub(crate) fn new(kind: FnKind, sig: syn::Signature) -> syn::Result { let span = sig.span(); let ident = sig.ident; + let looks_like_result = looks_like_result(&sig.output); let output = match sig.output { ReturnType::Default => quote! { () }, ReturnType::Type(_, ty) => quote! { #ty }, @@ -79,23 +84,34 @@ impl FnSignature { }) }) .collect::>>()?; + let mod_path = mod_path()?; Ok(Self { kind, span, - mod_path: mod_path()?, + mod_path, name: ident_to_string(&ident), ident, is_async, receiver, args, return_ty: output, + looks_like_result, }) } + pub fn return_ffi_converter(&self) -> TokenStream { + let return_ty = &self.return_ty; + quote! { + <#return_ty as ::uniffi::FfiConverter> + } + } + /// Lift expressions for each of our arguments pub fn lift_exprs(&self) -> impl Iterator + '_ { - self.args.iter().map(NamedArg::lift_expr) + self.args + .iter() + .map(|a| a.lift_expr(&self.return_ffi_converter())) } /// Write expressions for each of our arguments @@ -113,16 +129,17 @@ impl FnSignature { /// Name of the scaffolding function to generate for this function pub fn scaffolding_fn_ident(&self) -> syn::Result { - let mod_path = &self.mod_path; let name = &self.name; let name = match &self.kind { - FnKind::Function => uniffi_meta::fn_symbol_name(mod_path, name), + FnKind::Function => uniffi_meta::fn_symbol_name(&self.mod_path, name), FnKind::Method { self_ident } | FnKind::TraitMethod { self_ident, .. } => { - uniffi_meta::method_symbol_name(mod_path, &ident_to_string(self_ident), name) - } - FnKind::Constructor { self_ident } => { - uniffi_meta::constructor_symbol_name(mod_path, &ident_to_string(self_ident), name) + uniffi_meta::method_symbol_name(&self.mod_path, &ident_to_string(self_ident), name) } + FnKind::Constructor { self_ident } => uniffi_meta::constructor_symbol_name( + &self.mod_path, + &ident_to_string(self_ident), + name, + ), }; Ok(Ident::new(&name, Span::call_site())) } @@ -225,19 +242,18 @@ impl FnSignature { } pub(crate) fn checksum_symbol_name(&self) -> String { - let mod_path = &self.mod_path; let name = &self.name; match &self.kind { - FnKind::Function => uniffi_meta::fn_checksum_symbol_name(mod_path, name), + FnKind::Function => uniffi_meta::fn_checksum_symbol_name(&self.mod_path, name), FnKind::Method { self_ident } | FnKind::TraitMethod { self_ident, .. } => { uniffi_meta::method_checksum_symbol_name( - mod_path, + &self.mod_path, &ident_to_string(self_ident), name, ) } FnKind::Constructor { self_ident } => uniffi_meta::constructor_checksum_symbol_name( - mod_path, + &self.mod_path, &ident_to_string(self_ident), name, ), @@ -268,31 +284,42 @@ impl TryFrom for Arg { let span = syn_arg.span(); let kind = match syn_arg { FnArg::Typed(p) => match *p.pat { - Pat::Ident(i) => { - if i.ident == "self" { - Ok(ArgKind::Receiver(ReceiverArg)) - } else { - Ok(ArgKind::Named(NamedArg::new(i.ident, &p.ty))) - } - } + Pat::Ident(i) => Ok(ArgKind::Named(NamedArg::new(i.ident, &p.ty))), _ => Err(syn::Error::new_spanned(p, "Argument name missing")), }, - FnArg::Receiver(Receiver { .. }) => Ok(ArgKind::Receiver(ReceiverArg)), + FnArg::Receiver(receiver) => Ok(ArgKind::Receiver(ReceiverArg::from(receiver))), }?; Ok(Self { span, kind }) } } -// A unit struct is kind of silly now, but eventually we may want to differentiate between methods -// that input &self vs Arc for ReceiverArg { + fn from(receiver: Receiver) -> Self { + if let Type::Path(p) = *receiver.ty { + if let Some(segment) = p.path.segments.last() { + // This comparison will fail if a user uses a typedef for Arc. Maybe we could + // implement some system like TYPE_ID_META to figure this out from the type system. + // However, this seems good enough for now. + if segment.ident == "Arc" { + return ReceiverArg::Arc; + } + } + } + Self::Ref + } +} pub(crate) struct NamedArg { pub(crate) ident: Ident, pub(crate) name: String, pub(crate) ty: TokenStream, - pub(crate) is_ref: bool, + pub(crate) ref_type: Option, } impl NamedArg { @@ -303,15 +330,15 @@ impl NamedArg { Self { name: ident_to_string(&ident), ident, - ty: quote! { ::std::sync::Arc<#inner> }, - is_ref: true, + ty: quote! { <#inner as ::uniffi::LiftRef>::LiftType }, + ref_type: Some(*inner.clone()), } } _ => Self { name: ident_to_string(&ident), ident, ty: quote! { #ty }, - is_ref: false, + ref_type: None, }, } } @@ -343,17 +370,22 @@ impl NamedArg { } /// Generate the expression to lift the scaffolding parameter for this arg - pub(crate) fn lift_expr(&self) -> TokenStream { + pub(crate) fn lift_expr(&self, return_ffi_converter: &TokenStream) -> TokenStream { let ident = &self.ident; + let ty = &self.ty; let ffi_converter = self.ffi_converter(); - let panic_fmt = format!("Failed to convert arg '{}': {{}}", self.name); + let name = &self.name; let lift = quote! { - #ffi_converter::try_lift(#ident) - .unwrap_or_else(|err| ::std::panic!(#panic_fmt, err)) + match #ffi_converter::try_lift(#ident) { + Ok(v) => v, + Err(e) => return Err(#return_ffi_converter::handle_failed_lift(#name, e)) + } }; - match self.is_ref { - false => lift, - true => quote! { &*#lift }, + match &self.ref_type { + None => lift, + Some(ref_type) => quote! { + <#ty as ::std::borrow::Borrow<#ref_type>>::borrow(&#lift) + }, } } @@ -374,6 +406,20 @@ impl NamedArg { } } +fn looks_like_result(return_type: &ReturnType) -> bool { + if let ReturnType::Type(_, ty) = return_type { + if let Type::Path(p) = &**ty { + if let Some(seg) = p.path.segments.last() { + if seg.ident == "Result" { + return true; + } + } + } + } + + false +} + #[derive(Debug)] pub(crate) enum FnKind { Function, diff --git a/uniffi_macros/src/lib.rs b/uniffi_macros/src/lib.rs index d2236bb93d..4cffddfa0e 100644 --- a/uniffi_macros/src/lib.rs +++ b/uniffi_macros/src/lib.rs @@ -100,18 +100,21 @@ pub fn setup_scaffolding(tokens: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn export(attr_args: TokenStream, input: TokenStream) -> TokenStream { - let input2 = proc_macro2::TokenStream::from(input.clone()); + do_export(attr_args, input, false) +} + +fn do_export(attr_args: TokenStream, input: TokenStream, udl_mode: bool) -> TokenStream { + let copied_input = (!udl_mode).then(|| proc_macro2::TokenStream::from(input.clone())); let gen_output = || { - let mod_path = util::mod_path()?; let args = syn::parse(attr_args)?; let item = syn::parse(input)?; - expand_export(item, args, mod_path) + expand_export(item, args, udl_mode) }; let output = gen_output().unwrap_or_else(syn::Error::into_compile_error); quote! { - #input2 + #copied_input #output } .into() @@ -119,28 +122,28 @@ pub fn export(attr_args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_derive(Record, attributes(uniffi))] pub fn derive_record(input: TokenStream) -> TokenStream { - expand_record(parse_macro_input!(input)).into() + expand_record(parse_macro_input!(input), false) + .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)).into() + expand_enum(parse_macro_input!(input), false) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } #[proc_macro_derive(Object)] pub fn derive_object(input: TokenStream) -> TokenStream { - let mod_path = match util::mod_path() { - Ok(p) => p, - Err(e) => return e.into_compile_error().into(), - }; - let input = parse_macro_input!(input); - - expand_object(input, mod_path).into() + expand_object(parse_macro_input!(input), false) + .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)) + expand_error(parse_macro_input!(input), None, false) .unwrap_or_else(syn::Error::into_compile_error) .into() } @@ -150,13 +153,9 @@ pub fn derive_error(input: TokenStream) -> TokenStream { #[proc_macro] pub fn custom_type(tokens: TokenStream) -> TokenStream { let input: CustomTypeInfo = syn::parse_macro_input!(tokens); - custom::expand_ffi_converter_custom_type( - &input.ident, - &input.builtin, - Some(&syn::parse_quote!(crate::UniFfiTag)), - ) - .unwrap_or_else(syn::Error::into_compile_error) - .into() + custom::expand_ffi_converter_custom_type(&input.ident, &input.builtin, true) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } /// Generate the `FfiConverter` and the `UniffiCustomTypeConverter` implementations for a @@ -165,70 +164,97 @@ pub fn custom_type(tokens: TokenStream) -> TokenStream { #[proc_macro] pub fn custom_newtype(tokens: TokenStream) -> TokenStream { let input: CustomTypeInfo = syn::parse_macro_input!(tokens); - custom::expand_ffi_converter_custom_newtype( - &input.ident, - &input.builtin, - Some(&syn::parse_quote!(crate::UniFfiTag)), - ) - .unwrap_or_else(syn::Error::into_compile_error) - .into() + custom::expand_ffi_converter_custom_newtype(&input.ident, &input.builtin, true) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } -/// Generate the FfiConverter implementation for a Record -/// -/// This is used by the Askama scaffolding code. It this inputs a struct definition, but only -/// outputs the `FfiConverter` implementation, not the struct. +// == derive_for_udl and export_for_udl == +// +// 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 { ... } +// ``` +// +// # Differences between UDL-mode and normal mode +// +// ## Metadata symbols / checksum functions +// +// In UDL mode, we don't export the static metadata symbols or generate the checksum +// functions. This could be changed, but there doesn't seem to be much benefit at this point. +// +// ## The FfiConverter parameter +// +// In UDL-mode, we only implement `FfiConverter` for the local tag (`FfiConverter`) +// +// The reason for this split is remote types, i.e. types defined in remote crates that we +// don't control and therefore can't define a blanket impl on because of the orphan rules. +// +// With UDL, we handle this by only implementing `FfiConverter` for the +// type. This gets around the orphan rules since a local type is in the trait, but requires +// a `uniffi::ffi_converter_forward!` call if the type is used in a second local crate (an +// External typedef). This is natural for UDL-based generation, since you always need to +// define the external type in the UDL file. +// +// 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 ffi_converter_record(attrs: TokenStream, input: TokenStream) -> TokenStream { - record::expand_record_ffi_converter( - syn::parse_macro_input!(attrs), - syn::parse_macro_input!(input), - ) - .into() +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() } -/// Generate the FfiConverter implementation for an Enum -/// -/// This is used by the Askama scaffolding code. It this inputs an enum definition, but only -/// outputs the `FfiConverter` implementation, not the enum. #[doc(hidden)] #[proc_macro_attribute] -pub fn ffi_converter_enum(attrs: TokenStream, input: TokenStream) -> TokenStream { - enum_::expand_enum_ffi_converter( - syn::parse_macro_input!(attrs), - syn::parse_macro_input!(input), - ) - .into() +pub fn derive_enum_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream { + expand_enum(syn::parse_macro_input!(input), true) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } -/// Generate the FfiConverter implementation for an Error enum -/// -/// This is used by the Askama scaffolding code. It this inputs an enum definition, but only -/// outputs the `FfiConverter` implementation, not the enum. #[doc(hidden)] #[proc_macro_attribute] -pub fn ffi_converter_error(attrs: TokenStream, input: TokenStream) -> TokenStream { - error::expand_ffi_converter_error( - syn::parse_macro_input!(attrs), +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, ) + .unwrap_or_else(syn::Error::into_compile_error) .into() } -/// Generate various support elements, including the FfiConverter implementation, -/// for an Interface -/// -/// This is used by the Askama scaffolding code. It this inputs an struct/enum definition, but -/// only outputs the `FfiConverter` implementation, not the item. #[doc(hidden)] #[proc_macro_attribute] -pub fn expand_interface_support(attrs: TokenStream, input: TokenStream) -> TokenStream { - object::expand_interface_support( - syn::parse_macro_input!(attrs), - syn::parse_macro_input!(input), - ) - .into() +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() +} + +#[doc(hidden)] +#[proc_macro_attribute] +pub fn export_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream { + do_export(attrs, input, true) } /// Generate various support elements, including the FfiConverter implementation, @@ -236,11 +262,7 @@ pub fn expand_interface_support(attrs: TokenStream, input: TokenStream) -> Token #[doc(hidden)] #[proc_macro] pub fn expand_trait_interface_support(tokens: TokenStream) -> TokenStream { - export::ffi_converter_trait_impl( - &syn::parse_macro_input!(tokens), - Some(&syn::parse_quote!(crate::UniFfiTag)), - ) - .into() + export::ffi_converter_trait_impl(&syn::parse_macro_input!(tokens), true).into() } /// Generate the FfiConverter implementation for an trait interface for the scaffolding code @@ -248,12 +270,7 @@ pub fn expand_trait_interface_support(tokens: TokenStream) -> TokenStream { #[proc_macro] pub fn scaffolding_ffi_converter_callback_interface(tokens: TokenStream) -> TokenStream { let input: IdentPair = syn::parse_macro_input!(tokens); - export::ffi_converter_callback_interface_impl( - &input.lhs, - &input.rhs, - Some(&syn::parse_quote!(crate::UniFfiTag)), - ) - .into() + export::ffi_converter_callback_interface_impl(&input.lhs, &input.rhs, true).into() } /// A helper macro to include generated component scaffolding. @@ -305,9 +322,7 @@ pub fn include_scaffolding(udl_stem: TokenStream) -> TokenStream { } // Use a UniFFI types from dependent crates that uses UDL files -// -// See [util::CommonAttr] for a discussion of why this is needed. - +// See the derive_for_udl and export_for_udl section for a discussion of why this is needed. #[proc_macro] pub fn use_udl_record(tokens: TokenStream) -> TokenStream { use_udl_simple_type(tokens) diff --git a/uniffi_macros/src/object.rs b/uniffi_macros/src/object.rs index d7f0307c64..ee992eb074 100644 --- a/uniffi_macros/src/object.rs +++ b/uniffi_macros/src/object.rs @@ -1,27 +1,22 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{DeriveInput, Path}; +use syn::DeriveInput; use uniffi_meta::free_fn_symbol_name; -use crate::util::{ - create_metadata_items, ident_to_string, mod_path, tagged_impl_header, ArgumentNotAllowedHere, - AttributeSliceExt, CommonAttr, -}; +use crate::util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header}; -pub fn expand_object(input: DeriveInput, module_path: String) -> TokenStream { +pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result { + let module_path = mod_path()?; let ident = &input.ident; - let attr_error = input - .attrs - .parse_uniffi_attr_args::() - .err() - .map(syn::Error::into_compile_error); let name = ident_to_string(ident); let free_fn_ident = Ident::new(&free_fn_symbol_name(&module_path, &name), Span::call_site()); - let meta_static_var = interface_meta_static_var(ident, false, &module_path) - .unwrap_or_else(syn::Error::into_compile_error); - let interface_impl = interface_impl(ident, None); + let meta_static_var = (!udl_mode).then(|| { + interface_meta_static_var(ident, false, &module_path) + .unwrap_or_else(syn::Error::into_compile_error) + }); + let interface_impl = interface_impl(ident, udl_mode); - quote! { + Ok(quote! { #[doc(hidden)] #[no_mangle] pub extern "C" fn #free_fn_ident( @@ -38,19 +33,15 @@ pub fn expand_object(input: DeriveInput, module_path: String) -> TokenStream { }); } - #attr_error #interface_impl #meta_static_var - } -} - -pub(crate) fn expand_interface_support(attr: CommonAttr, input: DeriveInput) -> TokenStream { - interface_impl(&input.ident, attr.tag.as_ref()) + }) } -pub(crate) fn interface_impl(ident: &Ident, tag: Option<&Path>) -> TokenStream { +pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { let name = ident_to_string(ident); - let impl_spec = tagged_impl_header("FfiConverterArc", ident, tag); + let impl_spec = tagged_impl_header("FfiConverterArc", ident, udl_mode); + let lift_ref_impl_spec = tagged_impl_header("LiftRef", ident, udl_mode); let mod_path = match mod_path() { Ok(p) => p, Err(e) => return e.into_compile_error(), @@ -144,6 +135,10 @@ pub(crate) fn interface_impl(ident: &Ident, tag: Option<&Path>) -> TokenStream { .concat_str(#name) .concat_bool(false); } + + #lift_ref_impl_spec { + type LiftType = ::std::sync::Arc; + } } } diff --git a/uniffi_macros/src/record.rs b/uniffi_macros/src/record.rs index fea6b599de..aa7fa1eebc 100644 --- a/uniffi_macros/src/record.rs +++ b/uniffi_macros/src/record.rs @@ -2,63 +2,45 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, - Data, DataStruct, DeriveInput, Field, Lit, Path, Token, + Data, DataStruct, DeriveInput, Field, Lit, Token, }; use crate::util::{ - create_metadata_items, either_attribute_arg, ident_to_string, mod_path, tagged_impl_header, - try_metadata_value_from_usize, try_read_field, ArgumentNotAllowedHere, AttributeSliceExt, - CommonAttr, UniffiAttributeArgs, + create_metadata_items, either_attribute_arg, ident_to_string, kw, mod_path, tagged_impl_header, + try_metadata_value_from_usize, try_read_field, AttributeSliceExt, UniffiAttributeArgs, }; -pub fn expand_record(input: DeriveInput) -> TokenStream { +pub fn expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result { let record = match input.data { Data::Struct(s) => s, _ => { - return syn::Error::new( + return Err(syn::Error::new( Span::call_site(), "This derive must only be used on structs", - ) - .into_compile_error(); + )); } }; let ident = &input.ident; - let attr_error = input - .attrs - .parse_uniffi_attr_args::() - .err() - .map(syn::Error::into_compile_error); - let ffi_converter = record_ffi_converter_impl(ident, &record, None) + let ffi_converter = record_ffi_converter_impl(ident, &record, udl_mode) .unwrap_or_else(syn::Error::into_compile_error); - let meta_static_var = - record_meta_static_var(ident, &record).unwrap_or_else(syn::Error::into_compile_error); + let meta_static_var = (!udl_mode).then(|| { + record_meta_static_var(ident, &record).unwrap_or_else(syn::Error::into_compile_error) + }); - quote! { - #attr_error + Ok(quote! { #ffi_converter #meta_static_var - } -} - -pub(crate) fn expand_record_ffi_converter(attr: CommonAttr, input: DeriveInput) -> TokenStream { - match input.data { - Data::Struct(s) => record_ffi_converter_impl(&input.ident, &s, attr.tag.as_ref()) - .unwrap_or_else(syn::Error::into_compile_error), - _ => syn::Error::new( - proc_macro2::Span::call_site(), - "This attribute must only be used on structs", - ) - .into_compile_error(), - } + }) } pub(crate) fn record_ffi_converter_impl( ident: &Ident, record: &DataStruct, - tag: Option<&Path>, + udl_mode: bool, ) -> syn::Result { - let impl_spec = tagged_impl_header("FfiConverter", ident, tag); + let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode); + let lift_ref_impl_spec = tagged_impl_header("LiftRef", ident, udl_mode); let name = ident_to_string(ident); let mod_path = mod_path()?; let write_impl: TokenStream = record.fields.iter().map(write_field).collect(); @@ -82,6 +64,10 @@ pub(crate) fn record_ffi_converter_impl( .concat_str(#mod_path) .concat_str(#name); } + + #lift_ref_impl_spec { + type LiftType = Self; + } }) } @@ -94,11 +80,6 @@ fn write_field(f: &Field) -> TokenStream { } } -mod kw { - syn::custom_keyword!(default); - syn::custom_keyword!(None); -} - pub enum FieldDefault { Literal(Lit), Null(kw::None), diff --git a/uniffi_macros/src/util.rs b/uniffi_macros/src/util.rs index e3102e0b5e..b4a5a96108 100644 --- a/uniffi_macros/src/util.rs +++ b/uniffi_macros/src/util.rs @@ -8,7 +8,7 @@ use std::path::{Path as StdPath, PathBuf}; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, - Attribute, Path, Token, + Attribute, Token, }; pub fn manifest_path() -> Result { @@ -206,70 +206,26 @@ pub fn either_attribute_arg(a: Option, b: Option) -> syn::Res pub(crate) fn tagged_impl_header( trait_name: &str, ident: &impl ToTokens, - tag: Option<&Path>, + udl_mode: bool, ) -> TokenStream { let trait_name = Ident::new(trait_name, Span::call_site()); - match tag { - Some(tag) => quote! { impl ::uniffi::#trait_name<#tag> for #ident }, - None => quote! { impl ::uniffi::#trait_name for #ident }, - } -} - -mod kw { - syn::custom_keyword!(tag); -} - -#[derive(Default)] -pub(crate) struct CommonAttr { - /// Specifies the `UniFfiTag` used when implementing `FfiConverter` - /// - When things are defined with proc-macros, this is `None` which means create a blanket - /// impl for all types. - /// - When things are defined with UDL files this is `Some(crate::UniFfiTag)`, which means only - /// implement it for the local tag in the crate - /// - /// The reason for this split is remote types, i.e. types defined in remote crates that we - /// don't control and therefore can't define a blanket impl on because of the orphan rules. - /// - /// With UDL, we handle this by only implementing `FfiConverter` for the - /// type. This gets around the orphan rules since a local type is in the trait, but requires - /// a `uniffi::ffi_converter_forward!` call if the type is used in a second local crate (an - /// External typedef). This is natural for UDL-based generation, since you always need to - /// define the external type in the UDL file. - /// - /// With proc-macros this system isn't so natural. Instead, we plan to use this system: - /// - Most of the time, types aren't remote and we use the blanket impl. - /// - When types are remote, we'll need a special syntax to define an `FfiConverter` for them - /// and also a special declaration to use the types in other crates. This requires some - /// extra work for the consumer, but it should be rare. - pub tag: Option, -} - -impl UniffiAttributeArgs for CommonAttr { - fn parse_one(input: ParseStream<'_>) -> syn::Result { - let lookahead = input.lookahead1(); - if lookahead.peek(kw::tag) { - let _: kw::tag = input.parse()?; - let _: Token![=] = input.parse()?; - Ok(Self { - tag: Some(input.parse()?), - }) - } else { - Err(lookahead.error()) - } - } - - fn merge(self, other: Self) -> syn::Result { - Ok(Self { - tag: either_attribute_arg(self.tag, other.tag)?, - }) - } -} - -// So CommonAttr can be used with `parse_macro_input!` -impl Parse for CommonAttr { - fn parse(input: ParseStream<'_>) -> syn::Result { - parse_comma_separated(input) - } + if udl_mode { + quote! { impl ::uniffi::#trait_name for #ident } + } else { + quote! { impl ::uniffi::#trait_name for #ident } + } +} + +/// Custom keywords +pub mod kw { + syn::custom_keyword!(async_runtime); + syn::custom_keyword!(callback_interface); + syn::custom_keyword!(constructor); + syn::custom_keyword!(default); + syn::custom_keyword!(flat_error); + syn::custom_keyword!(handle_unknown_callback_error); + syn::custom_keyword!(None); + syn::custom_keyword!(with_try_read); } /// Specifies a type from a dependent crate