diff --git a/CHANGELOG.md b/CHANGELOG.md index ec4c2537b8..8af6ae6cfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,16 @@ No consumers of any languages are impacted, only the maintainers of these language bindings. ([#2066](https://github.com/mozilla/uniffi-rs/issues/2066)), ([#2094](https://github.com/mozilla/uniffi-rs/pull/2094)) +### ⚠️ Breaking Changes ⚠️ + +- Handling of remote/custom/external types has changed significantly: + - UDL users need to add the `[Remote]` attribute for remote types + - The `UniffiCustomTypeConverter` trait is no longer used. Instead, use the `custom_type!` macro. + - The `use_udl_*` macros are no longer needed and have been removed. + - To share remote type implementations between crates, use the `use_remote_type` macro. + - The UDL syntax for external types in the UDL has been changed. + - See https://mozilla.github.io/uniffi-rs/udl/remote_custom_ext_types.html for details + - The async runtime can be specified for constructors/methods, this will override the runtime specified at the impl block level. - Removed the dependency on the `oneshot' crate (https://github.com/mozilla/uniffi-rs/issues/1736) diff --git a/Cargo.lock b/Cargo.lock index c614e4389d..fd79812e71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1551,6 +1551,15 @@ dependencies = [ "uniffi", ] +[[package]] +name = "uniffi-example-remote-types" +version = "0.22.0" +dependencies = [ + "anyhow", + "log", + "uniffi", +] + [[package]] name = "uniffi-example-rondpoint" version = "0.22.0" @@ -1831,13 +1840,6 @@ dependencies = [ "uniffi", ] -[[package]] -name = "uniffi-fixture-regression-nested-module-import" -version = "0.22.0" -dependencies = [ - "uniffi", -] - [[package]] name = "uniffi-fixture-regression-swift-dictionary-nesting" version = "0.22.0" diff --git a/Cargo.toml b/Cargo.toml index b382b243a8..7c0536d006 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "examples/custom-types", "examples/futures", "examples/geometry", + "examples/remote-types", "examples/rondpoint", "examples/sprites", "examples/todolist", @@ -52,7 +53,6 @@ members = [ "fixtures/regressions/swift-callbacks-omit-labels", "fixtures/regressions/swift-dictionary-nesting", "fixtures/regressions/unary-result-alias", - "fixtures/regressions/nested-module-import", "fixtures/regressions/wrong-lower-check", "fixtures/trait-methods", "fixtures/uitests", diff --git a/docs/manual/src/internals/lifting_and_lowering.md b/docs/manual/src/internals/lifting_and_lowering.md index 3057b0c2a9..0d387e94ee 100644 --- a/docs/manual/src/internals/lifting_and_lowering.md +++ b/docs/manual/src/internals/lifting_and_lowering.md @@ -108,9 +108,10 @@ To work around this we do several things: - We generate a unit struct named `UniFfiTag` in the root of each UniFFIed crate. - Each crate uses the `FfiConverter` trait to lower/lift/serialize values for its scaffolding functions. -This allows us to work around the orphan rules when defining `FfiConverter` implementations. - - UniFFI consumer crates can implement lifting/lowering/serializing types for their own scaffolding functions, for example `impl FfiConverter for serde_json::Value`. This is allowed since `UniFfiTag` is a local type. +This allows us to work around the orphan rules when defining ffi trait implementations. - The `uniffi` crate can implement lifting/lowering/serializing types for all scaffolding functions using a generic impl, for example `impl FfiConverter for u8`. "UT" is short for "UniFFI Tag" - - We don't currently use this, but crates can also implement lifting/lowering/serializing their local types for all scaffolding functions using a similar generic impl (`impl FfiConverter for MyLocalType`). + - UniFFI consumer crates usually implement lifting/lowering/serializing types the same way. + - However, for remote types, they must only implement ffi traits for their local tag, for example `impl FfiConverter for serde_json::Value`. This is valid since `UniFfiTag` is a local type. + - If other crates also want to use the same remote type implementation, the need to implement the ffi traits for their local tag as well. This is what the `use_remote_type!` macro does. For more details on the specifics of the "orphan rule" and why these are legal implementations, see the [Rust Chalk Book](https://rust-lang.github.io/chalk/book/clauses/coherence.html#the-orphan-rules-in-rustc) diff --git a/docs/manual/src/kotlin/configuration.md b/docs/manual/src/kotlin/configuration.md index 19ae2a745b..f87e1f5aff 100644 --- a/docs/manual/src/kotlin/configuration.md +++ b/docs/manual/src/kotlin/configuration.md @@ -10,7 +10,7 @@ The generated Kotlin modules can be configured using a `uniffi.toml` configurati | `cdylib_name` | `uniffi_{namespace}`[^1] | The name of the compiled Rust library containing the FFI implementation (not needed when using `generate --library`). | | `generate_immutable_records` | `false` | Whether to generate records with immutable fields (`val` instead of `var`). | | `custom_types` | | A map which controls how custom types are exposed to Kotlin. See the [custom types section of the manual](../udl/custom_types.md#custom-types-in-the-bindings-code)| -| `external_packages` | | A map of packages to be used for the specified external crates. The key is the Rust crate name, the value is the Kotlin package which will be used referring to types in that crate. See the [external types section of the manual](../udl/ext_types_external.md#kotlin) +| `external_packages` | | A map of packages to be used for the specified external crates. The key is the Rust crate name, the value is the Kotlin package which will be used referring to types in that crate. See the [external types section of the manual](../udl/remote_ext_types.md#kotlin) | `android` | `false` | Used to toggle on Android specific optimizations | `android_cleaner` | `android` | Use the [`android.system.SystemCleaner`](https://developer.android.com/reference/android/system/SystemCleaner) instead of [`java.lang.ref.Cleaner`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ref/Cleaner.html). Fallback in both instances is the one shipped with JNA. diff --git a/docs/manual/src/proc_macro/index.md b/docs/manual/src/proc_macro/index.md index 0fa830456c..746dd7a630 100644 --- a/docs/manual/src/proc_macro/index.md +++ b/docs/manual/src/proc_macro/index.md @@ -354,44 +354,8 @@ impl Foo { ## 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 -[UDL documentation for Custom Types](../udl/custom_types.md) - -The `uniffi::custom_type!` macro requires you to specify the name of the custom type, and the name of the -builtin which implements this type. Use of this macro requires you to manually implement the -`UniffiCustomTypeConverter` trait for for your type, as shown below. -```rust -pub struct Uuid { - val: String, -} - -// Use `Uuid` as a custom type, with `String` as the Builtin -uniffi::custom_type!(Uuid, String); - -impl UniffiCustomTypeConverter for Uuid { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(Uuid { val }) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.val - } -} -``` - -There's also a `uniffi::custom_newtype!` macro, designed for custom types which use the -"new type" idiom. You still need to specify the type name and builtin type, but because UniFFI -is able to make assumptions about how the type is laid out, `UniffiCustomTypeConverter` -is implemented automatically. - -```rust -uniffi::custom_newtype!(NewTypeHandle, i64); -pub struct NewtypeHandle(i64); -``` - -and that's it! +See the [UDL documentation for Custom Types](../udl/custom_types.md). It works exactly the same for +proc-macros. ## The `uniffi::Error` derive @@ -463,32 +427,7 @@ pub trait Person { // } ``` -## Types from dependent crates - -When using proc-macros, you can use types from dependent crates in your exported library, as long as -the dependent crate annotates the type with one of the UniFFI derives. However, there are a couple -exceptions: - -### Types from UDL-based dependent crates - -If the dependent crate uses a UDL file to define their types, then you must invoke one of the -`uniffi::use_udl_*!` macros, for example: - -```rust -uniffi::use_udl_record!(dependent_crate, RecordType); -uniffi::use_udl_enum!(dependent_crate, EnumType); -uniffi::use_udl_error!(dependent_crate, ErrorType); -uniffi::use_udl_object!(dependent_crate, ObjectType); -``` - -### Non-UniFFI types from dependent crates - -If the dependent crate doesn't define the type in a UDL file or use one of the UniFFI derive macros, -then it's currently not possible to use them in an proc-macro exported interface. However, we hope -to fix this limitation soon. - -## Other limitations +## Mixing UDL and proc-macros -In addition to the per-item limitations of the macros presented above, there is also currently a -global restriction: You can only use the proc-macros inside a crate whose name is the same as the -namespace in your UDL file. This restriction will be lifted in the future. +If you use both UDL and proc-macro generation, then your crate name must match the namespace in your +UDL file. This restriction will be lifted in the future. diff --git a/docs/manual/src/udl/custom_types.md b/docs/manual/src/udl/custom_types.md index 57e93936ad..8b837ce0e0 100644 --- a/docs/manual/src/udl/custom_types.md +++ b/docs/manual/src/udl/custom_types.md @@ -1,72 +1,95 @@ # Custom types -Custom types allow you to extend the UniFFI type system to support types from your Rust crate or 3rd -party libraries. This works by converting to and from some other UniFFI type to move data across the -FFI. You must provide a `UniffiCustomTypeConverter` implementation to convert the types. +Custom types allow you to extend the UniFFI type system by converting to and from some other UniFFI +type to move data across the FFI. ## Custom types in the scaffolding code -Consider the following trivial Rust abstraction for a "handle" which wraps an integer: +### custom_type! + +Use the `custom_type!` macro to define a new custom type. ```rust -pub struct Handle(i64); + +// Some complex struct that can be serialized/deserialized to a string. +use some_mod::SerializableStruct; + +/// `SerializableStruct` objects will be passed across the FFI the same way `String` values are. +uniffi::custom_type!(SerializableStruct, String); ``` -In this trivial example, the simplest way to expose this is with a macro. +By default: + + - Values passed to the foreign code will be converted using `>` before being lowered as a `String`. + - Values passed to the Rust code will be converted using `>` after lifted as a `String`. + - The `TryInto::Error` type can be anything that implements `Into`. + - `>` will also work, since there is a blanket impl in the core libary. +### custom_type! with manual conversions + +You can also manually specify the conversions by passing an extra param to the macro: + +```rust +uniffi::custom_type!(SerializableStruct, String, { + from_custom: |s| s.serialize(), + try_into_custom: |s| s.deserialize(), +}); ``` + +### custom_newtype! + +The `custom_newtype!` can trivially handle newtypes that wrap a UniFFI type. + +```rust +/// handle which wraps an integer +pub struct Handle(i64); + +/// `Handle` objects will be passed across the FFI the same way `i64` values are. uniffi::custom_newtype!(Handle, i64); ``` -Or you can define this in UDL via a `typedef` with a `Custom` attribute, -defining the builtin type that it's based on. +### UDL + +Define custom types in UDL via a `typedef` with a `Custom` attribute, specifying the UniFFI type +followed by the custom type. ```idl [Custom] typedef i64 Handle; ``` -For this to work, your Rust code must also implement a special trait named -`UniffiCustomTypeConverter`. - -An implementation is provided if you used the `uniffi::custom_newtype!()` macro. -But if you use UDL or otherwise need to implement your own: +**note**: UDL users still need to call the `custom_type!` or `custom_newtype!` macro in their Rust +code. -This trait is generated by UniFFI and can be found in the generated -Rust scaffolding - it is defined as: +## User-defined types -```Rust -trait UniffiCustomTypeConverter { - type Builtin; +All examples so far in this section convert the custom type to a builtin type. +It's also possible to convert them to a user-defined type (Record, Enum, interface, etc.). +For example you might want to convert `log::Record` class into a UniFFI record: - fn into_custom(val: Self::Builtin) -> uniffi::Result - where - Self: Sized; - fn from_custom(obj: Self) -> Self::Builtin; -} -``` +```rust -where `Builtin` is the Rust type corresponding to the UniFFI builtin-type - `i64` in the example above. Thus, the trait -implementation for `Handle` would look something like: +pub type LogRecord = log::Record; -```rust -impl UniffiCustomTypeConverter for Handle { - type Builtin = i64; +#[derive(uniffi::Record)] +pub type LogRecordData { + level: LogLevel, + message: String, +} - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(Handle(val)) +uniffi::custom_type!(LogRecord, LogRecordData, { + from_custom: |r| LogRecordData { + level: r.level(), + message: r.to_string(), } + try_into_custom: |r| LogRecord::builder() + .level(r.level) + .args(format_args!("{}", r.message)) + .build() +}); - fn from_custom(obj: Self) -> Self::Builtin { - obj.0 - } -} ``` -Because `UniffiCustomTypeConverter` is defined in each crate, this means you can use custom types even -if they are not defined in your crate - see the 'custom_types' example which demonstrates -`url::Url` as a custom type. - ## Error handling during conversion You might have noticed that the `into_custom` function returns a `uniffi::Result` (which is an @@ -112,20 +135,14 @@ pub enum ExampleError { InvalidHandle, } -impl UniffiCustomTypeConverter for ExampleHandle { - type Builtin = i64; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - if val == 0 { - Err(ExampleErrors::InvalidHandle.into()) - } else if val == -1 { - Err(SomeOtherError.into()) // SomeOtherError decl. not shown. - } else { - Ok(Handle(val)) - } +uniffi::custom_type!(ExampleHandle, Builtin, { + from_custom: |handle| handle.0, + try_into_custom: |value| match value { + 0 => Err(ExampleErrors::InvalidHandle.into()), + -1 => Err(SomeOtherError.into()), // SomeOtherError decl. not shown. + n => Ok(Handle(n)), } - // ... -} +}) ``` The behavior of the generated scaffolding will be: @@ -168,12 +185,32 @@ Here's how the configuration works in `uniffi.toml`. * `from_custom`: Expression to convert the custom type to the UDL type. `{}` will be replaced with the value of the custom type. * `imports` (Optional) list of modules to import for your `into_custom`/`from_custom` functions. -## Using Custom Types from other crates +## Using custom types from other crates -To use the `Handle` example above from another crate, these other crates just refer to the type -as a regular `External` type - for example, another crate might use `udl` such as: +To use custom types from other crates, use a typedef wrapped with the `[External]` attribute. +For example, if another crate wanted to use the examples above: ```idl [External="crate_defining_handle_name"] -typedef extern Handle; +typedef i64 Handle; + +[External="crate_defining_log_record_name"] +typedef dictionary LogRecord; ``` + +## Remote custom types + +Custom types that convert [Remote types](./remote_ext_types.md#remote-types) defined in other crates require special handling. + +1) Specify `remote` param in the `custom_type!` macro: + +```rust + +uniffi::custom_type!(SerializableStructFromOtherCrate, String, { + remote, + from_custom: |s| s.serialize(), + try_into_custom: |s| s.deserialize(), +}); +``` + +2) To share the custom type implementation with other crates, use the [remote_type! macro](./remote_ext_types.md#external+remote-types). diff --git a/docs/manual/src/udl/ext_types.md b/docs/manual/src/udl/ext_types.md deleted file mode 100644 index 4ad4139c5e..0000000000 --- a/docs/manual/src/udl/ext_types.md +++ /dev/null @@ -1,42 +0,0 @@ -# External types - -External types are types implemented by UniFFI but outside of this UDL file. - -They are similar to, but different from [custom types](./custom_types.md) which wrap UniFFI primitive types. - -But like custom types, external types are all declared using a `typedef` with attributes -giving more detail. - -## Types in another crate - -[There's a whole page about that!](./ext_types_external.md) - -## Types from procmacros in this crate. - -If your crate has types defined via `#[uniffi::export]` etc you can make them available -to the UDL file in your own crate via a `typedef` with a `[Rust=]` attribute. Eg, your Rust -might have: - -```rust -#[derive(uniffi::Record)] -pub struct One { - inner: i32, -} -``` -you can use it in your UDL: - -```idl -[Rust="record"] -typedef extern One; - -namespace app { - // use the procmacro type. - One get_one(One? one); -} - -``` - -Supported values: -* "enum", "trait", "callback", "trait_with_foreign" -* For records, either "record" or "dictionary" -* For objects, either "object" or "interface" diff --git a/docs/manual/src/udl/ext_types_external.md b/docs/manual/src/udl/ext_types_external.md deleted file mode 100644 index 7484323a68..0000000000 --- a/docs/manual/src/udl/ext_types_external.md +++ /dev/null @@ -1,90 +0,0 @@ -# Declaring External Types - -It is possible to use types defined by UniFFI in an external crate. For example, let's assume -that you have an existing crate named `demo_crate` with the following UDL: - -```idl -dictionary DemoDict { - string string_val; - boolean bool_val; -}; -``` - -Inside another crate, `consuming_crate`, you'd like to use this dictionary. -Inside `consuming_crate`'s UDL file you can reference `DemoDict` by using a -`typedef` with an `External` attribute, as shown below. - -```idl -[External="demo_crate"] -typedef extern DemoDict; - -// Now define our own dictionary which references the imported type. -dictionary ConsumingDict { - DemoDict demo_dict; - boolean another_bool; -}; - -``` - -Inside `consuming_crate`'s Rust code you must `use` that struct as normal - for example, -`consuming_crate`'s `lib.rs` might look like: - -```rust -use demo_crate::DemoDict; - -pub struct ConsumingDict { - demo_dict: DemoDict, - another_bool: bool, -} - -uniffi::include_scaffolding!("consuming_crate"); -``` - -Your `Cargo.toml` must reference the external crate as normal. - -The `External` attribute can be specified on dictionaries, enums, errors. - -## External interface and trait types - -If the external type is an [Interface](./interfaces.md), then use the `[ExternalInterface]` attribute instead of `[External]`: - -```idl -[ExternalInterface="demo_crate"] -typedef extern DemoInterface; -``` - -similarly for traits: use `[ExternalTrait]`. - -## External procmacro types - -The above examples assume the external types were defined via UDL. -If they were defined by procmacros, you need different attribute names: - -- if `DemoDict` is implemented by a procmacro in `demo_crate`, you'd use `[ExternalExport=...]` -- for `DemoInterface` you'd use `[ExternalInterfaceExport=...]` - -For types defined by procmacros in _this_ crate, see the [attribute `[Rust=...]`](./ext_types.md) - -## Foreign bindings - -The foreign bindings will also need to know how to access the external type, -which varies slightly for each language: - -### Kotlin - -For Kotlin, "library mode" generation with `generate --library [path-to-cdylib]` is recommended when using external types. -If you use `generate [udl-path]` then the generated code needs to know how to import -the external types from the Kotlin module that corresponds to the Rust crate. -By default, UniFFI assumes that the Kotlin module name matches the Rust crate name, but this can be configured in `uniffi.toml` with an entry like this: - -``` -[bindings.kotlin.external_packages] -# Map the crate names from [External={name}] into Kotlin package names -rust-crate-name = "kotlin.package.name" -``` - -### Swift - -For Swift, you must compile all generated `.swift` files together in a single -module since the generate code expects that it can access external types -without importing them. diff --git a/docs/manual/src/udl/remote_ext_types.md b/docs/manual/src/udl/remote_ext_types.md new file mode 100644 index 0000000000..e01423bb68 --- /dev/null +++ b/docs/manual/src/udl/remote_ext_types.md @@ -0,0 +1,119 @@ +# Remote, Custom and External types + +Remote, custom, and external types can help solve some advanced use-cases when using UniFFI. +They are grouped this section, since they're often used together. + +# Remote types + +Rust's [orphan rule](https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits) places restrictions on implementing traits for types defined in other crates. +Because of that, Remote types require extra handling to use them in UniFFI APIs. + +- See https://github.com/mozilla/uniffi-rs/tree/main/examples/log-formatter for example code. + +## Proc-macros + +```rust + +// Type aliases can be used to give remote types nicer names when exposed in the UniFFI api. +type LogLevel = log::Level; + +// Wrap the definition of the type with `[uniffi::remote()]`. +// +// - The definiton should match the definition on the remote side. +// - UniFFI will generate the FFI scaffolding code for the item, but will not output the item itself +// (since the real item is defined in the remote crate). +// - `` can be any parameter that's valid for `uniffi::derive()`. +#[uniffi::remote(Enum)] +enum LogLevel { + Error, + Warn, + Info, + Debug, + "Trace", +} +``` + +## UDL + +Wrap the definition with `[Remote]` attribute: + +```idl +[Remote] +enum LogLevel { + "Error", + "Warn", + "Info", + "Debug", + "Trace", +}; +``` + +# External Types + +It is possible to use types defined by UniFFI in an external crate. For example, let's assume +that you have an existing crate with the following UDL: + +```idl +dictionary DemoDict { + string string_val; + boolean bool_val; +}; + +namespace demo_crate { + ... +}; +``` + +Inside another crate, `consuming_crate`, you'd like to use this dictionary. +Inside `consuming_crate`'s UDL file you can reference `DemoDict` by using the +`[External=]` attribute to wrap an empty definition. + +```idl +[External="demo_crate"] +dictionary DemoDict { } + +// Now define our own dictionary which references the imported type. +dictionary ConsumingDict { + DemoDict demo_dict; + boolean another_bool; +}; +``` + +## External + Remote types + +If a type is both External and Remote, then it requires some special handling to work around Rust's +orphan rules. Call the `use_remote_type!` macro to handle this. `use_remote_type!` works with both +UDL and proc-macro based generation. + +```rust +uniffi::use_remote_type!(RemoteType, crate_with_the_remote_type_implementation); +``` + +## Proc-macros + +Proc-macros do not need to take any special action to use external types (other than the external + +remote) case above. + +## Foreign bindings + +The foreign bindings will also need to know how to access the external type, +which varies slightly for each language: + +### Kotlin + +For Kotlin, "library mode" generation with `generate --library [path-to-cdylib]` is recommended when using external types. +If you use `generate [udl-path]` then the generated code needs to know how to import +the external types from the Kotlin module that corresponds to the Rust crate. +By default, UniFFI assumes that the Kotlin module name matches the Rust crate name, but this can be configured in `uniffi.toml` with an entry like this: + +``` +[bindings.kotlin.external_packages] +# Map the crate names from [External={name}] into Kotlin package names +rust-crate-name = "kotlin.package.name" +``` + +### Swift + +For Swift, you must compile all generated `.swift` files together in a single +module since the generate code expects that it can access external types +without importing them. diff --git a/examples/custom-types/src/lib.rs b/examples/custom-types/src/lib.rs index 55c502cc5a..821fab746c 100644 --- a/examples/custom-types/src/lib.rs +++ b/examples/custom-types/src/lib.rs @@ -1,98 +1,72 @@ use url::Url; // A custom guid defined via a proc-macro (ie, not referenced in the UDL) -// By far the easiest way to define custom types. pub struct ExampleCustomType(String); + +// custom_newtype! is the easiest way to define custom types. uniffi::custom_newtype!(ExampleCustomType, String); // Custom Handle type which trivially wraps an i64. pub struct Handle(pub i64); -// Custom TimeIntervalMs type which trivially wraps an i64. -pub struct TimeIntervalMs(pub i64); - -// Custom TimeIntervalSecDbl type which trivially wraps an f64. -pub struct TimeIntervalSecDbl(pub f64); +// This one could also use custom_newtype!, but let's use Into and TryFrom instead +uniffi::custom_type!(Handle, i64); -// Custom TimeIntervalSecFlt type which trivially wraps an f32. -pub struct TimeIntervalSecFlt(pub f32); +// Defining `From for i64` also gives us `Into for Handle` +impl From for i64 { + fn from(val: Handle) -> Self { + val.0 + } +} -// We must implement the UniffiCustomTypeConverter trait for each custom type on the scaffolding side -impl UniffiCustomTypeConverter for Handle { - // The `Builtin` type will be used to marshall values across the FFI - type Builtin = i64; +impl TryFrom for Handle { + type Error = std::convert::Infallible; - // Convert Builtin to our custom type - fn into_custom(val: Self::Builtin) -> uniffi::Result { + fn try_from(val: i64) -> Result { Ok(Handle(val)) } - - // Convert our custom type to Builtin - fn from_custom(obj: Self) -> Self::Builtin { - obj.0 - } } -// Use `url::Url` as a custom type, with `String` as the Builtin -impl UniffiCustomTypeConverter for Url { - type Builtin = String; +// Custom TimeIntervalMs type which trivially wraps an i64. +pub struct TimeIntervalMs(pub i64); - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(Url::parse(&val)?) - } +// Another custom type, this time we will define an infallible conversion back to Rust. +uniffi::custom_type!(TimeIntervalMs, i64); - fn from_custom(obj: Self) -> Self::Builtin { - obj.into() +impl From for i64 { + fn from(val: TimeIntervalMs) -> Self { + val.0 } } - -// We must implement the UniffiCustomTypeConverter trait for each custom type on the scaffolding side -impl UniffiCustomTypeConverter for TimeIntervalMs { - // The `Builtin` type will be used to marshall values across the FFI - type Builtin = i64; - - // Convert Builtin to our custom type - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(TimeIntervalMs(val)) - } - - // Convert our custom type to Builtin - fn from_custom(obj: Self) -> Self::Builtin { - obj.0 +// Defining `From for Handle` also gives us `Into for i64` +impl From for TimeIntervalMs { + fn from(val: i64) -> TimeIntervalMs { + TimeIntervalMs(val) } } -// We must implement the UniffiCustomTypeConverter trait for each custom type on the scaffolding side -impl UniffiCustomTypeConverter for TimeIntervalSecDbl { - // The `Builtin` type will be used to marshall values across the FFI - type Builtin = f64; +// Custom TimeIntervalSecDbl type which trivially wraps an f64. +pub struct TimeIntervalSecDbl(pub f64); - // Convert Builtin to our custom type - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(TimeIntervalSecDbl(val)) - } +// custom_type! can take an additional parameter with closures to control the conversions +uniffi::custom_type!(TimeIntervalSecDbl, f64, { + from_custom: |time_interval| time_interval.0, + try_into_custom: |val| Ok(TimeIntervalSecDbl(val)), +}); - // Convert our custom type to Builtin - fn from_custom(obj: Self) -> Self::Builtin { - obj.0 - } -} +// Custom TimeIntervalSecFlt type which trivially wraps an f32. +pub struct TimeIntervalSecFlt(pub f32); -// We must implement the UniffiCustomTypeConverter trait for each custom type on the scaffolding side -impl UniffiCustomTypeConverter for TimeIntervalSecFlt { - // The `Builtin` type will be used to marshall values across the FFI - type Builtin = f32; +// Let's go back to custom_newtype for this one. +uniffi::custom_newtype!(TimeIntervalSecFlt, f32); - // Convert Builtin to our custom type - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(TimeIntervalSecFlt(val)) - } - - // Convert our custom type to Builtin - fn from_custom(obj: Self) -> Self::Builtin { - obj.0 - } -} +// `Url` gets converted to a `String` to pass across the FFI. +// Use the `remote` param when types are defined in a different crate +uniffi::custom_type!(Url, String, { + try_into_custom: |val| Ok(Url::parse(&val)?), + from_custom: |obj| obj.into(), + remote, +}); // And a little struct and function that ties them together. pub struct CustomTypesDemo { diff --git a/fixtures/regressions/nested-module-import/Cargo.toml b/examples/remote-types/Cargo.toml similarity index 59% rename from fixtures/regressions/nested-module-import/Cargo.toml rename to examples/remote-types/Cargo.toml index 44552b4192..ae18af554e 100644 --- a/fixtures/regressions/nested-module-import/Cargo.toml +++ b/examples/remote-types/Cargo.toml @@ -1,17 +1,21 @@ [package] -name = "uniffi-fixture-regression-nested-module-import" +name = "uniffi-example-remote-types" edition = "2021" version = "0.22.0" -authors = ["Firefox Sync Team "] license = "MPL-2.0" publish = false [lib] crate-type = ["lib", "cdylib"] -name = "uniffi_regression_test_nested_module_import" +name = "remote_types" [dependencies] +anyhow = "1" +log = "0.4" uniffi = { workspace = true } [build-dependencies] uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } diff --git a/examples/remote-types/README.md b/examples/remote-types/README.md new file mode 100644 index 0000000000..eb1bb2e61e --- /dev/null +++ b/examples/remote-types/README.md @@ -0,0 +1,9 @@ +# Remote types example + +This crate uses 2 types from remote crates: `log::Level` and `anyhow::Error`. +Remote types require special handling, since there's no way to add a `#[derive]` to a type that's +already been defined upstream. Instead, use `#[uniffi::remote()]` to wrap the item's +definition. UniFFI will generate the code to handle that type in the FFI, similar to if it had been +wrapped with `#[derive(uniffi::)]`. + +See https://mozilla.github.io/uniffi-rs/udl/remote_ext_types.html for more details. diff --git a/examples/remote-types/src/lib.rs b/examples/remote-types/src/lib.rs new file mode 100644 index 0000000000..720fa7e865 --- /dev/null +++ b/examples/remote-types/src/lib.rs @@ -0,0 +1,53 @@ +// It's often useful, but not required, to define type aliases for remote type. +// +// These control the names of the types in the generated code. +type AnyhowError = anyhow::Error; +type LogLevel = log::Level; + +// Use #[uniffi::remote] to enable support for passing the types across the FFI + +// For records/enums, wrap the item definition with `#[uniffi::remote]`. +// Copy each field/variant definitions exactly as they appear in the remote crate. +#[uniffi::remote(Enum)] +pub enum LogLevel { + Error = 1, + Warn = 2, + Info = 3, + Debug = 4, + Trace = 5, +} + +// For interfaces, wrap a unit struct with `#[uniffi::remote]`. +#[uniffi::remote(Object)] +pub struct AnyhowError; + +// TODO: +// - Add support for exporting methods on remote interfaces types +// - Add support for Rust traits like `Display` + +/// Logger that uses the remote types +#[derive(uniffi::Object)] +pub struct LogSink {} + +#[uniffi::export] +impl LogSink { + /// Construct a new LogSink that writes to a file + /// We currently need to wrap AnyhowError in an Arc, but #2093 should fix that. + #[uniffi::constructor] + pub fn new(path: &str) -> Result> { + // For this example, we don't actually do anything with the path except check that it's + // non-empty. + if path.is_empty() { + Err(std::sync::Arc::new(AnyhowError::msg("Empty path"))) + } else { + Ok(Self {}) + } + } + + /// Log a message to our file + pub fn log(&self, _level: log::Level, _msg: String) { + // Pretend this code actually writes something out + } +} + +uniffi::setup_scaffolding!(); diff --git a/examples/remote-types/tests/bindings/test_remote_types.kts b/examples/remote-types/tests/bindings/test_remote_types.kts new file mode 100644 index 0000000000..00ba8f5810 --- /dev/null +++ b/examples/remote-types/tests/bindings/test_remote_types.kts @@ -0,0 +1,12 @@ +import remote_types.* + +val testLogger = LogSink("SomeFile") +testLogger.log(LogLevel.INFO, "Hello world") + +// Test the error handling +try { + LogSink("") + throw RuntimeException("Constructor should have thrown") +} catch (e: AnyhowException) { + // Expected +} diff --git a/examples/remote-types/tests/bindings/test_remote_types.py b/examples/remote-types/tests/bindings/test_remote_types.py new file mode 100644 index 0000000000..5311749032 --- /dev/null +++ b/examples/remote-types/tests/bindings/test_remote_types.py @@ -0,0 +1,11 @@ +from remote_types import * + +testLogger = LogSink("SomeFile") +testLogger.log(LogLevel.INFO, "Hello world") + +# Test the error handling +try: + LogSink("") + raise RuntimeError("Constructor should have thrown") +except AnyhowError: + pass diff --git a/examples/remote-types/tests/bindings/test_remote_types.swift b/examples/remote-types/tests/bindings/test_remote_types.swift new file mode 100644 index 0000000000..47b285f8f2 --- /dev/null +++ b/examples/remote-types/tests/bindings/test_remote_types.swift @@ -0,0 +1,12 @@ +import remote_types + +let testLogger = try! LogSink(path: "SomeFile") +testLogger.log(level: LogLevel.info, msg: "Hello world") + +// Test the error handling +do { + let _ = try LogSink(path: "") + fatalError("Constructor should have thrown") +} catch is AnyhowError { + // Expected +} diff --git a/examples/remote-types/tests/test_generated_bindings.rs b/examples/remote-types/tests/test_generated_bindings.rs new file mode 100644 index 0000000000..dbe7f8c2b3 --- /dev/null +++ b/examples/remote-types/tests/test_generated_bindings.rs @@ -0,0 +1,5 @@ +uniffi::build_foreign_language_testcases!( + "tests/bindings/test_remote_types.kts", + "tests/bindings/test_remote_types.py", + "tests/bindings/test_remote_types.swift", +); diff --git a/examples/remote-types/uniffi.toml b/examples/remote-types/uniffi.toml new file mode 100644 index 0000000000..34af2e6520 --- /dev/null +++ b/examples/remote-types/uniffi.toml @@ -0,0 +1,2 @@ +[bindings.kotlin] +package_name = "remote_types" diff --git a/fixtures/ext-types/custom-types/src/lib.rs b/fixtures/ext-types/custom-types/src/lib.rs index dc05f31c2e..af78d956c7 100644 --- a/fixtures/ext-types/custom-types/src/lib.rs +++ b/fixtures/ext-types/custom-types/src/lib.rs @@ -28,7 +28,7 @@ pub fn get_guid(guid: Option) -> Guid { Some(guid) => { assert!( !guid.0.is_empty(), - "our UniffiCustomTypeConverter already checked!" + "our custom type converter already checked!" ); guid } @@ -38,13 +38,13 @@ pub fn get_guid(guid: Option) -> Guid { fn try_get_guid(guid: Option) -> std::result::Result { // This function itself always returns Ok - but it's declared as a Result - // because the UniffiCustomTypeConverter might return the Err as part of + // because the custom type converter might return the Err as part of // turning the string into the Guid. Ok(match guid { Some(guid) => { assert!( !guid.0.is_empty(), - "our UniffiCustomTypeConverter failed to check for an empty GUID" + "our custom type converter failed to check for an empty GUID" ); guid } @@ -85,12 +85,10 @@ pub fn run_callback(callback: Box) -> Guid { callback.run(Guid("callback-test-payload".into())) } -impl UniffiCustomTypeConverter for Guid { - type Builtin = String; - - // This is a "fixture" rather than an "example", so we are free to do things that don't really - // make sense for real apps. - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(Guid, String, { + try_into_custom: |val| { + // This is a "fixture" rather than an "example", so we are free to do things that don't really + // make sense for real apps. if val.is_empty() { Err(GuidError::TooShort.into()) } else if val == "unexpected" { @@ -100,28 +98,13 @@ impl UniffiCustomTypeConverter for Guid { } else { Ok(Guid(val)) } - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.0 - } -} + }, + from_custom: |obj| obj.0, +}); pub struct ANestedGuid(pub Guid); -impl UniffiCustomTypeConverter for ANestedGuid { - type Builtin = Guid; - - // This is a "fixture" rather than an "example", so we are free to do things that don't really - // make sense for real apps. - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(ANestedGuid(val)) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.0 - } -} +uniffi::custom_newtype!(ANestedGuid, Guid); #[uniffi::export] fn get_nested_guid(nguid: Option) -> ANestedGuid { diff --git a/fixtures/ext-types/http-headermap/src/lib.rs b/fixtures/ext-types/http-headermap/src/lib.rs index 946d572af8..ca4fe5f6d3 100644 --- a/fixtures/ext-types/http-headermap/src/lib.rs +++ b/fixtures/ext-types/http-headermap/src/lib.rs @@ -8,14 +8,17 @@ pub struct HttpHeader { pub(crate) val: String, } -/// Expose `http::HeaderMap` to Uniffi. -impl crate::UniffiCustomTypeConverter for http::HeaderMap { - /// http::HeaderMap is a multimap so there may be multiple values - /// per key. We represent this as a vector of `HttpHeader` (AKA - /// `key` & `val`) where `key` may repeat. - type Builtin = Vec; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { +// Expose `http::HeaderMap` to Uniffi. +uniffi::custom_type!(HeaderMap, Vec, { + from_custom: |obj| { + obj.iter() + .map(|(k, v)| HttpHeader { + key: k.as_str().to_string(), + val: v.to_str().unwrap().to_string(), + }) + .collect() + }, + try_into_custom: |val| { Ok(http::HeaderMap::from_iter(val.into_iter().filter_map( |h| { let n = http::HeaderName::from_str(&h.key).ok()?; @@ -23,17 +26,10 @@ impl crate::UniffiCustomTypeConverter for http::HeaderMap { Some((n, v)) }, ))) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.iter() - .map(|(k, v)| HttpHeader { - key: k.as_str().to_string(), - val: v.to_str().unwrap().to_string(), - }) - .collect() - } -} + }, + // Must specify remote since http::HeaderMap is defined in a different crate. + remote, +}); pub fn get_headermap(v: String) -> HeaderMap { let n = http::HeaderName::from_str("test-header").unwrap(); diff --git a/fixtures/ext-types/lib/src/ext-types-lib.udl b/fixtures/ext-types/lib/src/ext-types-lib.udl index 828a499874..94ffffcdba 100644 --- a/fixtures/ext-types/lib/src/ext-types-lib.udl +++ b/fixtures/ext-types/lib/src/ext-types-lib.udl @@ -55,17 +55,20 @@ typedef extern Url; [External="custom_types"] typedef extern Handle; -// Here are some different kinds of "external" types - the types are described +// Here are some different kinds of remote types - the types are described // in this UDL, but the types themselves are defined in a different crate. + +[Remote] dictionary ExternalCrateDictionary { string sval; }; +[Remote] interface ExternalCrateInterface { string value(); }; -[NonExhaustive] +[Remote, NonExhaustive] enum ExternalCrateNonExhaustiveEnum { "One", "Two", diff --git a/fixtures/ext-types/lib/src/lib.rs b/fixtures/ext-types/lib/src/lib.rs index 63fdac77ae..9b39c0231a 100644 --- a/fixtures/ext-types/lib/src/lib.rs +++ b/fixtures/ext-types/lib/src/lib.rs @@ -11,17 +11,9 @@ use uniffi_one::{ use uniffi_sublib::SubLibType; use url::Url; -// #1988 -uniffi::ffi_converter_forward!( - ext_types_custom::Ouid, - ext_types_custom::UniFfiTag, - crate::UniFfiTag -); -uniffi::ffi_converter_forward!( - ext_types_custom::ANestedGuid, - ext_types_custom::UniFfiTag, - crate::UniFfiTag -); +// Remote types require a macro call in the Rust source + +uniffi::remote_type!(Url, custom_types); pub struct CombinedType { pub uoe: UniffiOneEnum, diff --git a/fixtures/ext-types/proc-macro-lib/src/lib.rs b/fixtures/ext-types/proc-macro-lib/src/lib.rs index e039ff091e..d501599e6c 100644 --- a/fixtures/ext-types/proc-macro-lib/src/lib.rs +++ b/fixtures/ext-types/proc-macro-lib/src/lib.rs @@ -6,12 +6,7 @@ use uniffi_one::{ }; use url::Url; -uniffi::use_udl_record!(uniffi_one, UniffiOneType); -uniffi::use_udl_enum!(uniffi_one, UniffiOneEnum); -uniffi::use_udl_object!(uniffi_one, UniffiOneInterface); -uniffi::use_udl_record!(ext_types_custom, Guid); -uniffi::use_udl_record!(custom_types, Url); -uniffi::use_udl_record!(custom_types, Handle); +uniffi::remote_type!(Url, custom_types); #[derive(uniffi::Record)] pub struct CombinedType { @@ -168,22 +163,11 @@ pub struct Uuid { val: String, } -// Tell UniFfi we want to use am UniffiCustomTypeConverter to go to and -// from a String. -// Note this could be done even if the above `struct` defn was external. -uniffi::custom_type!(Uuid, String); - -impl UniffiCustomTypeConverter for Uuid { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(Uuid { val }) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.val - } -} +// Define a custom type exactly like we would for UDL +uniffi::custom_type!(Uuid, String, { + from_custom: |uuid| uuid.val, + try_into_custom: |s| Ok(Uuid { val: s}), +}); mod submodule { // A custom type using the "newtype" idiom. diff --git a/fixtures/ext-types/sub-lib/src/lib.rs b/fixtures/ext-types/sub-lib/src/lib.rs index 565e7f4616..49f97fc004 100644 --- a/fixtures/ext-types/sub-lib/src/lib.rs +++ b/fixtures/ext-types/sub-lib/src/lib.rs @@ -1,9 +1,6 @@ use std::sync::Arc; use uniffi_one::{UniffiOneEnum, UniffiOneInterface, UniffiOneTrait}; -uniffi::use_udl_object!(uniffi_one, UniffiOneInterface); -uniffi::use_udl_enum!(uniffi_one, UniffiOneEnum); - #[derive(Default, uniffi::Record)] pub struct SubLibType { pub maybe_enum: Option, diff --git a/fixtures/metadata/src/tests.rs b/fixtures/metadata/src/tests.rs index fa6ad2364d..5d7aa4556b 100644 --- a/fixtures/metadata/src/tests.rs +++ b/fixtures/metadata/src/tests.rs @@ -181,6 +181,7 @@ mod test_metadata { RecordMetadata { module_path: "uniffi_fixture_metadata".into(), name: "Person".into(), + remote: false, fields: vec![ FieldMetadata { name: "name".into(), @@ -207,6 +208,7 @@ mod test_metadata { EnumMetadata { module_path: "uniffi_fixture_metadata".into(), name: "Weapon".into(), + remote: false, forced_flatness: None, discr_type: None, variants: vec![ @@ -242,6 +244,7 @@ mod test_metadata { EnumMetadata { module_path: "uniffi_fixture_metadata".into(), name: "State".into(), + remote: false, forced_flatness: None, discr_type: None, variants: vec![ @@ -290,6 +293,7 @@ mod test_metadata { EnumMetadata { module_path: "uniffi_fixture_metadata".into(), name: "ReprU8".into(), + remote: false, forced_flatness: None, discr_type: Some(Type::UInt8), variants: vec![ @@ -325,6 +329,7 @@ mod test_metadata { EnumMetadata { module_path: "uniffi_fixture_metadata".into(), name: "NoRepr".into(), + remote: false, forced_flatness: None, discr_type: None, variants: vec![VariantMetadata { @@ -346,6 +351,7 @@ mod test_metadata { EnumMetadata { module_path: "uniffi_fixture_metadata".into(), name: "FlatError".into(), + remote: false, forced_flatness: Some(true), discr_type: None, variants: vec![ @@ -375,6 +381,7 @@ mod test_metadata { EnumMetadata { module_path: "uniffi_fixture_metadata".into(), name: "ComplexError".into(), + remote: false, forced_flatness: Some(false), discr_type: None, variants: vec![ @@ -423,6 +430,7 @@ mod test_metadata { ObjectMetadata { module_path: "uniffi_fixture_metadata".into(), name: "Calculator".into(), + remote: false, imp: ObjectImpl::Struct, docstring: None, }, @@ -731,6 +739,7 @@ mod test_function_metadata { ObjectMetadata { module_path: "uniffi_fixture_metadata".into(), name: "CalculatorDisplay".into(), + remote: false, imp: ObjectImpl::Trait, docstring: None, }, @@ -744,6 +753,7 @@ mod test_function_metadata { ObjectMetadata { module_path: "uniffi_fixture_metadata".into(), name: "TraitWithForeign".into(), + remote: false, imp: ObjectImpl::CallbackTrait, docstring: None, }, diff --git a/fixtures/regressions/nested-module-import/README.md b/fixtures/regressions/nested-module-import/README.md deleted file mode 100644 index 8d6d2e4ba6..0000000000 --- a/fixtures/regressions/nested-module-import/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Regression test for include scaffolding in a module - -It is possible to include the generated scaffolding in a submodule, -then reexport everything on te top-level. - -This recently broke with the introduction of `UniffiCustomTypeConverter` in the scaffolding as a private type. -The fix was easy: make the type public, so it is also re-exported properly. diff --git a/fixtures/regressions/nested-module-import/build.rs b/fixtures/regressions/nested-module-import/build.rs deleted file mode 100644 index 05b0af1433..0000000000 --- a/fixtures/regressions/nested-module-import/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -fn main() { - uniffi::generate_scaffolding("src/test.udl").unwrap(); -} diff --git a/fixtures/regressions/nested-module-import/src/lib.rs b/fixtures/regressions/nested-module-import/src/lib.rs deleted file mode 100644 index d0fdf0d687..0000000000 --- a/fixtures/regressions/nested-module-import/src/lib.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// The generated scaffolding is imported into a submodule on the Rust side (for namespacing) -// and then reexported using `pub use *`. -// We hit a case where some required types weren't `pub` and thus this would fail. - -pub struct Integer(i32); - -pub fn run() -> Integer { - Integer(42) -} - -mod ffi { - use super::*; - uniffi::include_scaffolding!("test"); - - impl UniffiCustomTypeConverter for Integer { - type Builtin = i32; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(Integer(val)) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.0 - } - } -} -pub use ffi::*; diff --git a/fixtures/regressions/nested-module-import/src/test.udl b/fixtures/regressions/nested-module-import/src/test.udl deleted file mode 100644 index e942064afd..0000000000 --- a/fixtures/regressions/nested-module-import/src/test.udl +++ /dev/null @@ -1,10 +0,0 @@ -// The generated scaffolding is imported into a submodule on the Rust side (for namespacing) -// and then reexported using `pub use *`. -// We hit a case where some required types weren't `pub` and thus this would fail. - -[Custom] -typedef i32 Integer; - -namespace regression_test_nested_module { - Integer run(); -}; diff --git a/fixtures/simple-fns/src/simple-fns.udl b/fixtures/simple-fns/src/simple-fns.udl index 4290beb3ac..6c08169709 100644 --- a/fixtures/simple-fns/src/simple-fns.udl +++ b/fixtures/simple-fns/src/simple-fns.udl @@ -1,3 +1,4 @@ namespace uniffi_simple_fns {}; +[Remote] interface MyHashSet {}; diff --git a/mkdocs.yml b/mkdocs.yml index 6eca92b59e..ceabc1a51f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,10 +41,8 @@ nav: - 'Interfaces and Objects': - ./udl/interfaces.md - ./udl/callback_interfaces.md - - 'External Types': - - ./udl/ext_types.md - - ./udl/ext_types_external.md - - ./udl/custom_types.md + - ./udl/remote_ext_types.md + - ./udl/custom_types.md - ./udl/docstrings.md - ./proc_macro/index.md - ./futures.md diff --git a/uniffi_bindgen/src/interface/enum_.rs b/uniffi_bindgen/src/interface/enum_.rs index a666cc3605..b5a3b60d9b 100644 --- a/uniffi_bindgen/src/interface/enum_.rs +++ b/uniffi_bindgen/src/interface/enum_.rs @@ -174,6 +174,7 @@ use super::{AsType, Literal, Type, TypeIterator}; pub struct Enum { pub(super) name: String, pub(super) module_path: String, + pub(super) remote: bool, pub(super) discr_type: Option, pub(super) variants: Vec, // NOTE: `flat` is a misleading name and to make matters worse, has 2 different @@ -204,6 +205,10 @@ impl Enum { &self.name } + pub fn remote(&self) -> bool { + self.remote + } + pub fn variants(&self) -> &[Variant] { &self.variants } @@ -273,6 +278,7 @@ impl Enum { Ok(Self { name: meta.name, module_path: meta.module_path, + remote: meta.remote, discr_type: meta.discr_type, variants: meta .variants @@ -667,6 +673,7 @@ mod test { let mut e = Enum { module_path: "test".to_string(), name: "test".to_string(), + remote: false, discr_type: None, variants: vec![], flat: false, diff --git a/uniffi_bindgen/src/interface/mod.rs b/uniffi_bindgen/src/interface/mod.rs index 4137326670..c5650e09a8 100644 --- a/uniffi_bindgen/src/interface/mod.rs +++ b/uniffi_bindgen/src/interface/mod.rs @@ -1118,7 +1118,9 @@ fn throws_name(throws: &Option) -> Option<&str> { // Type has no `name()` method, just `canonical_name()` which isn't what we want. match throws { None => None, - Some(Type::Enum { name, .. }) | Some(Type::Object { name, .. }) => Some(name), + Some(Type::Enum { name, .. }) + | Some(Type::Object { name, .. }) + | Some(Type::External { name, .. }) => Some(name), _ => panic!("unknown throw type: {throws:?}"), } } @@ -1164,6 +1166,7 @@ mod test { existing definition: Enum { name: \"Testing\", module_path: \"crate_name\", + remote: false, discr_type: None, variants: [ Variant { @@ -1186,6 +1189,7 @@ existing definition: Enum { new definition: Enum { name: \"Testing\", module_path: \"crate_name\", + remote: false, discr_type: None, variants: [ Variant { diff --git a/uniffi_bindgen/src/interface/object.rs b/uniffi_bindgen/src/interface/object.rs index 2b86e54a45..73ba81ce5f 100644 --- a/uniffi_bindgen/src/interface/object.rs +++ b/uniffi_bindgen/src/interface/object.rs @@ -85,6 +85,7 @@ pub struct Object { /// How this object is implemented in Rust pub(super) imp: ObjectImpl, pub(super) module_path: String, + pub(super) remote: bool, pub(super) constructors: Vec, pub(super) methods: Vec, // The "trait" methods - they have a (presumably "well known") name, and @@ -127,6 +128,10 @@ impl Object { &self.imp } + pub fn remote(&self) -> bool { + self.remote + } + pub fn is_trait_interface(&self) -> bool { self.imp.is_trait_interface() } @@ -309,6 +314,7 @@ impl From for Object { module_path: meta.module_path, name: meta.name, imp: meta.imp, + remote: meta.remote, constructors: Default::default(), methods: Default::default(), uniffi_traits: Default::default(), diff --git a/uniffi_bindgen/src/interface/record.rs b/uniffi_bindgen/src/interface/record.rs index e9a6004189..7187d22658 100644 --- a/uniffi_bindgen/src/interface/record.rs +++ b/uniffi_bindgen/src/interface/record.rs @@ -59,6 +59,7 @@ use super::{AsType, Type, TypeIterator}; pub struct Record { pub(super) name: String, pub(super) module_path: String, + pub(super) remote: bool, pub(super) fields: Vec, #[checksum_ignore] pub(super) docstring: Option, @@ -69,6 +70,10 @@ impl Record { &self.name } + pub fn remote(&self) -> bool { + self.remote + } + pub fn fields(&self) -> &[Field] { &self.fields } @@ -102,6 +107,7 @@ impl TryFrom for Record { Ok(Self { name: meta.name, module_path: meta.module_path, + remote: meta.remote, fields: meta .fields .into_iter() diff --git a/uniffi_bindgen/src/scaffolding/mod.rs b/uniffi_bindgen/src/scaffolding/mod.rs index 7fd81831aa..ba7335fe80 100644 --- a/uniffi_bindgen/src/scaffolding/mod.rs +++ b/uniffi_bindgen/src/scaffolding/mod.rs @@ -7,7 +7,7 @@ use askama::Template; use std::borrow::Borrow; use super::interface::*; -use heck::{ToShoutySnakeCase, ToSnakeCase}; +use heck::ToShoutySnakeCase; #[derive(Template)] #[template(syntax = "rs", escape = "none", path = "scaffolding_template.rs")] @@ -71,9 +71,4 @@ mod filters { Type::External { name, .. } => format!("r#{name}"), }) } - - // Turns a `crate-name` into the `crate_name` the .rs code needs to specify. - pub fn crate_name_rs(nm: &str) -> Result { - Ok(format!("r#{}", nm.to_string().to_snake_case())) - } } diff --git a/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs index 20b845d515..b97d65698e 100644 --- a/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs @@ -2,7 +2,11 @@ // Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent. #} +{%- if e.remote() %} +#[::uniffi::udl_remote(Enum)] +{%- else %} #[::uniffi::udl_derive(Enum)] +{%- endif %} {%- if e.is_non_exhaustive() %} #[non_exhaustive] {%- endif %} diff --git a/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs index 2529c73421..a71be3121f 100644 --- a/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs @@ -2,7 +2,11 @@ // Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent. #} +{%- if e.remote() %} +#[::uniffi::udl_remote(Error)] +{%- else %} #[::uniffi::udl_derive(Error)] +{%- endif %} {% if e.is_flat() -%} #[uniffi(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 deleted file mode 100644 index d67e172cc2..0000000000 --- a/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Support for external types. - -// Types with an external `FfiConverter`... -{% for (name, crate_name, kind, tagged) in ci.iter_external_types() %} -// The FfiConverter for `{{ name }}` is defined in `{{ crate_name }}` -// If it has its existing FfiConverter defined with a UniFFITag, it needs forwarding. -{% if tagged %} -{%- match kind %} -{%- when ExternalKind::DataClass %} -::uniffi::ffi_converter_forward!(r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag); -{%- when ExternalKind::Interface %} -::uniffi::ffi_converter_arc_forward!(r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag); -{%- when ExternalKind::Trait %} -::uniffi::ffi_converter_arc_forward!(dyn r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag); -{%- endmatch %} -{% endif %} -{%- endfor %} - -// We generate support for each Custom Type and the builtin type it uses. -{%- for (name, builtin) in ci.iter_custom_types() %} -::uniffi::custom_type!(r#{{ name }}, {{builtin|type_rs}}); -{%- endfor -%} diff --git a/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs index f307f243a6..9c99c74dd1 100644 --- a/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs @@ -33,7 +33,11 @@ pub trait r#{{ obj.name() }} { #[uniffi::export(Eq)] {% endmatch %} {% endfor %} +{%- if obj.remote() %} +#[::uniffi::udl_remote(Object)] +{%- else %} #[::uniffi::udl_derive(Object)] +{%- endif %} struct {{ obj.rust_name() }} { } {%- for cons in obj.constructors() %} diff --git a/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs index c612dafda9..970d160a10 100644 --- a/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs @@ -2,7 +2,11 @@ // Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent. #} +{%- if rec.remote() %} +#[::uniffi::udl_remote(Record)] +{%- else %} #[::uniffi::udl_derive(Record)] +{%- endif %} 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/scaffolding_template.rs b/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs index c88e204e97..e5554b1fa9 100644 --- a/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs +++ b/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs @@ -45,8 +45,5 @@ uniffi::deps::static_assertions::assert_impl_all!({{ k|type_rs }}: ::std::cmp::E {% include "CallbackInterfaceTemplate.rs" %} {% endfor %} -// External and Wrapped types -{% include "ExternalTypesTemplate.rs" %} - // Export scaffolding checksums for UDL items {% include "Checksums.rs" %} diff --git a/uniffi_core/src/lib.rs b/uniffi_core/src/lib.rs index 1135753371..7852eeece4 100644 --- a/uniffi_core/src/lib.rs +++ b/uniffi_core/src/lib.rs @@ -185,73 +185,6 @@ macro_rules! ffi_converter_rust_buffer_lift_and_lower { }; } -/// Macro to implement `FfiConverter` for a UniFfiTag using a different UniFfiTag -/// -/// This is used for external types -#[macro_export] -macro_rules! ffi_converter_forward { - // Forward a `FfiConverter` implementation - ($T:ty, $existing_impl_tag:ty, $new_impl_tag:ty) => { - ::uniffi::do_ffi_converter_forward!( - FfiConverter, - $T, - $T, - $existing_impl_tag, - $new_impl_tag - ); - - $crate::derive_ffi_traits!(local $T); - }; -} - -/// Macro to implement `FfiConverterArc` for a UniFfiTag using a different UniFfiTag -/// -/// This is used for external types -#[macro_export] -macro_rules! ffi_converter_arc_forward { - ($T:ty, $existing_impl_tag:ty, $new_impl_tag:ty) => { - ::uniffi::do_ffi_converter_forward!( - FfiConverterArc, - ::std::sync::Arc<$T>, - $T, - $existing_impl_tag, - $new_impl_tag - ); - - // Note: no need to call derive_ffi_traits! because there is a blanket impl for all Arc - }; -} - -// Generic code between the two macros above -#[doc(hidden)] -#[macro_export] -macro_rules! do_ffi_converter_forward { - ($trait:ident, $rust_type:ty, $T:ty, $existing_impl_tag:ty, $new_impl_tag:ty) => { - unsafe impl $crate::$trait<$new_impl_tag> for $T { - type FfiType = <$T as $crate::$trait<$existing_impl_tag>>::FfiType; - - fn lower(obj: $rust_type) -> Self::FfiType { - <$T as $crate::$trait<$existing_impl_tag>>::lower(obj) - } - - fn try_lift(v: Self::FfiType) -> $crate::Result<$rust_type> { - <$T as $crate::$trait<$existing_impl_tag>>::try_lift(v) - } - - fn write(obj: $rust_type, buf: &mut Vec) { - <$T as $crate::$trait<$existing_impl_tag>>::write(obj, buf) - } - - fn try_read(buf: &mut &[u8]) -> $crate::Result<$rust_type> { - <$T as $crate::$trait<$existing_impl_tag>>::try_read(buf) - } - - const TYPE_ID_META: ::uniffi::MetadataBuffer = - <$T as $crate::$trait<$existing_impl_tag>>::TYPE_ID_META; - } - }; -} - #[cfg(test)] mod test { use super::{FfiConverter, UniFfiTag}; diff --git a/uniffi_macros/src/custom.rs b/uniffi_macros/src/custom.rs index 5a52fc4964..a709b51de4 100644 --- a/uniffi_macros/src/custom.rs +++ b/uniffi_macros/src/custom.rs @@ -2,96 +2,250 @@ * 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 crate::{ - ffiops, - util::{derive_all_ffi_traits, ident_to_string, mod_path, tagged_impl_header}, +use crate::util::{ + either_attribute_arg, ident_to_string, kw, mod_path, parse_comma_separated, UniffiAttributeArgs, }; use proc_macro2::{Ident, TokenStream}; -use quote::quote; -use syn::Path; - -// Generate an FfiConverter impl based on the UniffiCustomTypeConverter -// implementation that the library supplies -pub(crate) fn expand_ffi_converter_custom_type( - ident: &Ident, - builtin: &Path, - udl_mode: bool, -) -> syn::Result { - let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode); - let derive_ffi_traits = derive_all_ffi_traits(ident, udl_mode); - let name = ident_to_string(ident); +use quote::{quote, ToTokens}; +use syn::{ + braced, + parse::{Parse, ParseStream}, + spanned::Spanned, + token::Brace, + Expr, ExprClosure, Pat, Token, Type, +}; + +pub struct CustomTypeArgs { + custom_type: Type, + uniffi_type: Type, + options: CustomTypeOptions, +} + +impl Parse for CustomTypeArgs { + fn parse(input: ParseStream<'_>) -> syn::Result { + // Parse the custom / UniFFI type which are both required + let custom_type = input.parse()?; + input.parse::()?; + let uniffi_type = input.parse()?; + let options = if input.peek(Token![,]) { + input.parse::()?; + + if input.peek(Brace) { + let content; + braced!(content in input); + content.parse()? + } else { + CustomTypeOptions::default() + } + } else { + CustomTypeOptions::default() + }; + Ok(Self { + custom_type, + uniffi_type, + options, + }) + } +} + +/// Handle extra options for the custom type macro +#[derive(Default)] +pub struct CustomTypeOptions { + from_custom: Option, + try_into_custom: Option, + remote: Option, +} + +impl Parse for CustomTypeOptions { + fn parse(input: ParseStream<'_>) -> syn::Result { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for CustomTypeOptions { + fn parse_one(input: ParseStream<'_>) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::from_custom) { + input.parse::()?; + input.parse::()?; + Ok(Self { + from_custom: Some(input.parse()?), + ..Self::default() + }) + } else if lookahead.peek(kw::try_into_custom) { + input.parse::()?; + input.parse::()?; + Ok(Self { + try_into_custom: Some(input.parse()?), + ..Self::default() + }) + } else if lookahead.peek(kw::remote) { + Ok(Self { + remote: Some(input.parse()?), + ..Self::default() + }) + } else { + Err(lookahead.error()) + } + } + + fn merge(self, other: Self) -> syn::Result { + Ok(Self { + from_custom: either_attribute_arg(self.from_custom, other.from_custom)?, + try_into_custom: either_attribute_arg(self.try_into_custom, other.try_into_custom)?, + remote: either_attribute_arg(self.remote, other.remote)?, + }) + } +} + +struct ConvertClosure { + closure: ExprClosure, + param_ident: Ident, + body: Expr, +} + +impl ConvertClosure { + fn token_tuple(&self) -> (TokenStream, TokenStream) { + let ConvertClosure { + param_ident, body, .. + } = self; + (quote! { #param_ident }, quote! { #body }) + } +} + +impl Parse for ConvertClosure { + fn parse(input: ParseStream<'_>) -> syn::Result { + let closure: ExprClosure = input.parse()?; + if closure.inputs.len() != 1 { + return Err(syn::Error::new( + closure.inputs.span(), + "Expected exactly 1 argument", + )); + } + let param_ident = match closure.inputs.last().unwrap().clone() { + Pat::Ident(i) => Ok(i.ident), + Pat::Type(p) => match *p.pat { + Pat::Ident(i) => Ok(i.ident), + _ => Err(p.pat.span()), + }, + input => Err(input.span()), + } + .map_err(|span| syn::Error::new(span, "Unexpected argument type"))?; + Ok(Self { + body: *closure.body.clone(), + closure, + param_ident, + }) + } +} + +impl ToTokens for ConvertClosure { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.closure.to_tokens(tokens) + } +} + +pub(crate) fn expand_custom_type(args: CustomTypeArgs) -> syn::Result { + let CustomTypeArgs { + custom_type, + uniffi_type, + options, + } = args; + + let name = match &custom_type { + Type::Path(p) => match p.path.get_ident() { + Some(i) => Ok(ident_to_string(i)), + None => Err("Custom types must only have one component"), + }, + _ => Err("Custom types must be specified as simple idents"), + } + .map_err(|msg| syn::Error::new(custom_type.span(), msg))?; + let mod_path = mod_path()?; - let from_custom = quote! { <#ident as crate::UniffiCustomTypeConverter>::from_custom }; - let into_custom = quote! { <#ident as crate::UniffiCustomTypeConverter>::into_custom }; - let lower_type = ffiops::lower_type(builtin); - let lower = ffiops::lower(builtin); - let write = ffiops::write(builtin); - let try_lift = ffiops::try_lift(builtin); - let try_read = ffiops::try_read(builtin); - let type_id_meta = ffiops::type_id_meta(builtin); + let (impl_spec, derive_ffi_traits) = if options.remote.is_some() { + ( + quote! { unsafe impl ::uniffi::FfiConverter for #custom_type }, + quote! { ::uniffi::derive_ffi_traits!(local #custom_type); }, + ) + } else { + ( + quote! { unsafe impl ::uniffi::FfiConverter for #custom_type }, + quote! { ::uniffi::derive_ffi_traits!(blanket #custom_type); }, + ) + }; + + let (from_custom_param, from_custom_expr) = match options.from_custom { + Some(convert_closure) => convert_closure.token_tuple(), + None => ( + quote! { val }, + quote! { <#custom_type as Into<#uniffi_type>>::into(val) }, + ), + }; + let (try_into_custom_param, try_into_custom_expr) = match options.try_into_custom { + Some(convert_closure) => convert_closure.token_tuple(), + None => ( + quote! { val }, + quote! { Ok(<#uniffi_type as TryInto<#custom_type>>::try_into(val)?) }, + ), + }; Ok(quote! { - #[automatically_derived] - unsafe #impl_spec { - // Note: the builtin type needs to implement both `Lower` and `Lift'. We use the + #[allow(non_camel_case_types)] + #impl_spec { + // Note: the UniFFI type needs to implement both `Lower` and `Lift'. We use the // `Lower` trait to get the associated type `FfiType` and const `TYPE_ID_META`. These // can't differ between `Lower` and `Lift`. - type FfiType = #lower_type; - fn lower(obj: #ident ) -> Self::FfiType { - #lower(#from_custom(obj)) + type FfiType = <#uniffi_type as ::uniffi::Lower>::FfiType; + fn lower(#from_custom_param: #custom_type ) -> Self::FfiType { + <#uniffi_type as ::uniffi::Lower>::lower(#from_custom_expr) } - fn try_lift(v: Self::FfiType) -> uniffi::Result<#ident> { - #into_custom(#try_lift(v)?) + fn try_lift(v: Self::FfiType) -> ::uniffi::Result<#custom_type> { + let #try_into_custom_param = <#uniffi_type as ::uniffi::Lift>::try_lift(v)?; + #try_into_custom_expr } - fn write(obj: #ident, buf: &mut Vec) { - #write(#from_custom(obj), buf); + fn write(#from_custom_param: #custom_type, buf: &mut Vec) { + <#uniffi_type as ::uniffi::Lower>::write(#from_custom_expr, buf); } - fn try_read(buf: &mut &[u8]) -> uniffi::Result<#ident> { - #into_custom(#try_read(buf)?) + fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<#custom_type> { + let #try_into_custom_param = <#uniffi_type as ::uniffi::Lift>::try_read(buf)?; + #try_into_custom_expr } const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_CUSTOM) .concat_str(#mod_path) .concat_str(#name) - .concat(#type_id_meta); + .concat(<#uniffi_type as ::uniffi::TypeId>::TYPE_ID_META); } #derive_ffi_traits }) } -// Generate an FfiConverter impl *and* an UniffiCustomTypeConverter. -pub(crate) fn expand_ffi_converter_custom_newtype( - ident: &Ident, - builtin: &Path, - udl_mode: bool, -) -> syn::Result { - let ffi_converter = expand_ffi_converter_custom_type(ident, builtin, udl_mode)?; - let type_converter = custom_ffi_type_converter(ident, builtin)?; - - Ok(quote! { - #ffi_converter - - #[allow(non_camel_case_types)] - #type_converter - }) +pub struct CustomNewtypeArgs { + ident: Type, + uniffi_type: Type, } -fn custom_ffi_type_converter(ident: &Ident, builtin: &Path) -> syn::Result { - Ok(quote! { - impl crate::UniffiCustomTypeConverter for #ident { - type Builtin = #builtin; +impl Parse for CustomNewtypeArgs { + fn parse(input: ParseStream<'_>) -> syn::Result { + let ident = input.parse()?; + input.parse::()?; + let uniffi_type = input.parse()?; + Ok(Self { ident, uniffi_type }) + } +} - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(#ident(val)) - } +// Generate TypeConverter implementation for a newtype +pub(crate) fn expand_custom_newtype(args: CustomNewtypeArgs) -> syn::Result { + let CustomNewtypeArgs { ident, uniffi_type } = args; - fn from_custom(obj: Self) -> Self::Builtin { - obj.0 - } - } + Ok(quote! { + uniffi::custom_type!(#ident, #uniffi_type, { + from_custom: |obj| obj.0, + try_into_custom: |val| Ok(#ident(val)), + }); }) } diff --git a/uniffi_macros/src/derive.rs b/uniffi_macros/src/derive.rs index 571b5e88a5..ca92cd4776 100644 --- a/uniffi_macros/src/derive.rs +++ b/uniffi_macros/src/derive.rs @@ -69,6 +69,22 @@ impl Default for DeriveOptions { impl DeriveOptions { /// Construct DeriveOptions for `udl_derive` pub fn udl_derive() -> Self { + Self { + local_tag: false, + generate_metadata: false, + } + } + + /// DeriveOptions for `#[remote]` + pub fn remote() -> Self { + Self { + local_tag: true, + generate_metadata: true, + } + } + + /// DeriveOptions for `#[udl_remote]` + pub fn udl_remote() -> Self { Self { local_tag: true, generate_metadata: false, diff --git a/uniffi_macros/src/export.rs b/uniffi_macros/src/export.rs index 3843544b21..025385808e 100644 --- a/uniffi_macros/src/export.rs +++ b/uniffi_macros/src/export.rs @@ -112,7 +112,7 @@ pub(crate) fn expand_export( quote! { #(#items)* } }); let ffi_converter_tokens = - ffi_converter_callback_interface_impl(&self_ident, &trait_impl_ident, udl_mode); + ffi_converter_callback_interface_impl(&self_ident, &trait_impl_ident); Ok(quote! { #trait_impl diff --git a/uniffi_macros/src/export/callback_interface.rs b/uniffi_macros/src/export/callback_interface.rs index e9621913b7..2244b266a9 100644 --- a/uniffi_macros/src/export/callback_interface.rs +++ b/uniffi_macros/src/export/callback_interface.rs @@ -129,14 +129,15 @@ pub fn trait_impl_ident(trait_name: &str) -> Ident { pub fn ffi_converter_callback_interface_impl( trait_ident: &Ident, trait_impl_ident: &Ident, - udl_mode: bool, ) -> TokenStream { + // TODO: support remote callback interfaces + let remote = false; let trait_name = ident_to_string(trait_ident); let dyn_trait = quote! { dyn #trait_ident }; let box_dyn_trait = quote! { ::std::boxed::Box<#dyn_trait> }; - let lift_impl_spec = tagged_impl_header("Lift", &box_dyn_trait, udl_mode); - let type_id_impl_spec = tagged_impl_header("TypeId", &box_dyn_trait, udl_mode); - let derive_ffi_traits = derive_ffi_traits(&box_dyn_trait, udl_mode, &["LiftRef", "LiftReturn"]); + let lift_impl_spec = tagged_impl_header("Lift", &box_dyn_trait, remote); + let type_id_impl_spec = tagged_impl_header("TypeId", &box_dyn_trait, remote); + let derive_ffi_traits = derive_ffi_traits(&box_dyn_trait, remote, &["LiftRef", "LiftReturn"]); let mod_path = match mod_path() { Ok(p) => p, Err(e) => return e.into_compile_error(), diff --git a/uniffi_macros/src/export/trait_interface.rs b/uniffi_macros/src/export/trait_interface.rs index 51eb94d031..447e52377f 100644 --- a/uniffi_macros/src/export/trait_interface.rs +++ b/uniffi_macros/src/export/trait_interface.rs @@ -99,7 +99,7 @@ pub(super) fn gen_trait_scaffolding( interface_meta_static_var(&self_ident, imp, mod_path, docstring.as_str()) .unwrap_or_else(syn::Error::into_compile_error) }); - let ffi_converter_tokens = ffi_converter(mod_path, &self_ident, udl_mode, with_foreign); + let ffi_converter_tokens = ffi_converter(mod_path, &self_ident, with_foreign); Ok(quote_spanned! { self_ident.span() => #meta_static_var @@ -113,11 +113,12 @@ pub(super) fn gen_trait_scaffolding( pub(crate) fn ffi_converter( mod_path: &str, trait_ident: &Ident, - udl_mode: bool, with_foreign: 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); + // TODO: support defining remote trait interfaces + let remote = false; + let impl_spec = tagged_impl_header("FfiConverterArc", "e! { dyn #trait_ident }, remote); + let lift_ref_impl_spec = tagged_impl_header("LiftRef", "e! { dyn #trait_ident }, remote); let trait_name = ident_to_string(trait_ident); let try_lift = if with_foreign { let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name); diff --git a/uniffi_macros/src/ffiops.rs b/uniffi_macros/src/ffiops.rs index f43befd812..1b230afda4 100644 --- a/uniffi_macros/src/ffiops.rs +++ b/uniffi_macros/src/ffiops.rs @@ -11,13 +11,6 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; -// Lower type -pub fn lower_type(ty: impl ToTokens) -> TokenStream { - quote! { - <#ty as ::uniffi::Lower>::FfiType - } -} - // Lower function pub fn lower(ty: impl ToTokens) -> TokenStream { quote! { diff --git a/uniffi_macros/src/lib.rs b/uniffi_macros/src/lib.rs index 289740b1c8..f7d92d8454 100644 --- a/uniffi_macros/src/lib.rs +++ b/uniffi_macros/src/lib.rs @@ -10,10 +10,7 @@ use camino::Utf8Path; use proc_macro::TokenStream; use quote::quote; -use syn::{ - parse::{Parse, ParseStream}, - parse_macro_input, Ident, LitStr, Path, Token, -}; +use syn::{parse_macro_input, LitStr}; mod custom; mod default; @@ -25,6 +22,7 @@ mod ffiops; mod fnsig; mod object; mod record; +mod remote; mod setup_scaffolding; mod test; mod util; @@ -34,20 +32,6 @@ use self::{ object::expand_object, record::expand_record, }; -struct CustomTypeInfo { - ident: Ident, - builtin: Path, -} - -impl Parse for CustomTypeInfo { - fn parse(input: ParseStream<'_>) -> syn::Result { - let ident = input.parse()?; - input.parse::()?; - let builtin = input.parse()?; - Ok(Self { ident, builtin }) - } -} - /// A macro to build testcases for a component's generated bindings. /// /// This macro provides some plumbing to write automated tests for the generated @@ -134,55 +118,67 @@ pub fn derive_error(input: TokenStream) -> TokenStream { .into() } -/// Generate the `FfiConverter` implementation for a Custom Type - ie, -/// for a `` which implements `UniffiCustomTypeConverter`. +/// Generate FFI code for a custom type #[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, true) + custom::expand_custom_type(parse_macro_input!(tokens)) .unwrap_or_else(syn::Error::into_compile_error) .into() } -/// Generate the `FfiConverter` and the `UniffiCustomTypeConverter` implementations for a -/// Custom Type - ie, for a `` which implements `UniffiCustomTypeConverter` via the -/// newtype idiom. +/// Generate FFI code for a custom newtype #[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, true) + custom::expand_custom_newtype(parse_macro_input!(tokens)) .unwrap_or_else(syn::Error::into_compile_error) .into() } -// Derive items for UDL mode -// -// The Askama templates generate placeholder items wrapped with the `#[udl_derive()]` -// attribute. The macro code then generates derived items based on the input. This system ensures -// that the same code path is used for UDL-based code and proc-macros. -// -// # Differences between UDL-mode and normal mode -// -// ## 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. +/// `#[remote()]` attribute +/// +/// `remote()` generates the same code that `#[derive(uniffi::)]` would, except it only +/// implements the FFI traits for the local UniFfiTag. +/// +/// Use this to wrap the definition of an item defined in a remote crate. +/// See `` for details. +#[doc(hidden)] +#[proc_macro_attribute] +pub fn remote(attrs: TokenStream, input: TokenStream) -> TokenStream { + derive::expand_derive( + parse_macro_input!(attrs), + parse_macro_input!(input), + DeriveOptions::remote(), + ) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +/// `#[udl_remote()]` attribute +/// +/// Alternate version of `#[remote]` for UDL-based generation +/// +/// The difference is that it doesn't generate metadata items, since we get those from parsing the +/// UDL. +#[doc(hidden)] +#[proc_macro_attribute] +pub fn udl_remote(attrs: TokenStream, input: TokenStream) -> TokenStream { + derive::expand_derive( + parse_macro_input!(attrs), + parse_macro_input!(input), + DeriveOptions::udl_remote(), + ) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +/// Derive items for UDL mode +/// +/// The Askama templates generate placeholder items wrapped with the `#[udl_derive()]` +/// attribute. The macro code then generates derived items based on the input. This system ensures +/// that the same code path is used for UDL-based code and proc-macros. +/// +/// `udl_derive` works almost exactly like the `derive_*` macros, except it doesn't generate +/// metadata items, since we get those from parsing the UDL. #[doc(hidden)] #[proc_macro_attribute] pub fn udl_derive(attrs: TokenStream, input: TokenStream) -> TokenStream { @@ -252,45 +248,12 @@ pub fn include_scaffolding(udl_stem: TokenStream) -> TokenStream { }.into() } -// Use a UniFFI types from dependent crates that uses UDL files -// 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) -} - -#[proc_macro] -pub fn use_udl_enum(tokens: TokenStream) -> TokenStream { - use_udl_simple_type(tokens) -} - -#[proc_macro] -pub fn use_udl_error(tokens: TokenStream) -> TokenStream { - use_udl_simple_type(tokens) -} - -fn use_udl_simple_type(tokens: TokenStream) -> TokenStream { - let util::ExternalTypeItem { - crate_ident, - type_ident, - .. - } = parse_macro_input!(tokens); - quote! { - ::uniffi::ffi_converter_forward!(#type_ident, #crate_ident::UniFfiTag, crate::UniFfiTag); - } - .into() -} - +/// Use the FFI trait implementations defined in another crate for a remote type +/// +/// See `` for details. #[proc_macro] -pub fn use_udl_object(tokens: TokenStream) -> TokenStream { - let util::ExternalTypeItem { - crate_ident, - type_ident, - .. - } = parse_macro_input!(tokens); - quote! { - ::uniffi::ffi_converter_arc_forward!(#type_ident, #crate_ident::UniFfiTag, crate::UniFfiTag); - }.into() +pub fn remote_type(tokens: TokenStream) -> TokenStream { + remote::expand_remote_type(parse_macro_input!(tokens)).into() } /// A helper macro to generate and include component scaffolding. diff --git a/uniffi_macros/src/remote.rs b/uniffi_macros/src/remote.rs new file mode 100644 index 0000000000..8ad88cd04d --- /dev/null +++ b/uniffi_macros/src/remote.rs @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + Ident, Token, Type, +}; + +pub struct RemoteTypeArgs { + pub ty: Type, + pub sep: Token![,], + pub implementing_crate: Ident, +} + +impl Parse for RemoteTypeArgs { + fn parse(input: ParseStream<'_>) -> syn::Result { + Ok(Self { + ty: input.parse()?, + sep: input.parse()?, + implementing_crate: input.parse()?, + }) + } +} + +pub fn expand_remote_type(args: RemoteTypeArgs) -> TokenStream { + let RemoteTypeArgs { + ty, + implementing_crate, + .. + } = args; + let existing_tag = quote! { #implementing_crate::UniFfiTag }; + + quote! { + unsafe impl ::uniffi::FfiConverter for #ty { + type FfiType = <#ty as ::uniffi::FfiConverter<#existing_tag>>::FfiType; + + fn lower(obj: #ty) -> Self::FfiType { + <#ty as ::uniffi::FfiConverter<#existing_tag>>::lower(obj) + } + + fn try_lift(v: Self::FfiType) -> ::uniffi::Result<#ty> { + <#ty as ::uniffi::FfiConverter<#existing_tag>>::try_lift(v) + } + + fn write(obj: #ty, buf: &mut Vec) { + <#ty as ::uniffi::FfiConverter<#existing_tag>>::write(obj, buf) + } + + fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<#ty> { + <#ty as ::uniffi::FfiConverter<#existing_tag>>::try_read(buf) + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = + <#ty as ::uniffi::FfiConverter<#existing_tag>>::TYPE_ID_META; + } + + ::uniffi::derive_ffi_traits!(local #ty); + } +} diff --git a/uniffi_macros/src/setup_scaffolding.rs b/uniffi_macros/src/setup_scaffolding.rs index c82e9389bb..a2221fb956 100644 --- a/uniffi_macros/src/setup_scaffolding.rs +++ b/uniffi_macros/src/setup_scaffolding.rs @@ -115,15 +115,6 @@ pub fn setup_scaffolding(namespace: String) -> Result { } }; } - - // A trait that's in our crate for our external wrapped types to implement. - #[allow(unused)] - #[doc(hidden)] - pub trait UniffiCustomTypeConverter { - type Builtin; - fn into_custom(val: Self::Builtin) -> uniffi::Result where Self: Sized; - fn from_custom(obj: Self) -> Self::Builtin; - } }) } diff --git a/uniffi_macros/src/util.rs b/uniffi_macros/src/util.rs index 6f6214208b..5689278e2c 100644 --- a/uniffi_macros/src/util.rs +++ b/uniffi_macros/src/util.rs @@ -205,33 +205,25 @@ pub fn either_attribute_arg(a: Option, b: Option) -> syn::Res pub(crate) fn tagged_impl_header( trait_name: &str, ident: &impl ToTokens, - udl_mode: bool, + remote: bool, ) -> TokenStream { let trait_name = Ident::new(trait_name, Span::call_site()); - if udl_mode { + if remote { quote! { impl ::uniffi::#trait_name for #ident } } else { quote! { impl ::uniffi::#trait_name for #ident } } } -pub(crate) fn derive_all_ffi_traits(ty: &Ident, udl_mode: bool) -> TokenStream { - if udl_mode { - quote! { ::uniffi::derive_ffi_traits!(local #ty); } - } else { - quote! { ::uniffi::derive_ffi_traits!(blanket #ty); } - } -} - pub(crate) fn derive_ffi_traits( ty: impl ToTokens, - udl_mode: bool, + remote: bool, trait_names: &[&str], ) -> TokenStream { let trait_idents = trait_names .iter() .map(|name| Ident::new(name, Span::call_site())); - if udl_mode { + if remote { quote! { #( ::uniffi::derive_ffi_traits!(impl #trait_idents for #ty); @@ -258,6 +250,9 @@ pub mod kw { syn::custom_keyword!(with_try_read); syn::custom_keyword!(name); syn::custom_keyword!(non_exhaustive); + syn::custom_keyword!(from_custom); + syn::custom_keyword!(try_into_custom); + syn::custom_keyword!(remote); syn::custom_keyword!(Record); syn::custom_keyword!(Enum); syn::custom_keyword!(Error); diff --git a/uniffi_meta/src/lib.rs b/uniffi_meta/src/lib.rs index 90f7b2d3cd..1fce7bb9c3 100644 --- a/uniffi_meta/src/lib.rs +++ b/uniffi_meta/src/lib.rs @@ -297,6 +297,7 @@ pub enum Radix { pub struct RecordMetadata { pub module_path: String, pub name: String, + pub remote: bool, // only used when generating scaffolding from UDL pub fields: Vec, pub docstring: Option, } @@ -313,6 +314,7 @@ pub struct FieldMetadata { pub struct EnumMetadata { pub module_path: String, pub name: String, + pub remote: bool, // only used when generating scaffolding from UDL pub forced_flatness: Option, pub variants: Vec, pub discr_type: Option, @@ -332,6 +334,7 @@ pub struct VariantMetadata { pub struct ObjectMetadata { pub module_path: String, pub name: String, + pub remote: bool, // only used when generating scaffolding from UDL pub imp: types::ObjectImpl, pub docstring: Option, } diff --git a/uniffi_meta/src/reader.rs b/uniffi_meta/src/reader.rs index a48201297e..9b120b7efb 100644 --- a/uniffi_meta/src/reader.rs +++ b/uniffi_meta/src/reader.rs @@ -299,6 +299,7 @@ impl<'a> MetadataReader<'a> { Ok(RecordMetadata { module_path: self.read_string()?, name: self.read_string()?, + remote: false, // only used when generating scaffolding from UDL fields: self.read_fields()?, docstring: self.read_optional_long_string()?, }) @@ -327,6 +328,7 @@ impl<'a> MetadataReader<'a> { Ok(EnumMetadata { module_path, name, + remote: false, // only used when generating scaffolding from UDL forced_flatness, discr_type, variants, @@ -339,6 +341,7 @@ impl<'a> MetadataReader<'a> { Ok(ObjectMetadata { module_path: self.read_string()?, name: self.read_string()?, + remote: false, // only used when generating scaffolding from UDL imp, docstring: self.read_optional_long_string()?, }) diff --git a/uniffi_udl/src/attributes.rs b/uniffi_udl/src/attributes.rs index a40ef29fdb..2f054299d0 100644 --- a/uniffi_udl/src/attributes.rs +++ b/uniffi_udl/src/attributes.rs @@ -37,6 +37,7 @@ pub(super) enum Attribute { kind: ExternalKind, export: bool, }, + Remote, Rust { kind: RustKind, }, @@ -89,6 +90,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttribute<'_>> for Attribute { "WithForeign" => Ok(Attribute::WithForeign), "Async" => Ok(Attribute::Async), "NonExhaustive" => Ok(Attribute::NonExhaustive), + "Remote" => Ok(Attribute::Remote), _ => anyhow::bail!("ExtendedAttributeNoArgs not supported: {:?}", (attr.0).0), }, // Matches assignment-style attributes like ["Throws=Error"] @@ -215,6 +217,41 @@ where Ok(attrs) } +/// Attributes that can be attached to an `dictionary` definition in the UDL. +#[derive(Debug, Clone, Checksum, Default)] +pub(super) struct DictionaryAttributes(Vec); + +impl DictionaryAttributes { + pub fn contains_remote(&self) -> bool { + self.0.iter().any(|attr| matches!(attr, Attribute::Remote)) + } +} + +impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for DictionaryAttributes { + type Error = anyhow::Error; + fn try_from( + weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, + ) -> Result { + let attrs = parse_attributes(weedle_attributes, |attr| match attr { + Attribute::Remote => Ok(()), + _ => bail!(format!("{attr:?} not supported for dictionaries")), + })?; + Ok(Self(attrs)) + } +} + +impl> TryFrom> + for DictionaryAttributes +{ + type Error = anyhow::Error; + fn try_from(value: Option) -> Result { + match value { + None => Ok(Default::default()), + Some(v) => v.try_into(), + } + } +} + /// Attributes that can be attached to an `enum` definition in the UDL. #[derive(Debug, Clone, Checksum, Default)] pub(super) struct EnumAttributes(Vec); @@ -229,6 +266,10 @@ impl EnumAttributes { .iter() .any(|attr| matches!(attr, Attribute::NonExhaustive)) } + + pub fn contains_remote(&self) -> bool { + self.0.iter().any(|attr| matches!(attr, Attribute::Remote)) + } } impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for EnumAttributes { @@ -239,6 +280,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for EnumAttributes { let attrs = parse_attributes(weedle_attributes, |attr| match attr { Attribute::Error => Ok(()), Attribute::NonExhaustive => Ok(()), + Attribute::Remote => Ok(()), // Allow `[Enum]`, since we may be parsing an attribute list from an interface with the // `[Enum]` attribute. Attribute::Enum => Ok(()), @@ -367,6 +409,10 @@ impl InterfaceAttributes { self.0.iter().any(|attr| matches!(attr, Attribute::Trait)) } + pub fn contains_remote(&self) -> bool { + self.0.iter().any(|attr| matches!(attr, Attribute::Remote)) + } + pub fn contains_with_foreign(&self) -> bool { self.0 .iter() @@ -405,6 +451,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for InterfaceAttribu Attribute::Trait => Ok(()), Attribute::WithForeign => Ok(()), Attribute::Traits(_) => Ok(()), + Attribute::Remote => Ok(()), _ => bail!(format!("{attr:?} not supported for interface definition")), })?; if attrs.iter().any(|a| matches!(a, Attribute::Enum)) && attrs.len() != 1 { @@ -894,6 +941,17 @@ mod test { assert!(attrs.object_impl().is_err()) } + #[test] + fn test_dictionary_attributes() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Remote]").unwrap(); + let attrs = DictionaryAttributes::try_from(&node).unwrap(); + assert!(attrs.contains_remote()); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Trait]").unwrap(); + let err = DictionaryAttributes::try_from(&node).unwrap_err(); + assert_eq!(err.to_string(), "Trait not supported for dictionaries"); + } + #[test] fn test_enum_attribute_on_interface() { let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Enum]").unwrap(); @@ -920,10 +978,12 @@ mod test { #[test] fn test_enum_attributes() { let (_, node) = - weedle::attribute::ExtendedAttributeList::parse("[Error, NonExhaustive]").unwrap(); + weedle::attribute::ExtendedAttributeList::parse("[Error, NonExhaustive, Remote]") + .unwrap(); let attrs = EnumAttributes::try_from(&node).unwrap(); assert!(attrs.contains_error_attr()); assert!(attrs.contains_non_exhaustive_attr()); + assert!(attrs.contains_remote()); let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Trait]").unwrap(); let err = EnumAttributes::try_from(&node).unwrap_err(); @@ -937,11 +997,12 @@ mod test { assert!(EnumAttributes::try_from(&node).is_ok()); let (_, node) = - weedle::attribute::ExtendedAttributeList::parse("[Enum, Error, NonExhaustive]") + weedle::attribute::ExtendedAttributeList::parse("[Enum, Error, NonExhaustive, Remote]") .unwrap(); let attrs = EnumAttributes::try_from(&node).unwrap(); assert!(attrs.contains_error_attr()); assert!(attrs.contains_non_exhaustive_attr()); + assert!(attrs.contains_remote()); let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Enum, Trait]").unwrap(); let err = EnumAttributes::try_from(&node).unwrap_err(); @@ -949,7 +1010,11 @@ mod test { } #[test] - fn test_other_attributes_not_supported_for_interfaces() { + fn test_interface_attributes() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Remote]").unwrap(); + let attrs = InterfaceAttributes::try_from(&node).unwrap(); + assert!(attrs.contains_remote()); + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Trait, ByRef]").unwrap(); let err = InterfaceAttributes::try_from(&node).unwrap_err(); assert_eq!( diff --git a/uniffi_udl/src/converters/enum_.rs b/uniffi_udl/src/converters/enum_.rs index 1615a1a7ca..0975f479b7 100644 --- a/uniffi_udl/src/converters/enum_.rs +++ b/uniffi_udl/src/converters/enum_.rs @@ -17,6 +17,7 @@ impl APIConverter for weedle::EnumDefinition<'_> { Ok(EnumMetadata { module_path: ci.module_path(), name: self.identifier.0.to_string(), + remote: attributes.contains_remote(), forced_flatness: None, discr_type: None, variants: self @@ -48,6 +49,7 @@ impl APIConverter for weedle::InterfaceDefinition<'_> { Ok(EnumMetadata { module_path: ci.module_path(), name: self.identifier.0.to_string(), + remote: attributes.contains_remote(), forced_flatness: Some(false), variants: self .members diff --git a/uniffi_udl/src/converters/interface.rs b/uniffi_udl/src/converters/interface.rs index ef9bdd9540..efbeb38b63 100644 --- a/uniffi_udl/src/converters/interface.rs +++ b/uniffi_udl/src/converters/interface.rs @@ -130,6 +130,7 @@ impl APIConverter for weedle::InterfaceDefinition<'_> { Ok(ObjectMetadata { module_path: ci.module_path(), name: object_name.to_string(), + remote: attributes.contains_remote(), imp: object_impl, docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) diff --git a/uniffi_udl/src/converters/mod.rs b/uniffi_udl/src/converters/mod.rs index 195d9cc0b7..0cc949b725 100644 --- a/uniffi_udl/src/converters/mod.rs +++ b/uniffi_udl/src/converters/mod.rs @@ -2,8 +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 crate::literal::convert_default_value; -use crate::InterfaceCollector; +use crate::{attributes::DictionaryAttributes, literal::convert_default_value, InterfaceCollector}; use anyhow::{bail, Result}; use uniffi_meta::{ @@ -92,15 +91,14 @@ impl APIConverter for weedle::interface::OperationInterfaceMemb impl APIConverter for weedle::DictionaryDefinition<'_> { fn convert(&self, ci: &mut InterfaceCollector) -> Result { - if self.attributes.is_some() { - bail!("dictionary attributes are not supported yet"); - } + let attributes = DictionaryAttributes::try_from(self.attributes.as_ref())?; if self.inheritance.is_some() { bail!("dictionary inheritance is not supported"); } Ok(RecordMetadata { module_path: ci.module_path(), name: self.identifier.0.to_string(), + remote: attributes.contains_remote(), fields: self.members.body.convert(ci)?, docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), })