Skip to content

Commit

Permalink
Use proc-macros for all exports
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bendk committed Sep 20, 2023
1 parent eb8ab92 commit e10a7f9
Show file tree
Hide file tree
Showing 42 changed files with 726 additions and 561 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 38 additions & 19 deletions docs/manual/src/proc_macro/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self>`
fn some_method(&self) {
...
}
}

// Input foo as an Arc and bar as a reference
fn call_both(foo: Arc<Foo>, 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

Expand Down Expand Up @@ -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<Foo>, 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
Expand Down
2 changes: 2 additions & 0 deletions fixtures/coverall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down
1 change: 1 addition & 0 deletions fixtures/futures/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
6 changes: 4 additions & 2 deletions fixtures/futures/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down Expand Up @@ -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,
}

Expand Down
1 change: 1 addition & 0 deletions fixtures/metadata/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
33 changes: 7 additions & 26 deletions fixtures/metadata/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 9 additions & 1 deletion fixtures/proc-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 },
}

Expand Down
1 change: 1 addition & 0 deletions fixtures/proc-macro/tests/bindings/test_proc_macro.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions fixtures/proc-macro/tests/bindings/test_proc_macro.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions fixtures/proc-macro/tests/bindings/test_proc_macro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions fixtures/uitests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
<Self as std::fmt::Debug>::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<uniffi::UnexpectedUniFFICallbackError> for ArithmeticError {
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
10 changes: 5 additions & 5 deletions fixtures/uitests/tests/ui/interface_cannot_use_mut_self.stderr
Original file line number Diff line number Diff line change
@@ -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<Counter>`
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<Counter>`
= note: this error originates in the attribute macro `::uniffi::export_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
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`
--> $WORKSPACE/uniffi_core/src/ffi_converter_traits.rs
|
| pub unsafe trait FfiConverterArc<UT>: 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`
--> $WORKSPACE/uniffi_core/src/ffi_converter_traits.rs
|
| pub unsafe trait FfiConverterArc<UT>: 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
Expand Down
Loading

0 comments on commit e10a7f9

Please sign in to comment.