diff --git a/CHANGELOG.md b/CHANGELOG.md index fc49796b08..43427af47e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,12 +16,49 @@ ### What's changed? +- Trait interfaces defined in UDL need to be wrapped with `#[uniffi::trait_interface]`. +- Trait interfaces performance has been improved. If a trait interface handle is passed across the + FFI multiple times, UniFFI will only wrap the object once rather than each time it's passed. +- The `NoPointer` singleton was renamed to `NoHandle` + +### What's new? + +- Rust traits `Display`, `Hash` and `Eq` exposed to Kotlin and Swift [#1817](https://github.com/mozilla/uniffi-rs/pull/1817) +- Foreign types can now implement trait interfaces [#1791](https://github.com/mozilla/uniffi-rs/pull/1791) +- Generated Python code is able to specify a package name for the module [#1784](https://github.com/mozilla/uniffi-rs/pull/1784) +- UDL can describe async function [#1834](https://github.com/mozilla/uniffi-rs/pull/1834) +- UDL files can reference types defined in procmacros in this crate - see + [the external types docs](https://mozilla.github.io/uniffi-rs/udl/ext_types.html) +- Add support for [docstrings in UDL](https://mozilla.github.io/uniffi-rs/udl/docstrings.html) + +### Breaking changes for external bindings + - The `rust_future_continuation_callback_set` FFI function was removed. `rust_future_poll` now inputs the callback pointer. External bindings authors will need to update their code. +- The object handle FFI has changed. External bindings generators will need to update their code to + use the new handle system: + * A single `FfiType::Handle` is used for all object handles. + * `FfiType::Handle` is always a 64-bit int. + * Foreign handles must always set the lowest bit of that int. -### What's new? +[All changes in [[UnreleasedUniFFIVersion]]](https://github.com/mozilla/uniffi-rs/compare/v0.25.2...HEAD). + +## v0.25.2 (backend crates: v0.25.2) - (_2023-11-20_) + +### What's fixed? + +- Fixed regression in the name of error enums in Kotlin [#1842](https://github.com/mozilla/uniffi-rs/pull/1842) +- Fix regression when error types are in dicts etc [#1847](https://github.com/mozilla/uniffi-rs/pull/1847) + +[All changes in v0.25.2](https://github.com/mozilla/uniffi-rs/compare/v0.25.1...v0.25.2). + +## v0.25.1 (backend crates: v0.25.1) - (_2023-11-09_) + +[All changes in v0.25.1](https://github.com/mozilla/uniffi-rs/compare/v0.25.0...v0.25.1). + +### What's fixed? -- Rust traits `Display`, `Hash` and `Eq` exposed to Kotlin and Swift. +- Fixed several bugs with async functions were defined in multiple crates that get built together. ## v0.25.0 (backend crates: v0.25.0) - (_2023-10-18_) @@ -49,7 +86,6 @@ - Error types must now implement `Error + Send + Sync + 'static`. - Proc-macros: The `handle_unknown_callback_error` attribute is no longer needed for callback interface errors -- Foreign types can now implement trait interfaces ### What's Fixed diff --git a/Cargo.lock b/Cargo.lock index a3a018f69b..bd43653b15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -819,16 +819,6 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1314,6 +1304,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "socket2" version = "0.4.9" @@ -1366,6 +1362,17 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.47" @@ -1546,6 +1553,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.22" @@ -1555,9 +1568,15 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "uniffi" -version = "0.25.0" +version = "0.25.2" dependencies = [ "anyhow", "camino", @@ -1676,6 +1695,18 @@ dependencies = [ "uniffi", ] +[[package]] +name = "uniffi-fixture-docstring" +version = "0.22.0" +dependencies = [ + "camino", + "glob", + "thiserror", + "uniffi", + "uniffi_bindgen", + "uniffi_testing", +] + [[package]] name = "uniffi-fixture-ext-types" version = "0.22.0" @@ -1687,6 +1718,7 @@ dependencies = [ "uniffi-fixture-ext-types-external-crate", "uniffi-fixture-ext-types-guid", "uniffi-fixture-ext-types-lib-one", + "uniffi-fixture-ext-types-sub-lib", "url", ] @@ -1737,6 +1769,15 @@ dependencies = [ "url", ] +[[package]] +name = "uniffi-fixture-ext-types-sub-lib" +version = "0.22.0" +dependencies = [ + "anyhow", + "uniffi", + "uniffi-fixture-ext-types-lib-one", +] + [[package]] name = "uniffi-fixture-foreign-executor" version = "0.23.0" @@ -1805,18 +1846,6 @@ dependencies = [ "uniffi", ] -[[package]] -name = "uniffi-fixture-reexport-scaffolding-macro" -version = "0.22.0" -dependencies = [ - "cargo_metadata", - "libloading", - "uniffi", - "uniffi-fixture-callbacks", - "uniffi-fixture-coverall", - "uniffi_bindgen", -] - [[package]] name = "uniffi-fixture-regression-callbacks-omit-labels" version = "0.22.0" @@ -1935,7 +1964,7 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.25.0" +version = "0.25.2" dependencies = [ "anyhow", "askama", @@ -1949,6 +1978,7 @@ dependencies = [ "once_cell", "paste", "serde", + "textwrap", "toml", "uniffi_meta", "uniffi_testing", @@ -1957,7 +1987,7 @@ dependencies = [ [[package]] name = "uniffi_build" -version = "0.25.0" +version = "0.25.2" dependencies = [ "anyhow", "camino", @@ -1966,7 +1996,7 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.25.0" +version = "0.25.2" dependencies = [ "quote", "syn", @@ -1974,7 +2004,7 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.25.0" +version = "0.25.2" dependencies = [ "anyhow", "async-compat", @@ -1989,7 +2019,7 @@ dependencies = [ [[package]] name = "uniffi_macros" -version = "0.25.0" +version = "0.25.2" dependencies = [ "bincode", "camino", @@ -2006,7 +2036,7 @@ dependencies = [ [[package]] name = "uniffi_meta" -version = "0.25.0" +version = "0.25.2" dependencies = [ "anyhow", "bytes", @@ -2016,7 +2046,7 @@ dependencies = [ [[package]] name = "uniffi_testing" -version = "0.25.0" +version = "0.25.2" dependencies = [ "anyhow", "camino", @@ -2027,9 +2057,10 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.25.0" +version = "0.25.2" dependencies = [ "anyhow", + "textwrap", "uniffi_meta", "uniffi_testing", "weedle2", diff --git a/Cargo.toml b/Cargo.toml index 4d919af4a0..387c513fff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,14 +30,15 @@ members = [ "fixtures/ext-types/uniffi-one", "fixtures/ext-types/lib", "fixtures/ext-types/proc-macro-lib", + "fixtures/ext-types/sub-lib", + "fixtures/docstring", "fixtures/foreign-executor", "fixtures/keywords/kotlin", "fixtures/keywords/rust", "fixtures/keywords/swift", "fixtures/metadata", "fixtures/proc-macro", - "fixtures/reexport-scaffolding-macro", "fixtures/regressions/enum-without-i32-helpers", "fixtures/regressions/fully-qualified-types", "fixtures/regressions/kotlin-experimental-unsigned-types", diff --git a/docs/manual/src/SUMMARY.md b/docs/manual/src/SUMMARY.md index ba4704078d..8406e97ad2 100644 --- a/docs/manual/src/SUMMARY.md +++ b/docs/manual/src/SUMMARY.md @@ -19,6 +19,7 @@ - [External Types](./udl/ext_types.md) - [Declaring External Types](./udl/ext_types_external.md) - [Declaring Custom Types](./udl/custom_types.md) + - [Docstrings](./udl/docstrings.md) - [Procedural Macros: Attributes and Derives](./proc_macro/index.md) - [Futures and async support](./futures.md) diff --git a/docs/manual/src/foreign_traits.md b/docs/manual/src/foreign_traits.md index 14c58915c7..74d219cf9b 100644 --- a/docs/manual/src/foreign_traits.md +++ b/docs/manual/src/foreign_traits.md @@ -22,6 +22,7 @@ a [compatible error type](./udl/errors.md) - see below for more on error handlin For example: ```rust,no_run +#[uniffi::trait_interface] pub trait Keychain: Send + Sync + Debug { fn get(&self, key: String) -> Result, KeyChainError>; fn put(&self, key: String, value: String) -> Result<(), KeyChainError>; diff --git a/docs/manual/src/kotlin/configuration.md b/docs/manual/src/kotlin/configuration.md index 6dc98643cf..8d71b6c56e 100644 --- a/docs/manual/src/kotlin/configuration.md +++ b/docs/manual/src/kotlin/configuration.md @@ -8,6 +8,7 @@ The generated Kotlin modules can be configured using a `uniffi.toml` configurati | ------------------ | ------- |------------ | | `package_name` | `uniffi` | The Kotlin package name - ie, the value used in the `package` statement at the top of generated files. | | `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) diff --git a/docs/manual/src/proc_macro/index.md b/docs/manual/src/proc_macro/index.md index 9a53b58f83..04e0ea5049 100644 --- a/docs/manual/src/proc_macro/index.md +++ b/docs/manual/src/proc_macro/index.md @@ -23,7 +23,7 @@ true for all of UniFFI, so proceed with caution and the knowledge that things ma ## Build workflow -Library mode is recommended when using UniFFI proc-macros (See the [Foreign language bindings docs](../tutorial/foreign_language_bindings.md) for more info). +Be sure to use library mode when using UniFFI proc-macros (See the [Foreign language bindings docs](../tutorial/foreign_language_bindings.md) for more info). If your crate's API is declared using only proc-macros and not UDL files, call the `uniffi::setup_scaffolding` macro at the top of your source code: @@ -84,6 +84,8 @@ impl MyObject { // Corresponding UDL: // [Trait] // interface MyTrait {}; +// +// Note: `[uniffi::trait_interface]` is not needed when the trait is exported via a proc-macro. #[uniffi::export] trait MyTrait { // ... diff --git a/docs/manual/src/swift/configuration.md b/docs/manual/src/swift/configuration.md index c56fe5ae15..cac3641afb 100644 --- a/docs/manual/src/swift/configuration.md +++ b/docs/manual/src/swift/configuration.md @@ -12,6 +12,7 @@ The generated Swift module can be configured using a `uniffi.toml` configuration | `ffi_module_filename` | `{ffi_module_name}` | The filename stem for the lower-level C module containing the FFI declarations. | | `generate_module_map` | `true` | Whether to generate a `.modulemap` file for the lower-level C module with FFI declarations. | | `omit_argument_labels` | `false` | Whether to omit argument labels in Swift function definitions. | +| `generate_immutable_records` | `false` | Whether to generate records with immutable fields (`let` instead of `var`). | | `custom_types` | | A map which controls how custom types are exposed to Swift. See the [custom types section of the manual](../udl/custom_types.md#custom-types-in-the-bindings-code)| diff --git a/docs/manual/src/tutorial/Rust_scaffolding.md b/docs/manual/src/tutorial/Rust_scaffolding.md index 32d4c2e78c..89a9011ee0 100644 --- a/docs/manual/src/tutorial/Rust_scaffolding.md +++ b/docs/manual/src/tutorial/Rust_scaffolding.md @@ -14,7 +14,12 @@ uniffi = "0.XX.0" uniffi = { version = "0.XX.0", features = ["build"] } ``` -Then create a `build.rs` file next to `Cargo.toml` that uses `uniffi` to generate the Rust scaffolding code. +As noted in [Describing the interface](udl_file.md), UniFFI currently supports two methods of interface definitions: UDL files and proc macros. +If you are using only proc macros, you can skip some boilerplate in your crate setup as well. + +### Setup for crates using UDL + +Crates using UDL need a `build.rs` file next to `Cargo.toml`. This uses `uniffi` to generate the Rust scaffolding code. ```rust fn main() { @@ -30,7 +35,18 @@ uniffi::include_scaffolding!("math"); **Note:** The file name is always `.uniffi.rs`. -Great! `add` is ready to see the outside world! +### Setup for crates using only proc macros + +If you are only using proc macros, you can skip `build.rs` entirely! +All you need to do is add this to the top of `lib.rs` +NOTE: This function takes an optional parameter, the [`namespace`](../udl/namespace.md) used by the component. +If not specified, the crate name will be used as the namespace. + +```rust +uniffi::setup_scaffolding!(); +``` + +**⚠ Warning ⚠** Do not call both `uniffi::setup_scaffolding!()` and `uniffi::include_scaffolding!!()` in the same crate. ### Libraries that depend on UniFFI components diff --git a/docs/manual/src/tutorial/foreign_language_bindings.md b/docs/manual/src/tutorial/foreign_language_bindings.md index 8970b12744..2325525526 100644 --- a/docs/manual/src/tutorial/foreign_language_bindings.md +++ b/docs/manual/src/tutorial/foreign_language_bindings.md @@ -38,11 +38,11 @@ creating a binary for each crate that uses UniFFI. You can avoid this by creati Then your can run `uniffi-bindgen` from any create in your project using `cargo run -p uniffi-bindgen [args]` -## Running uniffi-bindgen using a library file +## Running uniffi-bindgen using a library file (RECOMMENDED) Use `generate --library` to generate foreign bindings by using a cdylib file built for your library. This flag was added in UniFFI 0.24 and can be more convenient than specifying the UDL file -- especially when multiple UniFFI-ed crates are built together in one library. -The plan is to make library mode the default in a future UniFFI version. +The plan is to make library mode the default in a future UniFFI version, and it is highly recommended to specify the flag for now (because some features simply don't work otherwise). Taking `example/arithmetic` as an example, you can generate the bindings with: ``` diff --git a/docs/manual/src/udl/docstrings.md b/docs/manual/src/udl/docstrings.md new file mode 100644 index 0000000000..7a1055ba94 --- /dev/null +++ b/docs/manual/src/udl/docstrings.md @@ -0,0 +1,86 @@ +# Docstrings + +UDL file supports docstring comments. The comments are emitted in generated bindings without any +transformations. What you see in UDL is what you get in generated bindings. The only change made to +UDL comments are the comment syntax specific to each language. Docstrings can be used for most +declarations in UDL file. Docstrings are parsed as AST nodes, so incorrectly placed docstrings will +generate parse errors. Docstrings in UDL are comments prefixed with `///`. + +## Docstrings in UDL +```java +/// The list of supported capitalization options +enum Capitalization { + /// Lowercase, i.e. `hello, world!` + Lower, + + /// Uppercase, i.e. `Hello, World!` + Upper +}; + +namespace example { + /// Return a greeting message, using `capitalization` for capitalization + string hello_world(Capitalization capitalization); +} +``` + +## Docstrings in generated Kotlin bindings +```kotlin +/** + * The list of supported capitalization options + */ +enum class Capitalization { + /** + * Lowercase, i.e. `hello, world!` + */ + LOWER, + + /** + * Uppercase, i.e. `Hello, World!` + */ + UPPER; +} + +/** + * Return a greeting message, using `capitalization` for capitalization + */ +fun `helloWorld`(`capitalization`: Capitalization): String { .. } +``` + +## Docstrings in generated Swift bindings +```swift +/** + * The list of supported capitalization options + */ +public enum Capitalization { + /** + * Lowercase, i.e. `hello, world!` + */ + case lower + + /** + * Uppercase, i.e. `Hello, World!` + */ + case upper +} + +/** + * Return a greeting message, using `capitalization` for capitalization + */ +public func helloWorld(capitalization: Capitalization) -> String; +``` + +## Docstrings in generated Python bindings +```python +class Capitalization(enum.Enum): + """The list of supported capitalization options""" + + LOWER = 1 + """Lowercase, i.e. `hello, world!`""" + + UPPER = 2 + """Uppercase, i.e. `Hello, World!`""" + +def hello_world(capitalization: "Capitalization") -> "str": + """Return a greeting message, using `capitalization` for capitalization""" + .. +``` diff --git a/docs/manual/src/udl/ext_types.md b/docs/manual/src/udl/ext_types.md index 769bf29ca7..593508ae5b 100644 --- a/docs/manual/src/udl/ext_types.md +++ b/docs/manual/src/udl/ext_types.md @@ -1,16 +1,42 @@ # External types -*Note: The facility described in this document is not yet available for all foreign language -bindings.* +External types are types implemented by UniFFI but outside of this UDL file. -UniFFI supports referring to types defined outside of the UDL file. These types must be -either: +They are similar to, but different from [custom types](./custom_types.md) which wrap UniFFI primitive types. -1) A locally defined type which [wraps a UniFFI primitive type](./custom_types.md). -2) A "UniFFI compatible" type [in another crate](./ext_types_external.md). +But like custom types, external types are all declared using a `typedef` with attributes +giving more detail. -Specifically, "UniFFI compatible" means either a type defined in `udl` in an external crate, or -a type defined in another crate that satisfies (1). +## Types in another crate -These types are all declared using a `typedef`, with attributes specifying how the type is -handled. See the links for details. +[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" +* 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 index 5872a14553..88e0b965fa 100644 --- a/docs/manual/src/udl/ext_types_external.md +++ b/docs/manual/src/udl/ext_types_external.md @@ -1,8 +1,5 @@ # Declaring External Types -*Note: The facility described in this document is not yet available for all foreign language -bindings.* - 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: @@ -13,8 +10,8 @@ dictionary DemoDict { }; ``` -And further, assume that you have another crate called `consuming_crate` which would like to use -this dictionary. Inside `consuming_crate`'s UDL file you can reference `DemoDict` by using a +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 @@ -29,12 +26,6 @@ dictionary ConsumingDict { ``` -If in the above example, `DemoDict` was actually "exported" via a procmacro -you should instead use `ExternExport` - -(Note the special type `extern` used on the `typedef`. It is not currently enforced that the -literal `extern` is used, but we hope to enforce this later, so please use it!) - Inside `consuming_crate`'s Rust code you must `use` that struct as normal - for example, `consuming_crate`'s `lib.rs` might look like: @@ -51,7 +42,7 @@ uniffi::include_scaffolding!("consuming_crate"); Your `Cargo.toml` must reference the external crate as normal. -The `External` attribute can be specified on dictionaries, enums and errors. +The `External` attribute can be specified on dictionaries, enums, errors. ## External interface types @@ -62,6 +53,15 @@ If the external type is an [Interface](./interfaces.md), then use the `[External typedef extern DemoInterface; ``` +## 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 []`[Rust=...]` attribute](../ext_types.md) + ## Foreign bindings The foreign bindings will also need to know how to access the external type, diff --git a/docs/manual/src/udl/interfaces.md b/docs/manual/src/udl/interfaces.md index 23db54a8d8..4a7fbd5a3d 100644 --- a/docs/manual/src/udl/interfaces.md +++ b/docs/manual/src/udl/interfaces.md @@ -87,9 +87,10 @@ interface Button { ``` -With the following Rust implementation: +The Rust implementation needs to be wrapped in `#[uniffi::trait_interface]`: ```rust +#[uniffi::trait_interface] pub trait Button: Send + Sync { fn name(&self) -> String; } diff --git a/examples/callbacks/src/lib.rs b/examples/callbacks/src/lib.rs index dac5653d1b..677f3b474b 100644 --- a/examples/callbacks/src/lib.rs +++ b/examples/callbacks/src/lib.rs @@ -21,6 +21,7 @@ impl From for TelephoneError { } // SIM cards. +#[uniffi::trait_interface] pub trait SimCard: Send + Sync { fn name(&self) -> String; } diff --git a/examples/traits/src/lib.rs b/examples/traits/src/lib.rs index 9d84bdae7f..51d81d83d8 100644 --- a/examples/traits/src/lib.rs +++ b/examples/traits/src/lib.rs @@ -13,6 +13,7 @@ fn press(button: Arc) -> Arc { button } +#[uniffi::trait_interface] pub trait Button: Send + Sync { fn name(&self) -> String; } diff --git a/fixtures/coverall/src/coverall.udl b/fixtures/coverall/src/coverall.udl index b59d4bce7d..578cfd6b82 100644 --- a/fixtures/coverall/src/coverall.udl +++ b/fixtures/coverall/src/coverall.udl @@ -22,6 +22,14 @@ namespace coverall { void test_getters(Getters g); sequence ancestor_names(NodeTrait node); + + ReturnOnlyDict output_return_only_dict(); + ReturnOnlyEnum output_return_only_enum(); + + void try_input_return_only_dict(ReturnOnlyDict d); + + Getters test_round_trip_through_rust(Getters getters); + void test_round_trip_through_foreign(Getters getters); }; dictionary SimpleDict { @@ -49,6 +57,21 @@ dictionary SimpleDict { NodeTrait? test_trait; }; +// Create a type that stores `CoverallFlatError` and therefore can only be lowered but not lifted. +// UniFFI should define a `Lower` implemenation but not try to define `Lift`. +dictionary ReturnOnlyDict { + CoverallFlatError e; +}; + +// More complicated version of the above, each variant is return-only for different reasons +[Enum] +interface ReturnOnlyEnum { + One(CoverallFlatError e); + Two(ReturnOnlyDict d); + Three(sequence l); + Four(record m); +}; + dictionary DictWithDefaults { string name = "default-value"; string? category = null; @@ -209,6 +232,7 @@ interface Getters { string? get_option(string v, boolean arg2); sequence get_list(sequence v, boolean arg2); void get_nothing(string v); + Coveralls round_trip_object(Coveralls coveralls); }; // Test trait #2 diff --git a/fixtures/coverall/src/lib.rs b/fixtures/coverall/src/lib.rs index 9916dadb67..301513de04 100644 --- a/fixtures/coverall/src/lib.rs +++ b/fixtures/coverall/src/lib.rs @@ -10,7 +10,10 @@ use std::time::SystemTime; use once_cell::sync::Lazy; mod traits; -pub use traits::{ancestor_names, get_traits, make_rust_getters, test_getters, Getters, NodeTrait}; +pub use traits::{ + ancestor_names, get_traits, make_rust_getters, test_getters, test_round_trip_through_foreign, + test_round_trip_through_rust, Getters, NodeTrait, +}; static NUM_ALIVE: Lazy> = Lazy::new(|| RwLock::new(0)); @@ -80,7 +83,7 @@ impl From for CoverallError { } } -#[derive(Debug, thiserror::Error, PartialEq, Eq)] +#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)] pub enum ComplexError { #[error("OsError: {code} ({extended_code})")] OsError { code: i16, extended_code: i16 }, @@ -108,6 +111,63 @@ fn throw_complex_macro_error() -> Result<(), ComplexMacroError> { }) } +// Note: intentionally *does not* derive `uniffi::Error`, yet ends with `Error`, just to +// mess with Kotlin etc. +#[derive(Clone, Debug, uniffi::Enum)] +pub enum OtherError { + Unexpected, +} + +#[derive(Clone, Debug, thiserror::Error, uniffi::Error)] +pub enum RootError { + #[error(transparent)] + // XXX - note Kotlin fails if this variant was called ComplexError + // (ie, the variant name can't match an existing type) + Complex { + #[from] + error: ComplexError, + }, + #[error("Other Error")] + Other { error: OtherError }, +} + +// For Kotlin, we throw a variant which itself is a plain enum. +#[uniffi::export] +fn throw_root_error() -> Result<(), RootError> { + Err(RootError::Complex { + error: ComplexError::OsError { + code: 1, + extended_code: 2, + }, + }) +} + +#[uniffi::export] +fn get_root_error() -> RootError { + RootError::Other { + error: OtherError::Unexpected, + } +} + +#[uniffi::export] +fn get_complex_error(e: Option) -> ComplexError { + e.unwrap_or(ComplexError::PermissionDenied { + reason: "too complex".to_string(), + }) +} + +#[uniffi::export] +fn get_error_dict(d: Option) -> ErrorDict { + d.unwrap_or(Default::default()) +} + +#[derive(Default, Debug, uniffi::Record)] +pub struct ErrorDict { + complex_error: Option, + root_error: Option, + errors: Vec, +} + #[derive(Clone, Debug, Default)] pub struct SimpleDict { text: String, @@ -134,6 +194,44 @@ pub struct SimpleDict { test_trait: Option>, } +pub struct ReturnOnlyDict { + e: CoverallFlatError, +} + +pub enum ReturnOnlyEnum { + One { + e: CoverallFlatError, + }, + Two { + d: ReturnOnlyDict, + }, + Three { + l: Vec, + }, + Four { + m: HashMap, + }, +} + +fn output_return_only_dict() -> ReturnOnlyDict { + ReturnOnlyDict { + e: CoverallFlatError::TooManyVariants { num: 1 }, + } +} + +fn output_return_only_enum() -> ReturnOnlyEnum { + ReturnOnlyEnum::One { + e: CoverallFlatError::TooManyVariants { num: 2 }, + } +} + +fn try_input_return_only_dict(_d: ReturnOnlyDict) { + // This function can't work because ReturnOnlyDict contains a flat error and therefore + // can't be lifted by Rust. There's a Python test that the UniFFI code panics before we get here. + // + // FIXME: should be a compile-time error rather than a runtime error (#1850) +} + #[derive(Debug, Clone)] pub struct DictWithDefaults { name: String, diff --git a/fixtures/coverall/src/traits.rs b/fixtures/coverall/src/traits.rs index 15785ef0c6..bbb3829b1b 100644 --- a/fixtures/coverall/src/traits.rs +++ b/fixtures/coverall/src/traits.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use super::{ComplexError, CoverallError}; +use super::{ComplexError, CoverallError, Coveralls}; use std::sync::{Arc, Mutex}; // namespace functions. @@ -10,6 +10,7 @@ pub fn get_traits() -> Vec> { vec![Arc::new(Trait1::default()), Arc::new(Trait2::default())] } +#[uniffi::trait_interface] pub trait NodeTrait: Send + Sync + std::fmt::Debug { fn name(&self) -> String; @@ -35,12 +36,23 @@ pub fn ancestor_names(node: Arc) -> Vec { /// Test trait /// /// The goal here is to test all possible arg, return, and error types. +#[uniffi::trait_interface] pub trait Getters: Send + Sync { fn get_bool(&self, v: bool, arg2: bool) -> bool; fn get_string(&self, v: String, arg2: bool) -> Result; fn get_option(&self, v: String, arg2: bool) -> Result, ComplexError>; fn get_list(&self, v: Vec, arg2: bool) -> Vec; fn get_nothing(&self, v: String); + fn round_trip_object(&self, coveralls: Arc) -> Arc; +} + +pub fn test_round_trip_through_rust(getters: Arc) -> Arc { + getters +} + +pub fn test_round_trip_through_foreign(getters: Arc) { + let coveralls = getters.round_trip_object(Arc::new(Coveralls::new("round-trip".to_owned()))); + assert_eq!(coveralls.get_name(), "round-trip"); } struct RustGetters; @@ -90,6 +102,10 @@ impl Getters for RustGetters { } fn get_nothing(&self, _v: String) {} + + fn round_trip_object(&self, coveralls: Arc) -> Arc { + coveralls + } } pub fn make_rust_getters() -> Arc { diff --git a/fixtures/coverall/tests/bindings/test_coverall.kts b/fixtures/coverall/tests/bindings/test_coverall.kts index a607fb4db8..bb8e82e9ca 100644 --- a/fixtures/coverall/tests/bindings/test_coverall.kts +++ b/fixtures/coverall/tests/bindings/test_coverall.kts @@ -202,6 +202,24 @@ Coveralls("test_complex_errors").use { coveralls -> } } +Coveralls("test_error_values").use { _coveralls -> + try { + throwRootError() + throw RuntimeException("Expected method to throw exception") + } catch(e: RootException.Complex) { + assert(e.error is ComplexException.OsException) + } + val e = getRootError() + if (e is RootException.Other) + assert(e.error == OtherError.UNEXPECTED) { + } else { + throw RuntimeException("Unexpected error subclass") + } + val ce = getComplexError(null) + assert(ce is ComplexException.PermissionDenied) + assert(getErrorDict(null).complexError == null) +} + Coveralls("test_interfaces_in_dicts").use { coveralls -> coveralls.addPatch(Patch(Color.RED)) coveralls.addRepair( @@ -262,6 +280,10 @@ class KotlinGetters : Getters { @Suppress("UNUSED_PARAMETER") override fun getNothing(v: String) = Unit + + override fun roundTripObject(coveralls: Coveralls): Coveralls { + return coveralls + } } // Test traits implemented in Rust @@ -349,12 +371,10 @@ getTraits().let { traits -> assert(traits[1].name() == "node-2") assert(traits[1].strongCount() == 2UL) - // Note: this doesn't increase the Rust strong count, since we wrap the Rust impl with a - // Swift impl before passing it to `setParent()` traits[0].setParent(traits[1]) assert(ancestorNames(traits[0]) == listOf("node-2")) assert(ancestorNames(traits[1]).isEmpty()) - assert(traits[1].strongCount() == 2UL) + assert(traits[1].strongCount() == 3UL) assert(traits[0].getParent()!!.name() == "node-2") val ktNode = KotlinNode() @@ -363,6 +383,10 @@ getTraits().let { traits -> assert(ancestorNames(traits[1]) == listOf("node-kt")) assert(ancestorNames(ktNode) == listOf()) + // If we get the node back from Rust, we should get the original object, not an object that's + // been wrapped again. + assert(traits[1].getParent() === ktNode) + traits[1].setParent(null) ktNode.setParent(traits[0]) assert(ancestorNames(ktNode) == listOf("node-1", "node-2")) @@ -377,6 +401,13 @@ getTraits().let { traits -> // not possible through the `NodeTrait` interface (see #1787). } +makeRustGetters().let { rustGetters -> + // Check that these don't cause use-after-free bugs + testRoundTripThroughRust(rustGetters) + + testRoundTripThroughForeign(KotlinGetters()) +} + // This tests that the UniFFI-generated scaffolding doesn't introduce any unexpected locking. // We have one thread busy-wait for a some period of time, while a second thread repeatedly // increments the counter and then checks if the object is still busy. The second thread should @@ -428,11 +459,11 @@ Coveralls("test_bytes").use { coveralls -> // Test fakes using open classes -class FakePatch(private val color: Color): Patch(NoPointer) { +class FakePatch(private val color: Color): Patch(NoHandle) { override fun `getColor`(): Color = color } -class FakeCoveralls(private val name: String) : Coveralls(NoPointer) { +class FakeCoveralls(private val name: String) : Coveralls(NoHandle) { private val repairs = mutableListOf() override fun `addPatch`(patch: Patch) { diff --git a/fixtures/coverall/tests/bindings/test_coverall.py b/fixtures/coverall/tests/bindings/test_coverall.py index 17593bc833..9dcbe4293a 100644 --- a/fixtures/coverall/tests/bindings/test_coverall.py +++ b/fixtures/coverall/tests/bindings/test_coverall.py @@ -4,6 +4,7 @@ import unittest from datetime import datetime, timezone + from coverall import * class TestCoverall(unittest.TestCase): @@ -179,6 +180,17 @@ def test_complex_errors(self): with self.assertRaises(InternalError) as cm: coveralls.maybe_throw_complex(4) + def test_error_values(self): + with self.assertRaises(RootError.Complex) as cm: + throw_root_error() + self.assertEqual(cm.exception.error.code, 1) + + e = get_root_error() + self.assertEqual(e.error, OtherError.UNEXPECTED) + + self.assertTrue(isinstance(get_complex_error(None), ComplexError.PermissionDenied)) + self.assertIsNone(get_error_dict(None).complex_error) + def test_enums(self): e = get_simple_flat_macro_enum(0) self.assertTrue(isinstance(e, SimpleFlatMacroEnum.FIRST)) @@ -278,6 +290,13 @@ def test_bytes(self): coveralls = Coveralls("test_bytes") self.assertEqual(coveralls.reverse(b"123"), b"321") + def test_return_only_dict(self): + # try_input_return_only_dict can never work, since ReturnOnlyDict should only be returned + # from Rust not inputted. Test that an attempt raises an internal error rather than tries + # to use an invalid value. + with self.assertRaises(InternalError): + try_input_return_only_dict(ReturnOnlyDict(e=CoverallFlatError.TooManyVariants)) + class PyGetters: def get_bool(self, v, arg2): return v ^ arg2 @@ -314,6 +333,9 @@ def get_list(self, v, arg2): def get_nothing(self, _v): return None + def round_trip_object(self, coveralls): + return coveralls + class PyNode: def __init__(self): self.parent = None @@ -332,14 +354,14 @@ def strong_count(self): class TraitsTest(unittest.TestCase): # Test traits implemented in Rust - # def test_rust_getters(self): - # test_getters(None) - # self.check_getters_from_python(make_rust_getters()) + def test_rust_getters(self): + test_getters(make_rust_getters()) + self.check_getters_from_python(make_rust_getters()) - # Test traits implemented in Rust + # Test traits implemented in Python def test_python_getters(self): test_getters(PyGetters()) - #self.check_getters_from_python(PyGetters()) + self.check_getters_from_python(PyGetters()) def check_getters_from_python(self, getters): self.assertEqual(getters.get_bool(True, True), False); @@ -370,7 +392,11 @@ def check_getters_from_python(self, getters): with self.assertRaises(ComplexError.UnknownError): getters.get_option("unknown-error", True) - with self.assertRaises(InternalError): + # If the trait is implmented in Rust, we should see an `InternalError`. + + # If it's implemented in Python, we see a `RuntimeError` since it's a direct call with no + # UniFFI wrapping. + with self.assertRaises((InternalError, RuntimeError)): getters.get_string("unexpected-error", True) def test_path(self): @@ -386,9 +412,7 @@ def test_path(self): # Let's try connecting them together traits[0].set_parent(traits[1]) - # Note: this doesn't increase the Rust strong count, since we wrap the Rust impl with a - # python impl before passing it to `set_parent()` - self.assertEqual(traits[1].strong_count(), 2) + self.assertEqual(traits[1].strong_count(), 3) self.assertEqual(ancestor_names(traits[0]), ["node-2"]) self.assertEqual(ancestor_names(traits[1]), []) self.assertEqual(traits[0].get_parent().name(), "node-2") @@ -401,6 +425,10 @@ def test_path(self): self.assertEqual(ancestor_names(traits[1]), ["node-py"]) self.assertEqual(ancestor_names(py_node), []) + # If we get the node back from Rust, we should get the original object, not an object that's + # been wrapped again. + self.assertIs(traits[1].get_parent(), py_node) + # Rotating things. # The ancestry chain now goes py_node -> traits[0] -> traits[1] traits[1].set_parent(None) @@ -413,5 +441,13 @@ def test_path(self): py_node.set_parent(None) traits[0].set_parent(None) + def test_round_tripping(self): + rust_getters = make_rust_getters(); + coveralls = Coveralls("test_round_tripping") + # Check that these don't cause use-after-free bugs + test_round_trip_through_rust(rust_getters) + + test_round_trip_through_foreign(PyGetters()) + if __name__=='__main__': unittest.main() diff --git a/fixtures/coverall/tests/bindings/test_coverall.swift b/fixtures/coverall/tests/bindings/test_coverall.swift index c6fcba4290..e7a0b8e6c1 100644 --- a/fixtures/coverall/tests/bindings/test_coverall.swift +++ b/fixtures/coverall/tests/bindings/test_coverall.swift @@ -179,6 +179,38 @@ do { } +// Test error values, including error enums with error variants. +do { + do { + let _ = try throwRootError() + fatalError("should have thrown") + } catch let e as RootError { + if case let .Complex(error) = e { + if case let .OsError(code, extendedCode) = error { + assert(code == 1) + assert(extendedCode == 2) + } else { + fatalError("wrong error variant: \(e)") + } + } else { + fatalError("wrong error variant: \(e)") + } + } + let e = getRootError(); + if case let .Other(error) = e { + assert(error == OtherError.unexpected) + } else { + fatalError("wrong error variant: \(e)") + } + let e2 = getComplexError(e: nil); + if case let .PermissionDenied(error) = e2 { + assert(error == "too complex") + } else { + fatalError("wrong error variant: \(e)") + } + assert(getErrorDict(d: nil).complexError == nil) +} + // Swift GC is deterministic, `coveralls` is freed when it goes out of scope. assert(getNumAlive() == 0); @@ -283,6 +315,10 @@ class SwiftGetters: Getters { func getList(v: [Int32], arg2: Bool) -> [Int32] { arg2 ? v : [] } func getNothing(v: String) -> () { } + + func roundTripObject(coveralls: Coveralls) -> Coveralls { + return coveralls + } } @@ -384,12 +420,10 @@ do { assert(traits[1].name() == "node-2") assert(traits[1].strongCount() == 2) - // Note: this doesn't increase the Rust strong count, since we wrap the Rust impl with a - // Swift impl before passing it to `set_parent()` traits[0].setParent(parent: traits[1]) assert(ancestorNames(node: traits[0]) == ["node-2"]) assert(ancestorNames(node: traits[1]) == []) - assert(traits[1].strongCount() == 2) + assert(traits[1].strongCount() == 3) assert(traits[0].getParent()!.name() == "node-2") // Throw in a Swift implementation of the trait @@ -400,6 +434,10 @@ do { assert(ancestorNames(node: traits[1]) == ["node-swift"]) assert(ancestorNames(node: swiftNode) == []) + // If we get the node back from Rust, we should get the original object, not an object that's + // been wrapped again. + assert(traits[1].getParent() === swiftNode) + // Rotating things. // The ancestry chain now goes swiftNode -> traits[0] -> traits[1] traits[1].setParent(parent: nil) @@ -412,3 +450,12 @@ do { swiftNode.setParent(parent: nil) traits[0].setParent(parent: nil) } + +// Test round tripping +do { + let rustGetters = makeRustGetters() + // Check that these don't cause use-after-free bugs + let _ = testRoundTripThroughRust(getters: rustGetters) + + testRoundTripThroughForeign(getters: SwiftGetters()) +} diff --git a/fixtures/docstring/Cargo.toml b/fixtures/docstring/Cargo.toml new file mode 100644 index 0000000000..cfbf710c11 --- /dev/null +++ b/fixtures/docstring/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "uniffi-fixture-docstring" +version = "0.22.0" +authors = ["Firefox Sync Team "] +edition = "2021" +license = "MPL-2.0" +publish = false + +[lib] +name = "uniffi_fixture_docstring" +crate-type = ["lib", "cdylib"] + +[dependencies] +thiserror = "1.0" +uniffi = { path = "../../uniffi" } + +[build-dependencies] +uniffi = {path = "../../uniffi", features = ["build"] } + +[dev-dependencies] +camino = "1.0.8" +glob = "0.3" +uniffi = { path = "../../uniffi", features = ["bindgen-tests"] } +uniffi_bindgen = { path = "../../uniffi_bindgen" } +uniffi_testing = { path = "../../uniffi_testing" } diff --git a/fixtures/docstring/README.md b/fixtures/docstring/README.md new file mode 100644 index 0000000000..fb480fd0f3 --- /dev/null +++ b/fixtures/docstring/README.md @@ -0,0 +1,3 @@ +# A basic test for uniffi components + +This test covers docstrings. \ No newline at end of file diff --git a/fixtures/docstring/build.rs b/fixtures/docstring/build.rs new file mode 100644 index 0000000000..00d0e9107d --- /dev/null +++ b/fixtures/docstring/build.rs @@ -0,0 +1,7 @@ +/* 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/docstring.udl").unwrap(); +} diff --git a/fixtures/docstring/src/docstring.udl b/fixtures/docstring/src/docstring.udl new file mode 100644 index 0000000000..74963f9121 --- /dev/null +++ b/fixtures/docstring/src/docstring.udl @@ -0,0 +1,67 @@ +/// +namespace uniffi_docstring { + /// + void test(); + + void test_without_docstring(); +}; + +/// +enum EnumTest { + /// + "One", + /// + "Two" +}; + +/// +[Enum] +interface AssociatedEnumTest { + /// + Test(i16 code); + /// + Test2(i16 code); +}; + +/// +[Error] +enum ErrorTest { + /// + "One", + /// + "Two", +}; + +/// +[Error] +interface AssociatedErrorTest { + /// + Test(i16 code); + /// + Test2(i16 code); +}; + +/// +interface ObjectTest { + /// + constructor(); + + /// + [Name="new_alternate"] + constructor(); + + /// + void test(); +}; + +/// +dictionary RecordTest { + /// + i32 test; +}; + +/// +callback interface CallbackTest { + /// + void test(); +}; diff --git a/fixtures/docstring/src/lib.rs b/fixtures/docstring/src/lib.rs new file mode 100644 index 0000000000..0b8513478e --- /dev/null +++ b/fixtures/docstring/src/lib.rs @@ -0,0 +1,60 @@ +/* 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/. */ + +enum EnumTest { + One, + Two, +} + +pub enum AssociatedEnumTest { + Test { code: i16 }, + Test2 { code: i16 }, +} + +#[derive(Debug, thiserror::Error)] +enum ErrorTest { + #[error("Test")] + One, + #[error("Two")] + Two, +} + +#[derive(Debug, thiserror::Error)] +enum AssociatedErrorTest { + #[error("Test")] + Test { code: i16 }, + #[error("Test2")] + Test2 { code: i16 }, +} + +struct ObjectTest {} + +impl ObjectTest { + pub fn new() -> Self { + ObjectTest {} + } + + pub fn new_alternate() -> Self { + ObjectTest {} + } + + pub fn test(&self) {} +} + +struct RecordTest { + test: i32, +} + +pub fn test() { + let _ = ErrorTest::One; + let _ = ErrorTest::Two; +} + +pub fn test_without_docstring() {} + +pub trait CallbackTest { + fn test(&self); +} + +uniffi::include_scaffolding!("docstring"); diff --git a/fixtures/docstring/tests/bindings/test_docstring.kts b/fixtures/docstring/tests/bindings/test_docstring.kts new file mode 100644 index 0000000000..cf6e80724d --- /dev/null +++ b/fixtures/docstring/tests/bindings/test_docstring.kts @@ -0,0 +1,35 @@ +/* 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/. */ + +// This test ensures the generated code works as expected when documentation comments are generated. +// Note: we do not check for existence of the doc comments here, as they are not programmatically +// exposed to the code. +// https://github.com/mozilla/uniffi-rs/pull/1493#discussion_r1375337478 + +import uniffi.fixture.docstring.*; + +test() + +EnumTest.ONE +EnumTest.TWO + +AssociatedEnumTest.Test(0) +AssociatedEnumTest.Test2(0) + +ErrorTest.One("hello") +ErrorTest.Two("hello") + +AssociatedErrorTest.Test(0) +AssociatedErrorTest.Test2(0) + +val obj1 = ObjectTest +val obj2 = ObjectTest.newAlternate() +obj2.test() + +val rec = RecordTest(123) +val recField = rec.test + +class CallbackImpls() : CallbackTest { + override fun test() {} +} diff --git a/fixtures/docstring/tests/bindings/test_docstring.py b/fixtures/docstring/tests/bindings/test_docstring.py new file mode 100644 index 0000000000..2a029e8a23 --- /dev/null +++ b/fixtures/docstring/tests/bindings/test_docstring.py @@ -0,0 +1,52 @@ +# 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/. + +# Test namespace +import uniffi_docstring +assert uniffi_docstring.__doc__ + +from uniffi_docstring import * + +# Test function +assert test.__doc__ == "" +assert test_without_docstring.__doc__ is None + +# Test enums +assert EnumTest.__doc__ == "" + +# Simple enum variants can't be tested, because `__doc__` is not supported for enums +# assert EnumTest.ONE.__doc__ == "" +# assert EnumTest.TWO.__doc__ == "" + +assert AssociatedEnumTest.__doc__ == "" + +# `__doc__` is lost because of how enum templates are generated +# https://github.com/mozilla/uniffi-rs/blob/eb97592f8c48a7f5cf02a94662b8b7861a6544f3/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py#L60 +# assert AssociatedEnumTest.TEST.__doc__ == "" +# assert AssociatedEnumTest.TEST2.__doc__ == "" + +# Test errors +assert ErrorTest.__doc__ == "" +assert ErrorTest.One.__doc__ == "" +assert ErrorTest.Two.__doc__ == "" + +assert AssociatedErrorTest.__doc__ == "" +assert AssociatedErrorTest.Test.__doc__ == "" +assert AssociatedErrorTest.Test2.__doc__ == "" + +# Test objects +assert ObjectTest.__doc__ == "" +assert ObjectTest.__init__.__doc__ == "" +assert ObjectTest.new_alternate.__doc__ == "" +assert ObjectTest.test.__doc__ == "" + +# Test records +assert RecordTest.__doc__ == "" + +# `__doc__` is not supported for class fields +# assert RecordTest.test.__doc__ == "" + +# Test callbacks +assert CallbackTest.__doc__ == "" +assert CallbackTest.test.__doc__ == "" diff --git a/fixtures/docstring/tests/bindings/test_docstring.swift b/fixtures/docstring/tests/bindings/test_docstring.swift new file mode 100644 index 0000000000..36d1244c55 --- /dev/null +++ b/fixtures/docstring/tests/bindings/test_docstring.swift @@ -0,0 +1,36 @@ +/* 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/. */ + +// This test ensures the generated code works as expected when documentation comments are generated. +// Note: we do not check for existence of the doc comments here, as they are not programmatically +// exposed to the code. +// https://github.com/mozilla/uniffi-rs/pull/1493#discussion_r1375337478 + +import uniffi_docstring + +test() + +var _ = EnumTest.one +var _ = EnumTest.two + +var _ = AssociatedEnumTest.test(code: 0) +var _ = AssociatedEnumTest.test2(code: 0) + +var _ = ErrorTest.One(message: "hello") +var _ = ErrorTest.Two(message: "hello") + +var _ = AssociatedErrorTest.Test(code: 0) +var _ = AssociatedErrorTest.Test2(code: 0) + +var obj1 = ObjectTest() +var obj2 = ObjectTest.newAlternate() +obj2.test() + +var rec = RecordTest(test: 123) +var recField = rec.test + +class CallbackImpls: CallbackTest { + func test() {} +} + diff --git a/fixtures/docstring/tests/test_generated_bindings.rs b/fixtures/docstring/tests/test_generated_bindings.rs new file mode 100644 index 0000000000..765580c18c --- /dev/null +++ b/fixtures/docstring/tests/test_generated_bindings.rs @@ -0,0 +1,98 @@ +uniffi::build_foreign_language_testcases!( + "tests/bindings/test_docstring.kts", + "tests/bindings/test_docstring.swift", + "tests/bindings/test_docstring.py", +); + +#[cfg(test)] +mod tests { + use camino::Utf8PathBuf; + use uniffi_bindgen::bindings::TargetLanguage; + use uniffi_testing::UniFFITestHelper; + + const DOCSTRINGS: &[&str] = &[ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ]; + + fn test_docstring(language: TargetLanguage, file_extension: &str) { + let test_helper = UniFFITestHelper::new(std::env!("CARGO_PKG_NAME")).unwrap(); + + let out_dir = test_helper + .create_out_dir( + std::env!("CARGO_TARGET_TMPDIR"), + format!("test-docstring-{}", language), + ) + .unwrap(); + + uniffi_bindgen::generate_bindings( + &Utf8PathBuf::from("src/docstring.udl"), + None, + vec![language], + Some(&out_dir), + None, + None, + false, + ) + .unwrap(); + + let glob_pattern = out_dir.join(format!("**/*.{}", file_extension)); + + let sources = glob::glob(glob_pattern.as_str()) + .unwrap() + .flatten() + .map(|p| String::from(p.to_string_lossy())) + .collect::>(); + + assert_eq!(sources.len(), 1); + + let bindings_source = std::fs::read_to_string(&sources[0]).unwrap(); + + let expected: Vec = vec![]; + assert_eq!( + expected, + DOCSTRINGS + .iter() + .filter(|v| !bindings_source.contains(*v)) + .map(|v| v.to_string()) + .collect::>(), + "docstrings not found in {}", + &sources[0] + ); + } + + #[test] + fn test_docstring_kotlin() { + test_docstring(TargetLanguage::Kotlin, "kt"); + } + + #[test] + fn test_docstring_python() { + test_docstring(TargetLanguage::Python, "py"); + } + + #[test] + fn test_docstring_swift() { + test_docstring(TargetLanguage::Swift, "swift"); + } +} diff --git a/fixtures/docstring/uniffi.toml b/fixtures/docstring/uniffi.toml new file mode 100644 index 0000000000..eccf016c8d --- /dev/null +++ b/fixtures/docstring/uniffi.toml @@ -0,0 +1,9 @@ +[bindings.kotlin] +package_name = "uniffi.fixture.docstring" +cdylib_name = "uniffi_fixture_docstring" + +[bindings.python] +cdylib_name = "uniffi_fixture_docstring" + +[bindings.swift] +cdylib_name = "uniffi_fixture_docstring" diff --git a/fixtures/ext-types/lib/Cargo.toml b/fixtures/ext-types/lib/Cargo.toml index 46351fbf8f..40a8f72bf4 100644 --- a/fixtures/ext-types/lib/Cargo.toml +++ b/fixtures/ext-types/lib/Cargo.toml @@ -11,6 +11,7 @@ external-crates = [ "uniffi-fixture-ext-types-guid", "uniffi-fixture-ext-types-lib-one", "uniffi-fixture-ext-types-external-crate", + "uniffi-fixture-ext-types-sub-lib", "uniffi-example-custom-types", ] @@ -26,6 +27,7 @@ uniffi = {path = "../../../uniffi", version = "0.25" } uniffi-fixture-ext-types-external-crate = {path = "../external-crate"} uniffi-fixture-ext-types-lib-one = {path = "../uniffi-one"} uniffi-fixture-ext-types-guid = {path = "../guid"} +uniffi-fixture-ext-types-sub-lib = {path = "../sub-lib"} # Reuse one of our examples. uniffi-example-custom-types = {path = "../../../examples/custom-types"} diff --git a/fixtures/ext-types/lib/src/lib.rs b/fixtures/ext-types/lib/src/lib.rs index c9dc4fe02a..4ace18b86d 100644 --- a/fixtures/ext-types/lib/src/lib.rs +++ b/fixtures/ext-types/lib/src/lib.rs @@ -2,7 +2,10 @@ use custom_types::Handle; use ext_types_external_crate::{ExternalCrateDictionary, ExternalCrateInterface}; use ext_types_guid::Guid; use std::sync::Arc; -use uniffi_one::{UniffiOneEnum, UniffiOneInterface, UniffiOneProcMacroType, UniffiOneType}; +use uniffi_one::{ + UniffiOneEnum, UniffiOneInterface, UniffiOneProcMacroType, UniffiOneTrait, UniffiOneType, +}; +use uniffi_sublib::SubLibType; use url::Url; pub struct CombinedType { @@ -58,6 +61,23 @@ fn get_combined_type(existing: Option) -> CombinedType { }) } +// Not part of CombinedType as (a) object refs prevent equality testing and +// (b) it's not currently possible to refer to external traits in UDL. +#[derive(Default, uniffi::Record)] +pub struct ObjectsType { + pub maybe_trait: Option>, + // XXX - can't refer to UniffiOneInterface here - #1854 + //pub maybe_interface: Option>, + // Use this in the meantime so the tests can still refer to it. + pub maybe_interface: Option, + pub sub: SubLibType, +} + +#[uniffi::export] +fn get_objects_type(value: Option) -> ObjectsType { + value.unwrap_or_default() +} + // A Custom type fn get_url(url: Url) -> Url { url @@ -113,6 +133,11 @@ fn get_uniffi_one_interface() -> Arc { Arc::new(UniffiOneInterface::new()) } +#[uniffi::export] +fn get_uniffi_one_trait(t: Option>) -> Option> { + t +} + fn get_uniffi_one_proc_macro_type(t: UniffiOneProcMacroType) -> UniffiOneProcMacroType { t } diff --git a/fixtures/ext-types/lib/tests/bindings/test_imported_types.kts b/fixtures/ext-types/lib/tests/bindings/test_imported_types.kts index 49937fe1ce..87e1dd04f2 100644 --- a/fixtures/ext-types/lib/tests/bindings/test_imported_types.kts +++ b/fixtures/ext-types/lib/tests/bindings/test_imported_types.kts @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import uniffi.imported_types_lib.* +import uniffi.imported_types_sublib.* import uniffi.uniffi_one_ns.* val ct = getCombinedType(null) @@ -13,6 +14,13 @@ assert(ct.url == java.net.URL("http://example.com/")) val ct2 = getCombinedType(ct) assert(ct == ct2) +assert(getObjectsType(null).maybeInterface == null) +assert(getObjectsType(null).maybeTrait == null) +assert(getUniffiOneTrait(null) == null) + +assert(getSubType(null).maybeInterface == null) +assert(getTraitImpl().hello() == "sub-lib trait impl says hello") + val url = java.net.URL("http://example.com/") assert(getUrl(url) == url) assert(getMaybeUrl(url)!! == url) diff --git a/fixtures/ext-types/lib/tests/bindings/test_imported_types.py b/fixtures/ext-types/lib/tests/bindings/test_imported_types.py index bf3b5cfe45..f8424482eb 100644 --- a/fixtures/ext-types/lib/tests/bindings/test_imported_types.py +++ b/fixtures/ext-types/lib/tests/bindings/test_imported_types.py @@ -5,6 +5,7 @@ import unittest import urllib from imported_types_lib import * +from imported_types_sublib import * from uniffi_one_ns import * class TestIt(unittest.TestCase): @@ -19,6 +20,18 @@ def test_it(self): ct2 = get_combined_type(ct) self.assertEqual(ct, ct2) + t = get_trait_impl() + self.assertEqual(t.hello(), "sub-lib trait impl says hello") + sub = SubLibType(maybe_enum = None, maybe_trait = t, maybe_interface = None) + self.assertTrue(get_sub_type(sub).maybe_trait is not None) + + ot = ObjectsType(maybe_trait = t, maybe_interface = None, sub = sub) + self.assertTrue(ot.maybe_trait is not None) + self.assertEqual(ot.maybe_interface, None) + self.assertEqual(get_uniffi_one_trait(None), None) + + get_sub_type(sub) + def test_get_url(self): url = urllib.parse.urlparse("http://example.com/") self.assertEqual(get_url(url), url) diff --git a/fixtures/ext-types/lib/tests/bindings/test_imported_types.swift b/fixtures/ext-types/lib/tests/bindings/test_imported_types.swift index 833d0dbd4b..4acbf83025 100644 --- a/fixtures/ext-types/lib/tests/bindings/test_imported_types.swift +++ b/fixtures/ext-types/lib/tests/bindings/test_imported_types.swift @@ -13,6 +13,16 @@ assert(ct.url == URL(string: "http://example.com/")) let ct2 = getCombinedType(value: ct) assert(ct == ct2) +let t = getTraitImpl() +assert(t.hello() == "sub-lib trait impl says hello") +let sub = SubLibType(maybeEnum: nil, maybeTrait: t, maybeInterface: nil) +assert(getSubType(existing: sub).maybeTrait != nil) + +let ob = ObjectsType(maybeTrait: t, maybeInterface: nil, sub: sub) +assert(getObjectsType(value: nil).maybeInterface == nil) +assert(getObjectsType(value: ob).maybeTrait != nil) +assert(getUniffiOneTrait(t: nil) == nil) + let url = URL(string: "http://example.com/")!; assert(getUrl(url: url) == url) assert(getMaybeUrl(url: url)! == url) diff --git a/fixtures/ext-types/lib/uniffi.toml b/fixtures/ext-types/lib/uniffi.toml index c090246e23..730ad54ffd 100644 --- a/fixtures/ext-types/lib/uniffi.toml +++ b/fixtures/ext-types/lib/uniffi.toml @@ -3,3 +3,4 @@ custom_types = "" ext_types_guid = "" uniffi_one_ns = "" +imported_types_sublib = "" diff --git a/fixtures/ext-types/proc-macro-lib/src/lib.rs b/fixtures/ext-types/proc-macro-lib/src/lib.rs index 9eee7c711f..ea576527e1 100644 --- a/fixtures/ext-types/proc-macro-lib/src/lib.rs +++ b/fixtures/ext-types/proc-macro-lib/src/lib.rs @@ -1,7 +1,9 @@ use custom_types::Handle; use ext_types_guid::Guid; use std::sync::Arc; -use uniffi_one::{UniffiOneEnum, UniffiOneInterface, UniffiOneProcMacroType, UniffiOneType}; +use uniffi_one::{ + UniffiOneEnum, UniffiOneInterface, UniffiOneProcMacroType, UniffiOneTrait, UniffiOneType, +}; use url::Url; uniffi::use_udl_record!(uniffi_one, UniffiOneType); @@ -61,6 +63,20 @@ fn get_combined_type(value: Option) -> CombinedType { maybe_handle: Some(Handle(4)), }) } +// Not part of CombinedType as object refs prevent equality testing. +#[derive(uniffi::Record)] +pub struct ObjectsType { + pub maybe_trait: Option>, + pub maybe_interface: Option>, +} + +#[uniffi::export] +fn get_objects_type(value: Option) -> ObjectsType { + value.unwrap_or_else(|| ObjectsType { + maybe_interface: None, + maybe_trait: None, + }) +} // A Custom type #[uniffi::export] @@ -141,6 +157,11 @@ fn get_uniffi_one_interface() -> Arc { Arc::new(UniffiOneInterface::new()) } +#[uniffi::export] +fn get_uniffi_one_trait(t: Option>) -> Option> { + t +} + // Some custom types via macros. // Another guid - here we use a regular struct. pub struct Uuid { diff --git a/fixtures/ext-types/proc-macro-lib/tests/bindings/test_imported_types.kts b/fixtures/ext-types/proc-macro-lib/tests/bindings/test_imported_types.kts index 04d19ffa63..d0f3048f82 100644 --- a/fixtures/ext-types/proc-macro-lib/tests/bindings/test_imported_types.kts +++ b/fixtures/ext-types/proc-macro-lib/tests/bindings/test_imported_types.kts @@ -14,6 +14,10 @@ assert(ct.url == java.net.URL("http://example.com/")) val ct2 = getCombinedType(ct) assert(ct == ct2) +assert(getObjectsType(null).maybeInterface == null) +assert(getObjectsType(null).maybeTrait == null) +assert(getUniffiOneTrait(null) == null) + val url = java.net.URL("http://example.com/") assert(getUrl(url) == url) assert(getMaybeUrl(url)!! == url) diff --git a/fixtures/ext-types/proc-macro-lib/tests/bindings/test_imported_types.py b/fixtures/ext-types/proc-macro-lib/tests/bindings/test_imported_types.py index 6aa636a202..382aa860e1 100644 --- a/fixtures/ext-types/proc-macro-lib/tests/bindings/test_imported_types.py +++ b/fixtures/ext-types/proc-macro-lib/tests/bindings/test_imported_types.py @@ -21,6 +21,11 @@ def test_it(self): ct2 = get_combined_type(ct) self.assertEqual(ct, ct2) + ot = get_objects_type(None) + self.assertEqual(ot.maybe_trait, None) + self.assertEqual(ot.maybe_interface, None) + self.assertEqual(get_uniffi_one_trait(None), None) + def test_get_url(self): url = urllib.parse.urlparse("http://example.com/") self.assertEqual(get_url(url), url) diff --git a/fixtures/ext-types/proc-macro-lib/tests/bindings/test_imported_types.swift b/fixtures/ext-types/proc-macro-lib/tests/bindings/test_imported_types.swift index 013fe9b4b1..9bfe018f8a 100644 --- a/fixtures/ext-types/proc-macro-lib/tests/bindings/test_imported_types.swift +++ b/fixtures/ext-types/proc-macro-lib/tests/bindings/test_imported_types.swift @@ -13,6 +13,10 @@ assert(ct.url == URL(string: "http://example.com/")) let ct2 = getCombinedType(value: ct) assert(ct == ct2) +assert(getObjectsType(value: nil).maybeInterface == nil) +assert(getObjectsType(value: nil).maybeTrait == nil) +assert(getUniffiOneTrait(t: nil) == nil) + let url = URL(string: "http://example.com/")!; assert(getUrl(url: url) == url) assert(getMaybeUrl(url: url)! == url) diff --git a/fixtures/ext-types/sub-lib/Cargo.toml b/fixtures/ext-types/sub-lib/Cargo.toml new file mode 100644 index 0000000000..1d25b61769 --- /dev/null +++ b/fixtures/ext-types/sub-lib/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "uniffi-fixture-ext-types-sub-lib" +edition = "2021" +version = "0.22.0" +authors = ["Firefox Sync Team "] +license = "MPL-2.0" +publish = false + +[package.metadata.uniffi.testing] +external-crates = [ + "uniffi-fixture-ext-types-lib-one", +] + +[lib] +crate-type = ["lib", "cdylib"] +name = "uniffi_sublib" + +[dependencies] +anyhow = "1" +uniffi = {path = "../../../uniffi", version = "0.25" } +uniffi-fixture-ext-types-lib-one = {path = "../uniffi-one"} + +[build-dependencies] +uniffi = {path = "../../../uniffi", version = "0.25", features = ["build"] } + +[dev-dependencies] +uniffi = {path = "../../../uniffi", version = "0.25", features = ["bindgen-tests"] } diff --git a/fixtures/ext-types/sub-lib/README.md b/fixtures/ext-types/sub-lib/README.md new file mode 100644 index 0000000000..41c153fde4 --- /dev/null +++ b/fixtures/ext-types/sub-lib/README.md @@ -0,0 +1,3 @@ +This is a "sub library" - it is itself a "library" which consumes types from +other external crates and *also* exports its own composite types and trait +implementations to be consumed by the "main" library. diff --git a/fixtures/ext-types/sub-lib/src/lib.rs b/fixtures/ext-types/sub-lib/src/lib.rs new file mode 100644 index 0000000000..565e7f4616 --- /dev/null +++ b/fixtures/ext-types/sub-lib/src/lib.rs @@ -0,0 +1,32 @@ +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, + pub maybe_trait: Option>, + pub maybe_interface: Option>, +} + +#[uniffi::export] +fn get_sub_type(existing: Option) -> SubLibType { + existing.unwrap_or_default() +} + +struct OneImpl; + +impl UniffiOneTrait for OneImpl { + fn hello(&self) -> String { + "sub-lib trait impl says hello".to_string() + } +} + +#[uniffi::export] +fn get_trait_impl() -> Arc { + Arc::new(OneImpl {}) +} + +uniffi::setup_scaffolding!("imported_types_sublib"); diff --git a/fixtures/ext-types/sub-lib/uniffi.toml b/fixtures/ext-types/sub-lib/uniffi.toml new file mode 100644 index 0000000000..94337b2227 --- /dev/null +++ b/fixtures/ext-types/sub-lib/uniffi.toml @@ -0,0 +1,3 @@ +[bindings.python.external_packages] +# This fixture does not create a Python package, so we want all modules to be top-level modules. +uniffi_one_ns = "" diff --git a/fixtures/ext-types/uniffi-one/src/lib.rs b/fixtures/ext-types/uniffi-one/src/lib.rs index 4bf130ad02..02702dcb9b 100644 --- a/fixtures/ext-types/uniffi-one/src/lib.rs +++ b/fixtures/ext-types/uniffi-one/src/lib.rs @@ -39,4 +39,9 @@ async fn get_uniffi_one_async() -> UniffiOneEnum { UniffiOneEnum::One } +#[uniffi::export] +pub trait UniffiOneTrait: Send + Sync { + fn hello(&self) -> String; +} + uniffi::include_scaffolding!("uniffi-one"); diff --git a/fixtures/foreign-executor/tests/bindings/test_foreign_executor.kts b/fixtures/foreign-executor/tests/bindings/test_foreign_executor.kts index 38ee5a7abd..404c06a463 100644 --- a/fixtures/foreign-executor/tests/bindings/test_foreign_executor.kts +++ b/fixtures/foreign-executor/tests/bindings/test_foreign_executor.kts @@ -37,11 +37,3 @@ runBlocking { assert(result.delayMs <= 200U) tester.close() } - -// Test that we cleanup when dropping a ForeignExecutor handles -assert(FfiConverterForeignExecutor.handleCount() == 0) -val tester = ForeignExecutorTester(coroutineScope) -val tester2 = ForeignExecutorTester.newFromSequence(listOf(coroutineScope)) -tester.close() -tester2.close() -assert(FfiConverterForeignExecutor.handleCount() == 0) diff --git a/fixtures/metadata/src/tests.rs b/fixtures/metadata/src/tests.rs index eac852cfea..8cf578cf4b 100644 --- a/fixtures/metadata/src/tests.rs +++ b/fixtures/metadata/src/tests.rs @@ -172,13 +172,16 @@ mod test_metadata { name: "name".into(), ty: Type::String, default: Some(LiteralMetadata::String("test".to_owned())), + docstring: None, }, FieldMetadata { name: "age".into(), ty: Type::UInt16, default: None, + docstring: None, }, ], + docstring: None, }, ); } @@ -194,16 +197,20 @@ mod test_metadata { VariantMetadata { name: "Rock".into(), fields: vec![], + docstring: None, }, VariantMetadata { name: "Paper".into(), fields: vec![], + docstring: None, }, VariantMetadata { name: "Scissors".into(), fields: vec![], + docstring: None, }, ], + docstring: None, }, ); } @@ -219,6 +226,7 @@ mod test_metadata { VariantMetadata { name: "Uninitialized".into(), fields: vec![], + docstring: None, }, VariantMetadata { name: "Initialized".into(), @@ -226,7 +234,9 @@ mod test_metadata { name: "data".into(), ty: Type::String, default: None, + docstring: None, }], + docstring: None, }, VariantMetadata { name: "Complete".into(), @@ -237,9 +247,12 @@ mod test_metadata { name: "Person".into(), }, default: None, + docstring: None, }], + docstring: None, }, ], + docstring: None, }, ); } @@ -256,12 +269,15 @@ mod test_metadata { VariantMetadata { name: "Overflow".into(), fields: vec![], + docstring: None, }, VariantMetadata { name: "DivideByZero".into(), fields: vec![], + docstring: None, }, ], + docstring: None, }, is_flat: true, }, @@ -280,6 +296,7 @@ mod test_metadata { VariantMetadata { name: "NotFound".into(), fields: vec![], + docstring: None, }, VariantMetadata { name: "PermissionDenied".into(), @@ -287,7 +304,9 @@ mod test_metadata { name: "reason".into(), ty: Type::String, default: None, + docstring: None, }], + docstring: None, }, VariantMetadata { name: "InvalidWeapon".into(), @@ -298,9 +317,12 @@ mod test_metadata { name: "Weapon".into(), }, default: None, + docstring: None, }], + docstring: None, }, ], + docstring: None, }, is_flat: false, }, @@ -315,6 +337,7 @@ mod test_metadata { module_path: "uniffi_fixture_metadata".into(), name: "Calculator".into(), imp: ObjectImpl::Struct, + docstring: None, }, ); } @@ -424,6 +447,7 @@ mod test_function_metadata { return_type: Some(Type::String), throws: None, checksum: Some(UNIFFI_META_CONST_UNIFFI_FIXTURE_METADATA_FUNC_TEST_FUNC.checksum()), + docstring: None, }, ); } @@ -442,6 +466,7 @@ mod test_function_metadata { checksum: Some( UNIFFI_META_CONST_UNIFFI_FIXTURE_METADATA_FUNC_TEST_FUNC_NO_RETURN.checksum(), ), + docstring: None, }, ); } @@ -466,6 +491,7 @@ mod test_function_metadata { checksum: Some( UNIFFI_META_CONST_UNIFFI_FIXTURE_METADATA_FUNC_TEST_FUNC_THAT_THROWS.checksum(), ), + docstring: None, }, ); } @@ -488,6 +514,7 @@ mod test_function_metadata { UNIFFI_META_CONST_UNIFFI_FIXTURE_METADATA_FUNC_TEST_FUNC_NO_RETURN_THAT_THROWS .checksum(), ), + docstring: None, }, ); } @@ -511,6 +538,7 @@ mod test_function_metadata { checksum: Some( UNIFFI_META_CONST_UNIFFI_FIXTURE_METADATA_METHOD_CALCULATOR_ADD.checksum(), ), + docstring: None, }, ); } @@ -544,6 +572,7 @@ mod test_function_metadata { checksum: Some( UNIFFI_META_CONST_UNIFFI_FIXTURE_METADATA_FUNC_TEST_ASYNC_FUNC.checksum(), ), + docstring: None, }, ); } @@ -569,6 +598,7 @@ mod test_function_metadata { UNIFFI_META_CONST_UNIFFI_FIXTURE_METADATA_FUNC_TEST_ASYNC_FUNC_THAT_THROWS .checksum(), ), + docstring: None, }, ); } @@ -593,6 +623,7 @@ mod test_function_metadata { UNIFFI_META_CONST_UNIFFI_FIXTURE_METADATA_METHOD_CALCULATOR_ASYNC_SUB .checksum(), ), + docstring: None, }, ); } @@ -618,6 +649,7 @@ mod test_function_metadata { UNIFFI_META_CONST_UNIFFI_FIXTURE_METADATA_METHOD_CALCULATOR_GET_DISPLAY .checksum(), ), + docstring: None, }, ); } @@ -640,6 +672,7 @@ mod test_function_metadata { takes_self_by_arc: false, checksum: Some(UNIFFI_META_CONST_UNIFFI_FIXTURE_METADATA_METHOD_CALCULATORDISPLAY_DISPLAY_RESULT .checksum()), + docstring: None, }, ); } @@ -651,6 +684,7 @@ mod test_function_metadata { CallbackInterfaceMetadata { module_path: "uniffi_fixture_metadata".into(), name: "Logger".into(), + docstring: None, }, ); check_metadata( @@ -668,6 +702,7 @@ mod test_function_metadata { checksum: Some( UNIFFI_META_CONST_UNIFFI_FIXTURE_METADATA_METHOD_LOGGER_LOG.checksum(), ), + docstring: None, }, ); } diff --git a/fixtures/proc-macro/src/lib.rs b/fixtures/proc-macro/src/lib.rs index 351dbd8245..0eaf162aef 100644 --- a/fixtures/proc-macro/src/lib.rs +++ b/fixtures/proc-macro/src/lib.rs @@ -234,4 +234,31 @@ impl Object { } } +// defined in UDL. +fn get_one(one: Option) -> One { + one.unwrap_or(One { inner: 0 }) +} + +fn get_bool(b: Option) -> MaybeBool { + b.unwrap_or(MaybeBool::Uncertain) +} + +fn get_object(o: Option>) -> Arc { + o.unwrap_or_else(Object::new) +} + +fn get_trait(o: Option>) -> Arc { + o.unwrap_or_else(|| Arc::new(TraitImpl {})) +} + +#[derive(Default)] +struct Externals { + one: Option, + bool: Option, +} + +fn get_externals(e: Option) -> Externals { + e.unwrap_or_default() +} + uniffi::include_scaffolding!("proc-macro"); diff --git a/fixtures/proc-macro/src/proc-macro.udl b/fixtures/proc-macro/src/proc-macro.udl index 8fb1881084..843b2e5df8 100644 --- a/fixtures/proc-macro/src/proc-macro.udl +++ b/fixtures/proc-macro/src/proc-macro.udl @@ -1,7 +1,32 @@ -// Namespace different from crate name. -namespace proc_macro { }; - // Use this to test that types defined in the UDL can be used in the proc-macros dictionary Zero { string inner; }; + +// And all of these for the opposite - proc-macro types used in UDL. +[Rust="record"] +typedef extern One; + +[Rust="enum"] +typedef extern MaybeBool; + +[Rust="interface"] +typedef extern Object; + +[Rust="trait"] +typedef extern Trait; + +// Then stuff defined here but referencing the imported types. +dictionary Externals { + One? one; + MaybeBool? bool; +}; + +// Namespace different from crate name. +namespace proc_macro { + One get_one(One? one); + MaybeBool get_bool(MaybeBool? b); + Object get_object(Object? o); + Trait get_trait(Trait? t); + Externals get_externals(Externals? e); +}; diff --git a/fixtures/proc-macro/tests/bindings/test_proc_macro.py b/fixtures/proc-macro/tests/bindings/test_proc_macro.py index c80489fc1a..2192b8a4cd 100644 --- a/fixtures/proc-macro/tests/bindings/test_proc_macro.py +++ b/fixtures/proc-macro/tests/bindings/test_proc_macro.py @@ -83,3 +83,10 @@ def callback_handler(self, h): return v call_callback_interface(PyTestCallbackInterface()) + +# udl exposed functions with procmacro types. +assert get_one(None).inner == 0 +assert get_bool(None) == MaybeBool.UNCERTAIN +assert get_object(None).is_heavy() == MaybeBool.UNCERTAIN +assert get_trait(None).name() == "TraitImpl" +assert get_externals(None).one is None diff --git a/fixtures/reexport-scaffolding-macro/Cargo.toml b/fixtures/reexport-scaffolding-macro/Cargo.toml deleted file mode 100644 index d494000c26..0000000000 --- a/fixtures/reexport-scaffolding-macro/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "uniffi-fixture-reexport-scaffolding-macro" -version = "0.22.0" -authors = ["Firefox Sync Team "] -edition = "2021" -license = "MPL-2.0" -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -name = "reexport_scaffolding_macro" -crate-type = ["lib", "cdylib"] - -[dependencies] -uniffi-fixture-callbacks = { path = "../callbacks" } -uniffi-fixture-coverall = { path = "../coverall" } -uniffi = { path = "../../uniffi", version = "0.25" } -uniffi_bindgen = { path = "../../uniffi_bindgen" } - -[dev-dependencies] -cargo_metadata = "0.15" -libloading = "0.7" -uniffi = { path = "../../uniffi", version = "0.25" } diff --git a/fixtures/reexport-scaffolding-macro/src/lib.rs b/fixtures/reexport-scaffolding-macro/src/lib.rs deleted file mode 100644 index 6bd04f2ccd..0000000000 --- a/fixtures/reexport-scaffolding-macro/src/lib.rs +++ /dev/null @@ -1,197 +0,0 @@ -uniffi_fixture_callbacks::uniffi_reexport_scaffolding!(); -uniffi_coverall::uniffi_reexport_scaffolding!(); - -#[cfg(test)] -mod tests { - use cargo_metadata::Message; - use libloading::{Library, Symbol}; - use std::ffi::CString; - use std::os::raw::c_void; - use std::process::{Command, Stdio}; - use uniffi::{FfiConverter, ForeignCallback, RustBuffer, RustCallStatus, RustCallStatusCode}; - use uniffi_bindgen::ComponentInterface; - - struct UniFfiTag; - - // Load the dynamic library that was built for this crate. The external functions from - // `uniffi_callbacks' and `uniffi_coverall` should be present. - pub fn load_library() -> Library { - let mut cmd = Command::new("cargo"); - cmd.arg("build").arg("--message-format=json").arg("--lib"); - cmd.stdout(Stdio::piped()); - let mut child = cmd.spawn().unwrap(); - let output = std::io::BufReader::new(child.stdout.take().unwrap()); - let artifacts = Message::parse_stream(output) - .filter_map(|message| match message { - Err(e) => panic!("{e}"), - Ok(Message::CompilerArtifact(artifact)) => { - if artifact.target.name == "reexport_scaffolding_macro" - && artifact.target.kind.iter().any(|item| item == "cdylib") - { - Some(artifact) - } else { - None - } - } - _ => None, - }) - .collect::>(); - if !child.wait().unwrap().success() { - panic!("Failed to execute `cargo build`"); - } - let artifact = match artifacts.len() { - 1 => &artifacts[0], - n => panic!("Found {n} artfiacts from cargo build"), - }; - let cdylib_files: Vec<_> = artifact - .filenames - .iter() - .filter(|nm| matches!(nm.extension(), Some(std::env::consts::DLL_EXTENSION))) - .collect(); - let library_path = match cdylib_files.len() { - 1 => cdylib_files[0].to_string(), - _ => panic!("Failed to build exactly one cdylib file"), - }; - unsafe { Library::new(library_path).unwrap() } - } - - pub fn has_symbol(library: &Library, name: &str) -> bool { - unsafe { - library - .get::(CString::new(name).unwrap().as_bytes_with_nul()) - .is_ok() - } - } - - pub fn get_symbol<'lib, T>(library: &'lib Library, name: &str) -> Symbol<'lib, T> { - unsafe { - library - .get::(CString::new(name).unwrap().as_bytes_with_nul()) - .unwrap() - } - } - - #[test] - fn test_symbols_present() { - let library = load_library(); - let coveralls_ci = ComponentInterface::from_webidl( - include_str!("../../coverall/src/coverall.udl"), - "uniffi_coverall", - ) - .unwrap(); - let callbacks_ci = ComponentInterface::from_webidl( - include_str!("../../callbacks/src/callbacks.udl"), - "uniffi_fixture_callbacks", - ) - .unwrap(); - - // UniFFI internal function - assert!(has_symbol::< - unsafe extern "C" fn(i32, &mut RustCallStatus) -> RustBuffer, - >( - &library, coveralls_ci.ffi_rustbuffer_alloc().name() - )); - - // Top-level function - assert!( - has_symbol:: u64>( - &library, - coveralls_ci - .get_function_definition("get_num_alive") - .unwrap() - .ffi_func() - .name() - ) - ); - - // Object method - assert!( - has_symbol:: u64>( - &library, - coveralls_ci - .get_object_definition("Coveralls") - .unwrap() - .get_method("get_name") - .ffi_func() - .name() - ) - ); - - // Callback init func - assert!(has_symbol::< - unsafe extern "C" fn(ForeignCallback, &mut RustCallStatus) -> (), - >( - &library, - callbacks_ci - .get_callback_interface_definition("ForeignGetters") - .unwrap() - .ffi_init_callback() - .name() - )); - } - - #[test] - fn test_calls() { - let mut call_status = RustCallStatus::default(); - let library = load_library(); - let coveralls_ci = ComponentInterface::from_webidl( - include_str!("../../coverall/src/coverall.udl"), - "uniffi_coverall", - ) - .unwrap(); - let object_def = coveralls_ci.get_object_definition("Coveralls").unwrap(); - - let get_num_alive: Symbol u64> = get_symbol( - &library, - coveralls_ci - .get_function_definition("get_num_alive") - .unwrap() - .ffi_func() - .name(), - ); - let coveralls_new: Symbol< - unsafe extern "C" fn(RustBuffer, &mut RustCallStatus) -> *const c_void, - > = get_symbol( - &library, - object_def.primary_constructor().unwrap().ffi_func().name(), - ); - let coveralls_get_name: Symbol< - unsafe extern "C" fn(*const c_void, &mut RustCallStatus) -> RustBuffer, - > = get_symbol( - &library, - object_def.get_method("get_name").ffi_func().name(), - ); - let coveralls_free: Symbol ()> = - get_symbol(&library, object_def.ffi_object_free().name()); - - let num_alive = unsafe { get_num_alive(&mut call_status) }; - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!(num_alive, 0); - - let obj_id = unsafe { - coveralls_new( - >::lower("TestName".into()), - &mut call_status, - ) - }; - assert_eq!(call_status.code, RustCallStatusCode::Success); - - let name_buf = unsafe { coveralls_get_name(obj_id, &mut call_status) }; - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!( - >::try_lift(name_buf).unwrap(), - "TestName" - ); - - let num_alive = unsafe { get_num_alive(&mut call_status) }; - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!(num_alive, 1); - - unsafe { coveralls_free(obj_id, &mut call_status) }; - assert_eq!(call_status.code, RustCallStatusCode::Success); - - let num_alive = unsafe { get_num_alive(&mut call_status) }; - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!(num_alive, 0); - } -} diff --git a/fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.rs b/fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.rs index 1bcd3a4237..6671a9624a 100644 --- a/fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.rs +++ b/fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.rs @@ -4,6 +4,7 @@ uniffi_macros::generate_and_include_scaffolding!("../../../../fixtures/uitests/s fn main() { /* empty main required by `trybuild` */} // This will fail to compile, because the trait is not explicit Send+Sync +#[uniffi::trait_interface] pub trait Trait { } diff --git a/fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.stderr b/fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.stderr index a8e7dc90a1..6832a59b09 100644 --- a/fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.stderr +++ b/fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.stderr @@ -1,3 +1,31 @@ +error[E0277]: `(dyn Trait + 'static)` cannot be shared between threads safely + --> $OUT_DIR[uniffi_uitests]/trait.uniffi.rs + | + | #[::uniffi::export_for_udl] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `(dyn Trait + 'static)` cannot be shared between threads safely + | + = help: the trait `Sync` is not implemented for `(dyn Trait + 'static)` +note: required by a bound in `HandleAlloc` + --> $WORKSPACE/uniffi_core/src/ffi_converter_traits.rs + | + | pub unsafe trait HandleAlloc: Send + Sync { + | ^^^^ required by this bound in `HandleAlloc` + = note: this error originates in the attribute macro `::uniffi::export_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `(dyn Trait + 'static)` cannot be sent between threads safely + --> $OUT_DIR[uniffi_uitests]/trait.uniffi.rs + | + | #[::uniffi::export_for_udl] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `(dyn Trait + 'static)` cannot be sent between threads safely + | + = help: the trait `Send` is not implemented for `(dyn Trait + 'static)` +note: required by a bound in `HandleAlloc` + --> $WORKSPACE/uniffi_core/src/ffi_converter_traits.rs + | + | pub unsafe trait HandleAlloc: Send + Sync { + | ^^^^ required by this bound in `HandleAlloc` + = note: this error originates in the attribute macro `::uniffi::export_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: `(dyn Trait + 'static)` cannot be shared between threads safely --> $OUT_DIR[uniffi_uitests]/trait.uniffi.rs | @@ -27,9 +55,37 @@ note: required by a bound in `FfiConverterArc` = note: this error originates in the attribute macro `::uniffi::export_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `(dyn ProcMacroTrait + 'static)` cannot be shared between threads safely - --> tests/ui/interface_trait_not_sync_and_send.rs:11:1 + --> tests/ui/interface_trait_not_sync_and_send.rs:12:1 + | +12 | #[uniffi::export] + | ^^^^^^^^^^^^^^^^^ `(dyn ProcMacroTrait + 'static)` cannot be shared between threads safely + | + = help: the trait `Sync` is not implemented for `(dyn ProcMacroTrait + 'static)` +note: required by a bound in `HandleAlloc` + --> $WORKSPACE/uniffi_core/src/ffi_converter_traits.rs + | + | pub unsafe trait HandleAlloc: Send + Sync { + | ^^^^ required by this bound in `HandleAlloc` + = note: this error originates in the attribute macro `uniffi::export` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `(dyn ProcMacroTrait + 'static)` cannot be sent between threads safely + --> tests/ui/interface_trait_not_sync_and_send.rs:12:1 + | +12 | #[uniffi::export] + | ^^^^^^^^^^^^^^^^^ `(dyn ProcMacroTrait + 'static)` cannot be sent between threads safely + | + = help: the trait `Send` is not implemented for `(dyn ProcMacroTrait + 'static)` +note: required by a bound in `HandleAlloc` + --> $WORKSPACE/uniffi_core/src/ffi_converter_traits.rs + | + | pub unsafe trait HandleAlloc: Send + Sync { + | ^^^^ required by this bound in `HandleAlloc` + = note: this error originates in the attribute macro `uniffi::export` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `(dyn ProcMacroTrait + 'static)` cannot be shared between threads safely + --> tests/ui/interface_trait_not_sync_and_send.rs:12:1 | -11 | #[uniffi::export] +12 | #[uniffi::export] | ^^^^^^^^^^^^^^^^^ `(dyn ProcMacroTrait + 'static)` cannot be shared between threads safely | = help: the trait `Sync` is not implemented for `(dyn ProcMacroTrait + 'static)` @@ -41,9 +97,9 @@ note: required by a bound in `FfiConverterArc` = note: this error originates in the attribute macro `uniffi::export` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `(dyn ProcMacroTrait + 'static)` cannot be sent between threads safely - --> tests/ui/interface_trait_not_sync_and_send.rs:11:1 + --> tests/ui/interface_trait_not_sync_and_send.rs:12:1 | -11 | #[uniffi::export] +12 | #[uniffi::export] | ^^^^^^^^^^^^^^^^^ `(dyn ProcMacroTrait + 'static)` cannot be sent between threads safely | = help: the trait `Send` is not implemented for `(dyn ProcMacroTrait + 'static)` diff --git a/uniffi/Cargo.toml b/uniffi/Cargo.toml index 5a6f78373a..0b05707bf9 100644 --- a/uniffi/Cargo.toml +++ b/uniffi/Cargo.toml @@ -7,17 +7,17 @@ repository = "https://github.com/mozilla/uniffi-rs" # Incrementing the minor version here means a breaking change to consumers. # * See `docs/uniffi-versioning.md` for guidance on when to increment this # * Make sure to also update `uniffi_bindgen::UNIFFI_CONTRACT_VERSION" -version = "0.25.0" +version = "0.25.2" authors = ["Firefox Sync Team "] license = "MPL-2.0" edition = "2021" keywords = ["ffi", "bindgen"] [dependencies] -uniffi_bindgen = { path = "../uniffi_bindgen", version = "=0.25.0", optional = true } -uniffi_build = { path = "../uniffi_build", version = "=0.25.0", optional = true } -uniffi_core = { path = "../uniffi_core", version = "=0.25.0" } -uniffi_macros = { path = "../uniffi_macros", version = "=0.25.0" } +uniffi_bindgen = { path = "../uniffi_bindgen", version = "=0.25.2", optional = true } +uniffi_build = { path = "../uniffi_build", version = "=0.25.2", optional = true } +uniffi_core = { path = "../uniffi_core", version = "=0.25.2" } +uniffi_macros = { path = "../uniffi_macros", version = "=0.25.2" } anyhow = "1" camino = { version = "1.0.8", optional = true } clap = { version = "4", features = ["cargo", "std", "derive"], optional = true } diff --git a/uniffi_bindgen/Cargo.toml b/uniffi_bindgen/Cargo.toml index 3c7c6ce79c..9c59879afa 100644 --- a/uniffi_bindgen/Cargo.toml +++ b/uniffi_bindgen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_bindgen" -version = "0.25.0" +version = "0.25.2" authors = ["Firefox Sync Team "] description = "a multi-language bindings generator for rust (codegen and cli tooling)" documentation = "https://mozilla.github.io/uniffi-rs" @@ -23,7 +23,8 @@ once_cell = "1.12" paste = "1.0" serde = "1" toml = "0.5" -uniffi_meta = { path = "../uniffi_meta", version = "=0.25.0" } -uniffi_testing = { path = "../uniffi_testing", version = "=0.25.0" } -uniffi_udl = { path = "../uniffi_udl", version = "=0.25.0" } +uniffi_meta = { path = "../uniffi_meta", version = "=0.25.2" } +uniffi_testing = { path = "../uniffi_testing", version = "=0.25.2" } +uniffi_udl = { path = "../uniffi_udl", version = "=0.25.2" } clap = { version = "4", default-features = false, features = ["std", "derive"], optional = true } +textwrap = "0.16" diff --git a/uniffi_bindgen/src/backend/types.rs b/uniffi_bindgen/src/backend/types.rs index 9c342f0e57..3f66fdfe01 100644 --- a/uniffi_bindgen/src/backend/types.rs +++ b/uniffi_bindgen/src/backend/types.rs @@ -10,6 +10,10 @@ use super::Literal; use std::fmt::Debug; +// XXX - Note that this trait is not used internally. It exists just to avoid an unnecessary +// breaking change for external bindings which use this trait. +// It is likely to be removed some time after 0.26.x. + /// A Trait to help render types in a language specific format. pub trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs index 17a08745b4..ae4bffc973 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::ComponentInterface; #[derive(Debug)] pub struct CallbackInterfaceCodeType { @@ -16,18 +17,14 @@ impl CallbackInterfaceCodeType { } impl CodeType for CallbackInterfaceCodeType { - fn type_label(&self) -> String { - super::KotlinCodeOracle.class_name(&self.id) + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.id) } fn canonical_name(&self) -> String { format!("Type{}", self.id) } - fn literal(&self, _literal: &Literal) -> String { - unreachable!(); - } - fn initialization_fn(&self) -> Option { Some(format!("uniffiCallbackInterface{}.register", self.id)) } diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs index 8eda7f1110..4329f32f4c 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs @@ -2,17 +2,19 @@ * 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::backend::{CodeType, Literal, Type}; +use super::{AsCodeType, CodeType}; +use crate::backend::{Literal, Type}; +use crate::ComponentInterface; use paste::paste; -fn render_literal(literal: &Literal, inner: &Type) -> String { +fn render_literal(literal: &Literal, inner: &Type, ci: &ComponentInterface) -> String { match literal { Literal::Null => "null".into(), Literal::EmptySequence => "listOf()".into(), Literal::EmptyMap => "mapOf()".into(), // For optionals - _ => super::KotlinCodeOracle.find(inner).literal(literal), + _ => super::KotlinCodeOracle.find(inner).literal(literal, ci), } } @@ -34,16 +36,16 @@ macro_rules! impl_code_type_for_compound { } impl CodeType for $T { - fn type_label(&self) -> String { - format!($type_label_pattern, super::KotlinCodeOracle.find(self.inner()).type_label()) + fn type_label(&self, ci: &ComponentInterface) -> String { + format!($type_label_pattern, super::KotlinCodeOracle.find(self.inner()).type_label(ci)) } fn canonical_name(&self) -> String { format!($canonical_name_pattern, super::KotlinCodeOracle.find(self.inner()).canonical_name()) } - fn literal(&self, literal: &Literal) -> String { - render_literal(literal, self.inner()) + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + render_literal(literal, self.inner(), ci) } } } @@ -74,23 +76,23 @@ impl MapCodeType { } impl CodeType for MapCodeType { - fn type_label(&self) -> String { + fn type_label(&self, ci: &ComponentInterface) -> String { format!( "Map<{}, {}>", - super::KotlinCodeOracle.find(self.key()).type_label(), - super::KotlinCodeOracle.find(self.value()).type_label(), + super::KotlinCodeOracle.find(self.key()).type_label(ci), + super::KotlinCodeOracle.find(self.value()).type_label(ci), ) } fn canonical_name(&self) -> String { format!( "Map{}{}", - super::KotlinCodeOracle.find(self.key()).canonical_name(), - super::KotlinCodeOracle.find(self.value()).canonical_name(), + self.key().as_codetype().canonical_name(), + self.value().as_codetype().canonical_name(), ) } - fn literal(&self, literal: &Literal) -> String { - render_literal(literal, &self.value) + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + render_literal(literal, &self.value, ci) } } diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs index 930fc5879e..137cd0d8d9 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::ComponentInterface; #[derive(Debug)] pub struct CustomCodeType { @@ -16,15 +17,11 @@ impl CustomCodeType { } impl CodeType for CustomCodeType { - fn type_label(&self) -> String { + fn type_label(&self, _ci: &ComponentInterface) -> String { self.name.clone() } fn canonical_name(&self) -> String { format!("Type{}", self.name) } - - fn literal(&self, _literal: &Literal) -> String { - unreachable!("Can't have a literal of a custom type"); - } } diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs index ffce616a7e..f5300c10ee 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs @@ -2,7 +2,9 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::backend::Literal; +use crate::ComponentInterface; #[derive(Debug)] pub struct EnumCodeType { @@ -16,19 +18,19 @@ impl EnumCodeType { } impl CodeType for EnumCodeType { - fn type_label(&self) -> String { - super::KotlinCodeOracle.class_name(&self.id) + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.id) } fn canonical_name(&self) -> String { format!("Type{}", self.id) } - fn literal(&self, literal: &Literal) -> String { + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { if let Literal::Enum(v, _) = literal { format!( "{}.{}", - self.type_label(), + self.type_label(ci), super::KotlinCodeOracle.enum_variant_name(v) ) } else { diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/error.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/error.rs deleted file mode 100644 index eb16aa5ce2..0000000000 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/error.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/. */ - -use crate::backend::{CodeType, Literal}; - -// When a type is used as an error it gets a special CodeType. -#[derive(Debug)] -pub struct ErrorCodeType { - id: String, -} - -impl ErrorCodeType { - pub fn new(id: String) -> Self { - Self { id } - } -} - -impl CodeType for ErrorCodeType { - fn type_label(&self) -> String { - super::KotlinCodeOracle.error_name(&self.id) - } - - fn canonical_name(&self) -> String { - format!("Type{}", self.id) - } - - fn literal(&self, _literal: &Literal) -> String { - unreachable!(); - } -} diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs index e46058e7f1..154e12a381 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs @@ -2,13 +2,14 @@ * 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::backend::CodeType; +use super::CodeType; +use crate::ComponentInterface; #[derive(Debug)] pub struct ForeignExecutorCodeType; impl CodeType for ForeignExecutorCodeType { - fn type_label(&self) -> String { + fn type_label(&self, _ci: &ComponentInterface) -> String { // Kotlin uses a CoroutineScope for ForeignExecutor "CoroutineScope".into() } diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs index a637c964a1..3ecf09d47f 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::ComponentInterface; #[derive(Debug)] pub struct ExternalCodeType { @@ -16,15 +17,11 @@ impl ExternalCodeType { } impl CodeType for ExternalCodeType { - fn type_label(&self) -> String { + fn type_label(&self, _ci: &ComponentInterface) -> String { self.name.clone() } fn canonical_name(&self) -> String { format!("Type{}", self.name) } - - fn literal(&self, _literal: &Literal) -> String { - unreachable!("Can't have a literal of an external type"); - } } diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs index 9a744ade72..17331ff511 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::ComponentInterface; use paste::paste; macro_rules! impl_code_type_for_miscellany { @@ -12,17 +13,13 @@ macro_rules! impl_code_type_for_miscellany { pub struct $T; impl CodeType for $T { - fn type_label(&self) -> String { + fn type_label(&self, _ci: &ComponentInterface) -> String { $class_name.into() } fn canonical_name(&self) -> String { $canonical_name.into() } - - fn literal(&self, _literal: &Literal) -> String { - unreachable!() - } } } }; diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index f36a5bda2f..c1b4f98995 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -5,13 +5,14 @@ use std::borrow::Borrow; use std::cell::RefCell; use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt::Debug; use anyhow::{Context, Result}; use askama::Template; use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; -use crate::backend::{CodeType, TemplateExpression}; +use crate::backend::TemplateExpression; use crate::interface::*; use crate::BindingsConfig; @@ -19,7 +20,6 @@ mod callback_interface; mod compounds; mod custom; mod enum_; -mod error; mod executor; mod external; mod miscellany; @@ -28,11 +28,52 @@ mod primitives; mod record; mod variant; +trait CodeType: Debug { + /// The language specific label used to reference this type. This will be used in + /// method signatures and property declarations. + fn type_label(&self, ci: &ComponentInterface) -> String; + + /// A representation of this type label that can be used as part of another + /// identifier. e.g. `read_foo()`, or `FooInternals`. + /// + /// This is especially useful when creating specialized objects or methods to deal + /// with this type only. + fn canonical_name(&self) -> String; + + fn literal(&self, _literal: &Literal, ci: &ComponentInterface) -> String { + unimplemented!("Unimplemented for {}", self.type_label(ci)) + } + + /// Name of the FfiConverter + /// + /// This is the object that contains the lower, write, lift, and read methods for this type. + /// Depending on the binding this will either be a singleton or a class with static methods. + /// + /// This is the newer way of handling these methods and replaces the lower, write, lift, and + /// read CodeType methods. Currently only used by Kotlin, but the plan is to move other + /// backends to using this. + fn ffi_converter_name(&self) -> String { + format!("FfiConverter{}", self.canonical_name()) + } + + /// A list of imports that are needed if this type is in use. + /// Classes are imported exactly once. + fn imports(&self) -> Option> { + None + } + + /// Function to run at startup + fn initialization_fn(&self) -> Option { + None + } +} + // config options to customize the generated Kotlin. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Config { package_name: Option, cdylib_name: Option, + generate_immutable_records: Option, #[serde(default)] custom_types: HashMap, #[serde(default)] @@ -63,6 +104,11 @@ impl Config { "uniffi".into() } } + + /// Whether to generate immutable records (`val` instead of `var`) + pub fn generate_immutable_records(&self) -> bool { + self.generate_immutable_records.unwrap_or(false) + } } impl BindingsConfig for Config { @@ -123,7 +169,7 @@ impl ImportRequirement { #[derive(Template)] #[template(syntax = "kt", escape = "none", path = "Types.kt")] pub struct TypeRenderer<'a> { - kotlin_config: &'a Config, + config: &'a Config, ci: &'a ComponentInterface, // Track included modules for the `include_once()` macro include_once_names: RefCell>, @@ -132,9 +178,9 @@ pub struct TypeRenderer<'a> { } impl<'a> TypeRenderer<'a> { - fn new(kotlin_config: &'a Config, ci: &'a ComponentInterface) -> Self { + fn new(config: &'a Config, ci: &'a ComponentInterface) -> Self { Self { - kotlin_config, + config, ci, include_once_names: RefCell::new(HashSet::new()), imports: RefCell::new(BTreeSet::new()), @@ -145,7 +191,7 @@ impl<'a> TypeRenderer<'a> { fn external_type_package_name(&self, module_path: &str, namespace: &str) -> String { // config overrides are keyed by the crate name, default fallback is the namespace. let crate_name = module_path.split("::").next().unwrap(); - match self.kotlin_config.external_packages.get(crate_name) { + match self.config.external_packages.get(crate_name) { Some(name) => name.clone(), // unreachable in library mode - all deps are in our config with correct namespace. None => format!("uniffi.{namespace}"), @@ -232,17 +278,20 @@ impl KotlinCodeOracle { type_.clone().as_type().as_codetype() } - fn find_as_error(&self, type_: &Type) -> Box { - match type_ { - Type::Enum { name, .. } => Box::new(error::ErrorCodeType::new(name.clone())), - // XXX - not sure how we are supposed to return askama::Error? - _ => panic!("unsupported type for error: {type_:?}"), - } + /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc). + fn class_name(&self, ci: &ComponentInterface, nm: &str) -> String { + let name = nm.to_string().to_upper_camel_case(); + // fixup errors. + ci.is_name_used_as_error(nm) + .then(|| self.convert_error_suffix(&name)) + .unwrap_or(name) } - /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc). - fn class_name(&self, nm: &str) -> String { - nm.to_string().to_upper_camel_case() + fn convert_error_suffix(&self, nm: &str) -> String { + match nm.strip_suffix("Error") { + None => nm.to_string(), + Some(stripped) => format!("{stripped}Exception"), + } } /// Get the idiomatic Kotlin rendering of a function name. @@ -260,20 +309,6 @@ impl KotlinCodeOracle { nm.to_string().to_shouty_snake_case() } - /// Get the idiomatic Kotlin rendering of an exception name - /// - /// This replaces "Error" at the end of the name with "Exception". Rust code typically uses - /// "Error" for any type of error but in the Java world, "Error" means a non-recoverable error - /// and is distinguished from an "Exception". - fn error_name(&self, nm: &str) -> String { - // errors are a class in kotlin. - let name = self.class_name(nm); - match name.strip_suffix("Error") { - None => name, - Some(stripped) => format!("{stripped}Exception"), - } - } - fn ffi_type_label_by_value(ffi_type: &FfiType) -> String { match ffi_type { FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)), @@ -292,19 +327,16 @@ impl KotlinCodeOracle { FfiType::Int64 | FfiType::UInt64 => "Long".to_string(), FfiType::Float32 => "Float".to_string(), FfiType::Float64 => "Double".to_string(), - FfiType::RustArcPtr(_) => "Pointer".to_string(), + FfiType::Handle => "UniffiHandle".to_string(), FfiType::RustBuffer(maybe_suffix) => { format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default()) } FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(), FfiType::ForeignCallback => "ForeignCallback".to_string(), - FfiType::ForeignExecutorHandle => "USize".to_string(), FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(), - FfiType::RustFutureHandle => "Pointer".to_string(), FfiType::RustFutureContinuationCallback => { "UniFffiRustFutureContinuationCallbackType".to_string() } - FfiType::RustFutureContinuationData => "USize".to_string(), } } @@ -318,8 +350,8 @@ impl KotlinCodeOracle { /// This split is needed because of the `FfiConverter` interface. For struct impls, `lower` /// can only lower the concrete class. For trait impls, `lower` can lower anything that /// implement the interface. - fn object_names(&self, obj: &Object) -> (String, String) { - let class_name = self.class_name(obj.name()); + fn object_names(&self, ci: &ComponentInterface, obj: &Object) -> (String, String) { + let class_name = self.class_name(ci, obj.name()); match obj.imp() { ObjectImpl::Struct => (format!("{class_name}Interface"), class_name), ObjectImpl::Trait => { @@ -330,7 +362,7 @@ impl KotlinCodeOracle { } } -pub trait AsCodeType { +trait AsCodeType { fn as_codetype(&self) -> Box; } @@ -384,112 +416,66 @@ impl AsCodeType for T { } } -pub mod filters { +mod filters { use super::*; pub use crate::backend::filters::*; - pub fn type_name(as_ct: &impl AsCodeType) -> Result { - Ok(as_ct.as_codetype().type_label()) + pub(super) fn type_name( + as_ct: &impl AsCodeType, + ci: &ComponentInterface, + ) -> Result { + Ok(as_ct.as_codetype().type_label(ci)) } - pub fn canonical_name(as_ct: &impl AsCodeType) -> Result { + pub(super) fn canonical_name(as_ct: &impl AsCodeType) -> Result { Ok(as_ct.as_codetype().canonical_name()) } - pub fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result { + pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result { Ok(as_ct.as_codetype().ffi_converter_name()) } - pub fn lower_fn(as_ct: &impl AsCodeType) -> Result { + pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!( "{}.lower", as_ct.as_codetype().ffi_converter_name() )) } - pub fn allocation_size_fn(as_ct: &impl AsCodeType) -> Result { + pub(super) fn allocation_size_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!( "{}.allocationSize", as_ct.as_codetype().ffi_converter_name() )) } - pub fn write_fn(as_ct: &impl AsCodeType) -> Result { + pub(super) fn write_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!( "{}.write", as_ct.as_codetype().ffi_converter_name() )) } - pub fn lift_fn(as_ct: &impl AsCodeType) -> Result { + pub(super) fn lift_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!("{}.lift", as_ct.as_codetype().ffi_converter_name())) } - pub fn read_fn(as_ct: &impl AsCodeType) -> Result { + pub(super) fn read_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!("{}.read", as_ct.as_codetype().ffi_converter_name())) } - pub fn error_handler(result_type: &ResultType) -> Result { - match &result_type.throws_type { - Some(error_type) => Ok(KotlinCodeOracle.error_name(&type_name(error_type)?)), - None => Ok("NullCallStatusErrorHandler".into()), - } - } - - pub fn future_callback_handler(result_type: &ResultType) -> Result { - let return_component = match &result_type.return_type { - Some(return_type) => KotlinCodeOracle.find(return_type).canonical_name(), - None => "Void".into(), - }; - let throws_component = match &result_type.throws_type { - Some(throws_type) => { - format!("_{}", KotlinCodeOracle.find(throws_type).canonical_name()) - } - None => "".into(), - }; - Ok(format!( - "UniFfiFutureCallbackHandler{return_component}{throws_component}" - )) - } - - pub fn future_continuation_type(result_type: &ResultType) -> Result { - let return_type_name = match &result_type.return_type { - Some(t) => type_name(t)?, - None => "Unit".into(), - }; - Ok(format!("Continuation<{return_type_name}>")) - } - pub fn render_literal( literal: &Literal, - as_ct: &impl AsCodeType, + as_ct: &impl AsType, + ci: &ComponentInterface, ) -> Result { - Ok(as_ct.as_codetype().literal(literal)) - } - - /// Get the Kotlin syntax for representing a given low-level `FfiType`. - pub fn ffi_type_name(type_: &FfiType) -> Result { - Ok(KotlinCodeOracle::ffi_type_label(type_)) + Ok(as_ct.as_codetype().literal(literal, ci)) } pub fn ffi_type_name_by_value(type_: &FfiType) -> Result { Ok(KotlinCodeOracle::ffi_type_label_by_value(type_)) } - // Some FfiTypes have the same ffi_type_label - this makes a vec of them unique. - pub fn unique_ffi_types( - types: impl Iterator, - ) -> Result, askama::Error> { - let mut seen = HashSet::new(); - let mut result = Vec::new(); - for t in types { - if seen.insert(KotlinCodeOracle::ffi_type_label(&t)) { - result.push(t) - } - } - Ok(result.into_iter()) - } - /// Get the idiomatic Kotlin rendering of a function name. pub fn fn_name(nm: &str) -> Result { Ok(KotlinCodeOracle.fn_name(nm)) @@ -500,43 +486,21 @@ pub mod filters { Ok(KotlinCodeOracle.var_name(nm)) } + /// Get a String representing the name used for an individual enum variant. pub fn variant_name(v: &Variant) -> Result { Ok(KotlinCodeOracle.enum_variant_name(v.name())) } - /// Get a codetype for idiomatic Kotlin rendering of an individual enum variant. - pub fn enum_variant(v: &Variant) -> Result { - Ok(v.clone()) - } - - /// Get a codetype for idiomatic Kotlin rendering of an individual enum variant - /// when used in an error. - pub fn error_variant(v: &Variant) -> Result { - Ok(variant::ErrorVariantCodeTypeProvider { v: v.clone() }) - } - - /// Some of the above filters have different versions to help when the type - /// is used as an error. - pub fn error_type_name(as_type: &impl AsType) -> Result { - Ok(KotlinCodeOracle - .find_as_error(&as_type.as_type()) - .type_label()) + pub fn error_variant_name(v: &Variant) -> Result { + let name = v.name().to_string().to_upper_camel_case(); + Ok(KotlinCodeOracle.convert_error_suffix(&name)) } - pub fn error_canonical_name(as_type: &impl AsType) -> Result { - Ok(KotlinCodeOracle - .find_as_error(&as_type.as_type()) - .canonical_name()) - } - - pub fn error_ffi_converter_name(as_type: &impl AsType) -> Result { - Ok(KotlinCodeOracle - .find_as_error(&as_type.as_type()) - .ffi_converter_name()) - } - - pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> { - Ok(KotlinCodeOracle.object_names(obj)) + pub fn object_names( + obj: &Object, + ci: &ComponentInterface, + ) -> Result<(String, String), askama::Error> { + Ok(KotlinCodeOracle.object_names(ci, obj)) } pub fn async_poll( @@ -562,7 +526,7 @@ pub mod filters { .. }) => { // Need to convert the RustBuffer from our package to the RustBuffer of the external package - let suffix = KotlinCodeOracle.class_name(&name); + let suffix = KotlinCodeOracle.class_name(ci, &name); format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity, it.len, it.data) }}") } _ => call, @@ -588,4 +552,13 @@ pub mod filters { pub fn unquote(nm: &str) -> Result { Ok(nm.trim_matches('`').to_string()) } + + /// Get the idiomatic Kotlin rendering of docstring + pub fn docstring(docstring: &str, spaces: &i32) -> Result { + let middle = textwrap::indent(&textwrap::dedent(docstring), " * "); + let wrapped = format!("/**\n{middle}\n */"); + + let spaces = usize::try_from(*spaces).unwrap_or_default(); + Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) + } } diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs index c6c42194ac..5c0b5d5535 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs @@ -2,10 +2,8 @@ * 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::{ - backend::{CodeType, Literal}, - interface::ObjectImpl, -}; +use super::CodeType; +use crate::{interface::ObjectImpl, ComponentInterface}; #[derive(Debug)] pub struct ObjectCodeType { @@ -20,18 +18,14 @@ impl ObjectCodeType { } impl CodeType for ObjectCodeType { - fn type_label(&self) -> String { - super::KotlinCodeOracle.class_name(&self.name) + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.name) } fn canonical_name(&self) -> String { format!("Type{}", self.name) } - fn literal(&self, _literal: &Literal) -> String { - unreachable!(); - } - fn initialization_fn(&self) -> Option { match &self.imp { ObjectImpl::Struct => None, diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs index 93081e9051..22495fa209 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs @@ -2,11 +2,12 @@ * 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::backend::{CodeType, Literal}; -use crate::interface::{Radix, Type}; +use super::CodeType; +use crate::backend::Literal; +use crate::interface::{ComponentInterface, Radix, Type}; use paste::paste; -fn render_literal(literal: &Literal) -> String { +fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String { fn typed_number(type_: &Type, num_str: String) -> String { match type_ { // Bytes, Shorts and Ints can all be inferred from the type. @@ -54,12 +55,16 @@ macro_rules! impl_code_type_for_primitive { pub struct $T; impl CodeType for $T { - fn type_label(&self) -> String { + fn type_label(&self, _ci: &ComponentInterface) -> String { $class_name.into() } - fn literal(&self, literal: &Literal) -> String { - render_literal(&literal) + fn canonical_name(&self) -> String { + $class_name.into() + } + + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + render_literal(&literal, ci) } } } diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs index 07556fd47c..17781c2220 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::ComponentInterface; #[derive(Debug)] pub struct RecordCodeType { @@ -16,15 +17,11 @@ impl RecordCodeType { } impl CodeType for RecordCodeType { - fn type_label(&self) -> String { - super::KotlinCodeOracle.class_name(&self.id) + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.id) } fn canonical_name(&self) -> String { format!("Type{}", self.id) } - - fn literal(&self, _literal: &Literal) -> String { - unreachable!(); - } } diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs index 624950949a..c7673882d9 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs @@ -2,9 +2,8 @@ * 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 super::{AsCodeType, KotlinCodeOracle}; -use crate::backend::CodeType; -use crate::interface::Variant; +use super::{AsCodeType, CodeType, KotlinCodeOracle}; +use crate::interface::{ComponentInterface, Variant}; #[derive(Debug)] pub(super) struct VariantCodeType { @@ -12,8 +11,12 @@ pub(super) struct VariantCodeType { } impl CodeType for VariantCodeType { - fn type_label(&self) -> String { - KotlinCodeOracle.class_name(self.v.name()) + fn type_label(&self, ci: &ComponentInterface) -> String { + KotlinCodeOracle.class_name(ci, self.v.name()) + } + + fn canonical_name(&self) -> String { + self.v.name().to_string() } } @@ -23,23 +26,8 @@ impl AsCodeType for Variant { } } -#[derive(Debug)] -pub(super) struct ErrorVariantCodeType { - pub v: Variant, -} - -impl CodeType for ErrorVariantCodeType { - fn type_label(&self) -> String { - KotlinCodeOracle.error_name(self.v.name()) - } -} - -pub(super) struct ErrorVariantCodeTypeProvider { - pub v: Variant, -} - -impl AsCodeType for ErrorVariantCodeTypeProvider { +impl AsCodeType for &Variant { fn as_codetype(&self) -> Box { - Box::new(ErrorVariantCodeType { v: self.v.clone() }) + Box::new(VariantCodeType { v: (*self).clone() }) } } diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt index 4d1c099a02..f2ee0ba0d5 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt @@ -3,20 +3,20 @@ internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort() internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort() -internal val uniffiContinuationHandleMap = UniFfiHandleMap>() +internal val uniffiContinuationHandleMap = UniffiHandleMap>() // FFI type for Rust future continuations internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType { - override fun callback(continuationHandle: USize, pollResult: Short) { - uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult) + override fun callback(continuationHandle: UniffiHandle, pollResult: Short) { + uniffiContinuationHandleMap.consumeHandle(continuationHandle).resume(pollResult) } } internal suspend fun uniffiRustCallAsync( - rustFuture: Pointer, - pollFunc: (Pointer, UniFffiRustFutureContinuationCallbackType, USize) -> Unit, - completeFunc: (Pointer, RustCallStatus) -> F, - freeFunc: (Pointer) -> Unit, + rustFuture: UniffiHandle, + pollFunc: (UniffiHandle, UniFffiRustFutureContinuationCallbackType, UniffiHandle) -> Unit, + completeFunc: (UniffiHandle, RustCallStatus) -> F, + freeFunc: (UniffiHandle) -> Unit, liftFunc: (F) -> T, errorHandler: CallStatusErrorHandler ): T { @@ -26,7 +26,7 @@ internal suspend fun uniffiRustCallAsync( pollFunc( rustFuture, uniffiRustFutureContinuationCallback, - uniffiContinuationHandleMap.insert(continuation) + uniffiContinuationHandleMap.newHandle(continuation) ) } } while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY); diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt index f1c58ee971..359777cbd3 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt @@ -3,14 +3,16 @@ // Implement the foreign callback handler for {{ interface_name }} internal class {{ callback_handler_class }} : ForeignCallback { @Suppress("TooGenericExceptionCaught") - override fun invoke(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int { + override fun invoke(handle: UniffiHandle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int { val cb = {{ ffi_converter_name }}.handleMap.get(handle) return when (method) { IDX_CALLBACK_FREE -> { - {{ ffi_converter_name }}.handleMap.remove(handle) - - // Successful return - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + {{ ffi_converter_name }}.handleMap.consumeHandle(handle) + UNIFFI_CALLBACK_SUCCESS + } + IDX_CALLBACK_CLONE -> { + val obj = {{ ffi_converter_name }}.handleMap.get(handle) + outBuf.setValue({{ ffi_converter_name }}.lowerIntoRustBuffer(obj)) UNIFFI_CALLBACK_SUCCESS } {% for meth in methods.iter() -%} @@ -86,7 +88,7 @@ internal class {{ callback_handler_class }} : ForeignCallback { {%- when Some(error_type) %} fun makeCallAndHandleError() : Int = try { makeCall() - } catch (e: {{ error_type|error_type_name }}) { + } catch (e: {{ error_type|type_name(ci) }}) { // Expected error, serialize it into outBuf outBuf.setValue({{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e)) UNIFFI_CALLBACK_ERROR diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt index d0e0686322..2e73ab2e4f 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt @@ -1,63 +1,30 @@ {{- self.add_import("java.util.concurrent.atomic.AtomicLong") }} {{- self.add_import("java.util.concurrent.locks.ReentrantLock") }} -{{- self.add_import("kotlin.concurrent.withLock") }} - -internal typealias Handle = Long -internal class ConcurrentHandleMap( - private val leftMap: MutableMap = mutableMapOf(), -) { - private val lock = java.util.concurrent.locks.ReentrantLock() - private val currentHandle = AtomicLong(0L) - private val stride = 1L - - fun insert(obj: T): Handle = - lock.withLock { - currentHandle.getAndAdd(stride) - .also { handle -> - leftMap[handle] = obj - } - } - - fun get(handle: Handle) = lock.withLock { - leftMap[handle] ?: throw InternalException("No callback in handlemap; this is a Uniffi bug") - } - - fun delete(handle: Handle) { - this.remove(handle) - } - - fun remove(handle: Handle): T? = - lock.withLock { - leftMap.remove(handle) - } -} interface ForeignCallback : com.sun.jna.Callback { - public fun invoke(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int + public fun invoke(handle: UniffiHandle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int } -// Magic number for the Rust proxy to call using the same mechanism as every other method, -// to free the callback once it's dropped by Rust. +// Magic numbers for the Rust proxy to call using the same mechanism as every other method. + internal const val IDX_CALLBACK_FREE = 0 -// Callback return codes +internal const val IDX_CALLBACK_CLONE = 0x7FFF_FFFF; + +// Callback return values internal const val UNIFFI_CALLBACK_SUCCESS = 0 internal const val UNIFFI_CALLBACK_ERROR = 1 internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 -public abstract class FfiConverterCallbackInterface: FfiConverter { - internal val handleMap = ConcurrentHandleMap() - - internal fun drop(handle: Handle) { - handleMap.remove(handle) - } +public abstract class FfiConverterCallbackInterface: FfiConverter { + internal val handleMap = UniffiHandleMap() - override fun lift(value: Handle): CallbackInterface { + override fun lift(value: UniffiHandle): CallbackInterface { return handleMap.get(value) } override fun read(buf: ByteBuffer) = lift(buf.getLong()) - override fun lower(value: CallbackInterface) = handleMap.insert(value) + override fun lower(value: CallbackInterface) = handleMap.newHandle(value) override fun allocationSize(value: CallbackInterface) = 8 diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt index 59a127b1a2..4a88d56aef 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt @@ -2,8 +2,9 @@ {%- let callback_handler_class = format!("UniffiCallbackInterface{}", name) %} {%- let callback_handler_obj = format!("uniffiCallbackInterface{}", name) %} {%- let ffi_init_callback = cbi.ffi_init_callback() %} -{%- let interface_name = cbi|type_name %} +{%- let interface_name = cbi|type_name(ci) %} {%- let methods = cbi.methods() %} +{%- let interface_docstring = cbi.docstring() %} {% include "Interface.kt" %} {% include "CallbackInterfaceImpl.kt" %} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt index 4d7dffa9dd..04150c5d78 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt @@ -1,4 +1,4 @@ -{%- match kotlin_config.custom_types.get(name.as_str()) %} +{%- match config.custom_types.get(name.as_str()) %} {%- when None %} {#- Define the type using typealiases to the builtin #} /** @@ -6,7 +6,7 @@ * is needed because the UDL type name is used in function/method signatures. * It's also what we have an external type that references a custom type. */ -public typealias {{ name }} = {{ builtin|type_name }} +public typealias {{ name }} = {{ builtin|type_name(ci) }} public typealias {{ ffi_converter_name }} = {{ builtin|ffi_converter_name }} {%- when Some with (config) %} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt index a84ab50875..943812911b 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt @@ -7,8 +7,10 @@ {%- if e.is_flat() %} +{%- call kt::docstring(e, 0) %} enum class {{ type_name }} { {% for variant in e.variants() -%} + {%- call kt::docstring(variant, 4) %} {{ variant|variant_name }}{% if loop.last %};{% else %},{% endif %} {%- endfor %} companion object @@ -30,14 +32,16 @@ public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }} {% else %} +{%- call kt::docstring(e, 0) %} sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} { {% for variant in e.variants() -%} + {%- call kt::docstring(variant, 4) %} {% if !variant.has_fields() -%} - object {{ variant|enum_variant|type_name }} : {{ type_name }}() + object {{ variant|type_name(ci) }} : {{ type_name }}() {% else -%} - data class {{ variant|enum_variant|type_name }}( + data class {{ variant|type_name(ci) }}( {% for field in variant.fields() -%} - val {{ field.name()|var_name }}: {{ field|type_name}}{% if loop.last %}{% else %}, {% endif %} + val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %} {% endfor -%} ) : {{ type_name }}() { companion object @@ -50,7 +54,7 @@ sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% e override fun destroy() { when(this) { {%- for variant in e.variants() %} - is {{ type_name }}.{{ variant|enum_variant|type_name }} -> { + is {{ type_name }}.{{ variant|type_name(ci) }} -> { {%- if variant.has_fields() %} {% call kt::destroy_fields(variant) %} {% else -%} @@ -68,7 +72,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } override fun read(buf: ByteBuffer): {{ type_name }} { return when(buf.getInt()) { {%- for variant in e.variants() %} - {{ loop.index }} -> {{ type_name }}.{{ variant|enum_variant|type_name }}{% if variant.has_fields() %}( + {{ loop.index }} -> {{ type_name }}.{{ variant|type_name(ci) }}{% if variant.has_fields() %}( {% for field in variant.fields() -%} {{ field|read_fn }}(buf), {% endfor -%} @@ -80,7 +84,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } override fun allocationSize(value: {{ type_name }}) = when(value) { {%- for variant in e.variants() %} - is {{ type_name }}.{{ variant|enum_variant|type_name }} -> { + is {{ type_name }}.{{ variant|type_name(ci) }} -> { // Add the size for the Int that specifies the variant plus the size needed for all fields ( 4 @@ -95,7 +99,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } override fun write(value: {{ type_name }}, buf: ByteBuffer) { when(value) { {%- for variant in e.variants() %} - is {{ type_name }}.{{ variant|enum_variant|type_name }} -> { + is {{ type_name }}.{{ variant|type_name(ci) }} -> { buf.putInt({{ loop.index }}) {%- for field in variant.fields() %} {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt index 56f685225e..77594c5b80 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt @@ -1,13 +1,13 @@ -{%- let type_name = type_|error_type_name %} -{%- let ffi_converter_name = type_|error_ffi_converter_name %} -{%- let canonical_type_name = type_|error_canonical_name %} +{%- let type_name = type_|type_name(ci) %} +{%- let ffi_converter_name = type_|ffi_converter_name %} +{%- let canonical_type_name = type_|canonical_name %} {% if e.is_flat() %} +{%- call kt::docstring(e, 0) %} sealed class {{ type_name }}(message: String): Exception(message){% if contains_object_references %}, Disposable {% endif %} { - // Each variant is a nested class - // Flat enums carries a string error message, so no special implementation is necessary. {% for variant in e.variants() -%} - class {{ variant|error_variant|type_name }}(message: String) : {{ type_name }}(message) + {%- call kt::docstring(variant, 4) %} + class {{ variant|error_variant_name }}(message: String) : {{ type_name }}(message) {% endfor %} companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> { @@ -15,13 +15,14 @@ sealed class {{ type_name }}(message: String): Exception(message){% if contains_ } } {%- else %} +{%- call kt::docstring(e, 0) %} sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} { - // Each variant is a nested class {% for variant in e.variants() -%} - {%- let variant_name = variant|error_variant|type_name %} + {%- call kt::docstring(variant, 4) %} + {%- let variant_name = variant|error_variant_name %} class {{ variant_name }}( {% for field in variant.fields() -%} - val {{ field.name()|var_name }}: {{ field|type_name}}{% if loop.last %}{% else %}, {% endif %} + val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %} {% endfor -%} ) : {{ type_name }}() { override val message @@ -38,7 +39,7 @@ sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Di override fun destroy() { when(this) { {%- for variant in e.variants() %} - is {{ type_name }}.{{ variant|error_variant|type_name }} -> { + is {{ type_name }}.{{ variant|error_variant_name }} -> { {%- if variant.has_fields() %} {% call kt::destroy_fields(variant) %} {% else -%} @@ -57,7 +58,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } {% if e.is_flat() %} return when(buf.getInt()) { {%- for variant in e.variants() %} - {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant|type_name }}({{ Type::String.borrow()|read_fn }}(buf)) + {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant_name }}({{ Type::String.borrow()|read_fn }}(buf)) {%- endfor %} else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } @@ -65,7 +66,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } return when(buf.getInt()) { {%- for variant in e.variants() %} - {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant|type_name }}({% if variant.has_fields() %} + {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant_name }}({% if variant.has_fields() %} {% for field in variant.fields() -%} {{ field|read_fn }}(buf), {% endfor -%} @@ -82,7 +83,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } {%- else %} return when(value) { {%- for variant in e.variants() %} - is {{ type_name }}.{{ variant|error_variant|type_name }} -> ( + is {{ type_name }}.{{ variant|error_variant_name }} -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4 {%- for field in variant.fields() %} @@ -97,7 +98,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } override fun write(value: {{ type_name }}, buf: ByteBuffer) { when(value) { {%- for variant in e.variants() %} - is {{ type_name }}.{{ variant|error_variant|type_name }} -> { + is {{ type_name }}.{{ variant|error_variant_name }} -> { buf.putInt({{ loop.index }}) {%- for field in variant.fields() %} {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt index 3544b2f9e6..90c84b9fc9 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt @@ -16,7 +16,7 @@ internal interface UniFfiRustTaskCallback : com.sun.jna.Callback { } internal object UniFfiForeignExecutorCallback : com.sun.jna.Callback { - fun callback(handle: USize, delayMs: Int, rustTask: UniFfiRustTaskCallback?, rustTaskData: Pointer?) : Byte { + fun callback(handle: UniffiHandle, delayMs: Int, rustTask: UniFfiRustTaskCallback?, rustTaskData: Pointer?) : Byte { if (rustTask == null) { FfiConverterForeignExecutor.drop(handle) return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS @@ -42,11 +42,11 @@ internal object UniFfiForeignExecutorCallback : com.sun.jna.Callback { } } -public object FfiConverterForeignExecutor: FfiConverter { - internal val handleMap = UniFfiHandleMap() +public object FfiConverterForeignExecutor: FfiConverter { + internal val handleMap = UniffiHandleMap() - internal fun drop(handle: USize) { - handleMap.remove(handle) + internal fun drop(handle: UniffiHandle) { + handleMap.consumeHandle(handle) } internal fun register(lib: _UniFFILib) { @@ -58,26 +58,21 @@ public object FfiConverterForeignExecutor: FfiConverter { {% endmatch %} } - // Number of live handles, exposed so we can test the memory management - public fun handleCount() : Int { - return handleMap.size - } - - override fun allocationSize(value: CoroutineScope) = USize.size + override fun allocationSize(value: CoroutineScope) = 8 - override fun lift(value: USize): CoroutineScope { - return handleMap.get(value) ?: throw RuntimeException("unknown handle in FfiConverterForeignExecutor.lift") + override fun lift(value: UniffiHandle): CoroutineScope { + return handleMap.get(value) } override fun read(buf: ByteBuffer): CoroutineScope { - return lift(USize.readFromBuffer(buf)) + return lift(buf.getLong()) } - override fun lower(value: CoroutineScope): USize { - return handleMap.insert(value) + override fun lower(value: CoroutineScope): UniffiHandle { + return handleMap.newHandle(value) } override fun write(value: CoroutineScope, buf: ByteBuffer) { - lower(value).writeToBuffer(buf) + buf.putLong(lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt b/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt new file mode 100644 index 0000000000..eaa6704806 --- /dev/null +++ b/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt @@ -0,0 +1,56 @@ +internal class UniffiHandleMap { + private val lock = ReentrantReadWriteLock() + private var mapId: Long = UniffiHandleMap.nextMapId() + private val map: MutableMap = mutableMapOf() + // Note: Foreign handles are always odd + private var keyCounter = 1L + + private fun nextKey(): Long = keyCounter.also { + keyCounter = (keyCounter + 2L).and(0xFFFF_FFFF_FFFFL) + } + + private fun makeHandle(key: Long): UniffiHandle = key.or(mapId) + + private fun key(handle: UniffiHandle): Long { + if (handle.and(0x7FFF_0000_0000_0000L) != mapId) { + throw InternalException("Handle map ID mismatch") + } + return handle.and(0xFFFF_FFFF_FFFFL) + } + + fun newHandle(obj: T): UniffiHandle = lock.writeLock().withLock { + val key = nextKey() + map[key] = obj + makeHandle(key) + } + + fun get(handle: UniffiHandle) = lock.readLock().withLock { + map[key(handle)] ?: throw InternalException("Missing key in handlemap: was the handle used after being freed?") + } + + fun cloneHandle(handle: UniffiHandle): UniffiHandle = lock.writeLock().withLock { + val obj = map[key(handle)] ?: throw InternalException("Missing key in handlemap: was the handle used after being freed?") + val clone = nextKey() + map[clone] = obj + makeHandle(clone) + } + + fun consumeHandle(handle: UniffiHandle): T = lock.writeLock().withLock { + map.remove(key(handle)) ?: throw InternalException("Missing key in handlemap: was the handle used after being freed?") + } + + companion object { + // Generate map IDs that are likely to be unique + private var mapIdCounter: Long = {{ ci.namespace_hash() }}.and(0x7FFF) + + // Map ID, shifted into the top 16 bits + internal fun nextMapId(): Long = mapIdCounter.shl(48).also { + // On Kotlin, map ids are only 15 bits to get around signed/unsigned issues + mapIdCounter = ((mapIdCounter + 1).and(0x7FFF)) + } + } +} + +internal fun uniffiHandleIsFromRust(handle: Long): Boolean { + return handle.and(1L) == 0L +} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt index 382a5f7413..e91c8e4119 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt @@ -1,3 +1,8 @@ +// Handles are defined as unsigned in Rust, but that's doesn't work well with JNA. +// We can pretend its signed since Rust handles are opaque values and Kotlin handles don't use all +// 64 bits. +typealias UniffiHandle = Long + // A handful of classes and functions to support the generated data structures. // This would be a good candidate for isolating in its own ffi-support lib. // Error runtime. @@ -73,89 +78,7 @@ private inline fun rustCall(callback: (RustCallStatus) -> U): U { return rustCallWithError(NullCallStatusErrorHandler, callback); } -// IntegerType that matches Rust's `usize` / C's `size_t` -public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, true) { - // This is needed to fill in the gaps of IntegerType's implementation of Number for Kotlin. - override fun toByte() = toInt().toByte() - // Needed until https://youtrack.jetbrains.com/issue/KT-47902 is fixed. - @Deprecated("`toInt().toChar()` is deprecated") - override fun toChar() = toInt().toChar() - override fun toShort() = toInt().toShort() - - fun writeToBuffer(buf: ByteBuffer) { - // Make sure we always write usize integers using native byte-order, since they may be - // casted to pointer values - buf.order(ByteOrder.nativeOrder()) - try { - when (Native.SIZE_T_SIZE) { - 4 -> buf.putInt(toInt()) - 8 -> buf.putLong(toLong()) - else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}") - } - } finally { - buf.order(ByteOrder.BIG_ENDIAN) - } - } - - companion object { - val size: Int - get() = Native.SIZE_T_SIZE - - fun readFromBuffer(buf: ByteBuffer) : USize { - // Make sure we always read usize integers using native byte-order, since they may be - // casted from pointer values - buf.order(ByteOrder.nativeOrder()) - try { - return when (Native.SIZE_T_SIZE) { - 4 -> USize(buf.getInt().toLong()) - 8 -> USize(buf.getLong()) - else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}") - } - } finally { - buf.order(ByteOrder.BIG_ENDIAN) - } - } - } -} - - -// Map handles to objects -// -// This is used when the Rust code expects an opaque pointer to represent some foreign object. -// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an -// object reference , nor does it support leaking a reference to Rust. -// -// Instead, this class maps USize values to objects so that we can pass a pointer-sized type to -// Rust when it needs an opaque pointer. -// -// TODO: refactor callbacks to use this class -internal class UniFfiHandleMap { - private val map = ConcurrentHashMap() - // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible - // values seems like enough. If somehow we generate 4 billion handles, then this will wrap - // around back to zero and we can assume the first handle generated will have been dropped by - // then. - private val counter = java.util.concurrent.atomic.AtomicInteger(0) - - val size: Int - get() = map.size - - fun insert(obj: T): USize { - val handle = USize(counter.getAndAdd(1).toLong()) - map.put(handle, obj) - return handle - } - - fun get(handle: USize): T? { - return map.get(handle) - } - - fun remove(handle: USize): T? { - return map.remove(handle) - } -} - // FFI type for Rust future continuations internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback { - fun callback(continuationHandle: USize, pollResult: Short); + fun callback(continuationHandle: Long, pollResult: Short); } diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt b/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt index c8610d4d65..c68e972c95 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt @@ -1,9 +1,11 @@ +{%- call kt::docstring_value(interface_docstring, 0) %} public interface {{ interface_name }} { {% for meth in methods.iter() -%} + {%- call kt::docstring(meth, 4) %} {% if meth.is_async() -%}suspend {% endif -%} fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %}) {%- match meth.return_type() -%} - {%- when Some with (return_type) %}: {{ return_type|type_name -}} + {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}} {%- else -%} {%- endmatch %} {% endfor %} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt index 5fb635342d..776c402727 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt @@ -1,5 +1,5 @@ -{%- let key_type_name = key_type|type_name %} -{%- let value_type_name = value_type|type_name %} +{%- let key_type_name = key_type|type_name(ci) %} +{%- let value_type_name = value_type|type_name(ci) %} public object {{ ffi_converter_name }}: FfiConverterRustBuffer> { override fun read(buf: ByteBuffer): Map<{{ key_type_name }}, {{ value_type_name }}> { val len = buf.getInt() diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt index 2c15119a3c..3ac70256e5 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt @@ -1,49 +1,30 @@ {{- self.add_import("java.util.concurrent.atomic.AtomicLong") }} {{- self.add_import("java.util.concurrent.atomic.AtomicBoolean") }} -// Interface implemented by anything that can contain an object reference. -// -// Such types expose a `destroy()` method that must be called to cleanly -// dispose of the contained objects. Failure to call this method may result -// in memory leaks. -// -// The easiest way to ensure this method is called is to use the `.use` -// helper method to execute a block and destroy the object at the end. -interface Disposable { - fun destroy() - companion object { - fun destroy(vararg args: Any?) { - args.filterIsInstance() - .forEach(Disposable::destroy) - } - } -} -inline fun T.use(block: (T) -> R) = - try { - block(this) - } finally { - try { - // N.B. our implementation is on the nullable type `Disposable?`. - this?.destroy() - } catch (e: Throwable) { - // swallow - } - } +// Wraps `UniffiHandle` to pass to object constructors. +// +// This class exists because `UniffiHandle` is a typealias to `Long`. If the object constructor +// inputs `UniffiHandle` directly and the user defines a primary constructor than inputs a single +// `Long` or `ULong` input, then we get JVM signature conflicts. To avoid this, we pass this type +// in instead. +// +// Let's try to remove this when we update the code based on ADR-0008. +data class UniffiHandleWrapper(val handle: UniffiHandle) // The base class for all UniFFI Object types. // -// This class provides core operations for working with the Rust `Arc` pointer to -// the live Rust struct on the other side of the FFI. +// This class provides core operations for working with the Rust handle to the live Rust struct on +// the other side of the FFI. // // There's some subtlety here, because we have to be careful not to operate on a Rust // struct after it has been dropped, and because we must expose a public API for freeing // the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: // -// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to +// * Each `FFIObject` instance holds an opaque handle to the underlying Rust struct. +// Method calls need to read this handle from the object's state and pass it in to // the Rust FFI. // -// * When an `FFIObject` is no longer needed, its pointer should be passed to a +// * When an `FFIObject` is no longer needed, its handle should be passed to a // special destructor function provided by the Rust FFI, which will drop the // underlying Rust struct. // @@ -60,13 +41,13 @@ inline fun T.use(block: (T) -> R) = // the destructor has been called, and must never call the destructor more than once. // Doing so may trigger memory unsafety. // -// If we try to implement this with mutual exclusion on access to the pointer, there is the +// If we try to implement this with mutual exclusion on access to the handle, there is the // possibility of a race between a method call and a concurrent call to `destroy`: // -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. +// * Thread A starts a method call, reads the value of the handle, but is interrupted +// before it can pass the handle over the FFI to Rust. // * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// * Thread A resumes, passing the already-read handle value to Rust and triggering // a use-after-free. // // One possible solution would be to use a `ReadWriteLock`, with each method call taking @@ -113,24 +94,24 @@ inline fun T.use(block: (T) -> R) = // abstract class FFIObject: Disposable, AutoCloseable { - constructor(pointer: Pointer) { - this.pointer = pointer + constructor(handle: UniffiHandle) { + this.handle = handle } /** * This constructor can be used to instantiate a fake object. * * **WARNING: Any object instantiated with this constructor cannot be passed to an actual Rust-backed object.** - * Since there isn't a backing [Pointer] the FFI lower functions will crash. - * @param noPointer Placeholder value so we can have a constructor separate from the default empty one that may be + * Since there isn't a backing [UniffiHandle] the FFI lower functions will crash. + * @param NoHandle Placeholder value so we can have a constructor separate from the default empty one that may be * implemented for classes extending [FFIObject]. */ @Suppress("UNUSED_PARAMETER") - constructor(noPointer: NoPointer) { - this.pointer = null + constructor(noHandle: NoHandle) { + this.handle = null } - protected val pointer: Pointer? + internal val handle: UniffiHandle? private val wasDestroyed = AtomicBoolean(false) private val callCounter = AtomicLong(1) @@ -139,6 +120,12 @@ abstract class FFIObject: Disposable, AutoCloseable { // To be overridden in subclasses. } + fun uniffiCloneHandle(): UniffiHandle { + return rustCall() { status -> + _UniFFILib.INSTANCE.{{ obj.ffi_object_clone().name() }}(handle!!, status) + } + } + override fun destroy() { // Only allow a single call to this method. // TODO: maybe we should log a warning if called more than once? @@ -155,7 +142,7 @@ abstract class FFIObject: Disposable, AutoCloseable { this.destroy() } - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + internal inline fun callWithHandle(block: (handle: UniffiHandle) -> R): R { // Check and increment the call counter, to keep the object alive. // This needs a compare-and-set retry loop in case of concurrent updates. do { @@ -167,9 +154,9 @@ abstract class FFIObject: Disposable, AutoCloseable { throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") } } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. + // Now we can safely do the method call without the handle being freed concurrently. try { - return block(this.pointer!!) + return block(this.uniffiCloneHandle()) } finally { // This decrement always matches the increment we performed above. if (this.callCounter.decrementAndGet() == 0L) { @@ -180,4 +167,4 @@ abstract class FFIObject: Disposable, AutoCloseable { } /** Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. */ -object NoPointer \ No newline at end of file +object NoHandle diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt index 6003b606d2..9719c24cda 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt @@ -1,28 +1,31 @@ {%- let obj = ci|get_object_definition(name) %} {%- if self.include_once_check("ObjectRuntime.kt") %}{% include "ObjectRuntime.kt" %}{% endif %} -{%- let (interface_name, impl_class_name) = obj|object_names %} +{%- let (interface_name, impl_class_name) = obj|object_names(ci) %} {%- let methods = obj.methods() %} +{%- let interface_docstring = obj.docstring() %} {% include "Interface.kt" %} +{%- call kt::docstring(obj, 0) %} open class {{ impl_class_name }} : FFIObject, {{ interface_name }} { - constructor(pointer: Pointer): super(pointer) + constructor(handleWrapper: UniffiHandleWrapper): super(handleWrapper.handle) /** * This constructor can be used to instantiate a fake object. * * **WARNING: Any object instantiated with this constructor cannot be passed to an actual Rust-backed object.** - * Since there isn't a backing [Pointer] the FFI lower functions will crash. - * @param noPointer Placeholder value so we can have a constructor separate from the default empty one that may be + * Since there isn't a backing [UniffiHandle] the FFI lower functions will crash. + * @param noHandle Placeholder value so we can have a constructor separate from the default empty one that may be * implemented for classes extending [FFIObject]. */ - constructor(noPointer: NoPointer): super(noPointer) + constructor(noHandle: NoHandle): super(noHandle) {%- match obj.primary_constructor() %} {%- when Some with (cons) %} + {%- call kt::docstring(cons, 4) %} constructor({% call kt::arg_list_decl(cons) -%}) : - this({% call kt::to_ffi_call(cons) %}) + this(UniffiHandleWrapper({% call kt::to_ffi_call(cons) %})) {%- when None %} {%- endmatch %} @@ -35,28 +38,29 @@ open class {{ impl_class_name }} : FFIObject, {{ interface_name }} { * Clients **must** call this method once done with the object, or cause a memory leak. */ override protected fun freeRustArcPtr() { - this.pointer?.let { ptr -> + this.handle?.let { handle -> rustCall() { status -> - _UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(ptr, status) + _UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(handle, status) } } } {% for meth in obj.methods() -%} + {%- call kt::docstring(meth, 4) %} {%- match meth.throws_type() -%} {%- when Some with (throwable) %} - @Throws({{ throwable|error_type_name }}::class) + @Throws({{ throwable|type_name(ci) }}::class) {%- else -%} {%- endmatch -%} {%- if meth.is_async() %} @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") override suspend fun {{ meth.name()|fn_name }}( {%- call kt::arg_list_decl(meth) -%} - ){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name }}{% when None %}{%- endmatch %} { + ){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} { return uniffiRustCallAsync( - callWithPointer { thisPtr -> + callWithHandle { uniffiHandle -> _UniFFILib.INSTANCE.{{ meth.ffi_func().name() }}( - thisPtr, + uniffiHandle, {% call kt::arg_list_lowered(meth) %} ) }, @@ -73,7 +77,7 @@ open class {{ impl_class_name }} : FFIObject, {{ interface_name }} { // Error FFI converter {%- match meth.throws_type() %} {%- when Some(e) %} - {{ e|error_type_name }}.ErrorHandler, + {{ e|type_name(ci) }}.ErrorHandler, {%- when None %} NullCallStatusErrorHandler, {%- endmatch %} @@ -82,20 +86,16 @@ open class {{ impl_class_name }} : FFIObject, {{ interface_name }} { {%- else -%} {%- match meth.return_type() -%} {%- when Some with (return_type) -%} - override fun {{ meth.name()|fn_name }}( - {%- call kt::arg_list_protocol(meth) -%} - ): {{ return_type|type_name }} = - callWithPointer { + override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name(ci) }} = + callWithHandle { {%- call kt::to_ffi_call_with_prefix("it", meth) %} }.let { {{ return_type|lift_fn }}(it) } {%- when None -%} - override fun {{ meth.name()|fn_name }}( - {%- call kt::arg_list_protocol(meth) -%} - ) = - callWithPointer { + override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) = + callWithHandle { {%- call kt::to_ffi_call_with_prefix("it", meth) %} } {% endmatch %} @@ -106,7 +106,7 @@ open class {{ impl_class_name }} : FFIObject, {{ interface_name }} { {%- match tm %} {%- when UniffiTrait::Display { fmt } %} override fun toString(): String = - callWithPointer { + callWithHandle { {%- call kt::to_ffi_call_with_prefix("it", fmt) %} }.let { {{ fmt.return_type().unwrap()|lift_fn }}(it) @@ -116,7 +116,7 @@ open class {{ impl_class_name }} : FFIObject, {{ interface_name }} { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is {{ impl_class_name}}) return false - return callWithPointer { + return callWithHandle { {%- call kt::to_ffi_call_with_prefix("it", eq) %} }.let { {{ eq.return_type().unwrap()|lift_fn }}(it) @@ -124,7 +124,7 @@ open class {{ impl_class_name }} : FFIObject, {{ interface_name }} { } {%- when UniffiTrait::Hash { hash } %} override fun hashCode(): Int = - callWithPointer { + callWithHandle { {%- call kt::to_ffi_call_with_prefix("it", hash) %} }.let { {{ hash.return_type().unwrap()|lift_fn }}(it).toInt() @@ -136,8 +136,9 @@ open class {{ impl_class_name }} : FFIObject, {{ interface_name }} { {% if !obj.alternate_constructors().is_empty() -%} companion object { {% for cons in obj.alternate_constructors() -%} + {%- call kt::docstring(cons, 4) %} fun {{ cons.name()|fn_name }}({% call kt::arg_list_decl(cons) %}): {{ impl_class_name }} = - {{ impl_class_name }}({% call kt::to_ffi_call(cons) %}) + {{ impl_class_name }}(UniffiHandleWrapper({% call kt::to_ffi_call(cons) %})) {% endfor %} } {% else %} @@ -152,35 +153,44 @@ open class {{ impl_class_name }} : FFIObject, {{ interface_name }} { {% include "CallbackInterfaceImpl.kt" %} {%- endif %} -public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> { +public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, UniffiHandle> { {%- if obj.is_trait_interface() %} - internal val handleMap = ConcurrentHandleMap<{{ interface_name }}>() + internal val handleMap = UniffiHandleMap<{{ type_name }}>() {%- endif %} - override fun lower(value: {{ type_name }}): Pointer { - {%- match obj.imp() %} - {%- when ObjectImpl::Struct %} - return value.callWithPointer { it } - {%- when ObjectImpl::Trait %} - return Pointer(handleMap.insert(value)) - {%- endmatch %} + override fun lower(value: {{ type_name }}): UniffiHandle { + {%- if obj.is_trait_interface() %} + if (value is {{ impl_class_name }}) { + // If we're wrapping a trait implemented in Rust, return that handle directly rather + // than wrapping it again in Kotlin. + return value.uniffiCloneHandle() + } else { + return handleMap.newHandle(value) + } + {%- else %} + return value.uniffiCloneHandle() + {%- endif %} } - override fun lift(value: Pointer): {{ type_name }} { - return {{ impl_class_name }}(value) + override fun lift(value: UniffiHandle): {{ type_name }} { + {%- if obj.is_trait_interface() %} + if (uniffiHandleIsFromRust(value)) { + return {{ impl_class_name }}(UniffiHandleWrapper(value)) + } else { + return handleMap.consumeHandle(value) + } + {%- else %} + return {{ impl_class_name }}(UniffiHandleWrapper(value)) + {%- endif %} } override fun read(buf: ByteBuffer): {{ type_name }} { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return lift(Pointer(buf.getLong())) + return lift(buf.getLong()) } override fun allocationSize(value: {{ type_name }}) = 8 override fun write(value: {{ type_name }}, buf: ByteBuffer) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(lower(value))) + buf.putLong(lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt index 6830b51863..56cb5f87a5 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt @@ -1,4 +1,4 @@ -{%- let inner_type_name = inner_type|type_name %} +{%- let inner_type_name = inner_type|type_name(ci) %} public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_name }}?> { override fun read(buf: ByteBuffer): {{ inner_type_name }}? { diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt index c247889714..d2b6f18c7c 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt @@ -1,11 +1,13 @@ {%- let rec = ci|get_record_definition(name) %} {%- if rec.has_fields() %} +{%- call kt::docstring(rec, 0) %} data class {{ type_name }} ( {%- for field in rec.fields() %} - var {{ field.name()|var_name }}: {{ field|type_name -}} + {%- call kt::docstring(field, 4) %} + {% if config.generate_immutable_records() %}val{% else %}var{% endif %} {{ field.name()|var_name }}: {{ field|type_name(ci) -}} {%- match field.default_value() %} - {%- when Some with(literal) %} = {{ literal|render_literal(field) }} + {%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }} {%- else %} {%- endmatch -%} {% if !loop.last %}, {% endif %} @@ -20,6 +22,7 @@ data class {{ type_name }} ( companion object } {%- else -%} +{%- call kt::docstring(rec, 0) %} class {{ type_name }} { override fun equals(other: Any?): Boolean { return other is {{ type_name }} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt index b5c699ba3b..876d1bc05e 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt @@ -1,4 +1,4 @@ -{%- let inner_type_name = inner_type|type_name %} +{%- let inner_type_name = inner_type|type_name(ci) %} public object {{ ffi_converter_name }}: FfiConverterRustBuffer> { override fun read(buf: ByteBuffer): List<{{ inner_type_name }}> { diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt index 2fa1737e48..c6f2a41c17 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt @@ -1,12 +1,13 @@ +{%- call kt::docstring(func, 8) %} {%- if func.is_async() %} {%- match func.throws_type() -%} {%- when Some with (throwable) %} -@Throws({{ throwable|error_type_name }}::class) +@Throws({{ throwable|type_name(ci) }}::class) {%- else -%} {%- endmatch %} @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") -suspend fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}){% match func.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name }}{% when None %}{%- endmatch %} { +suspend fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}){% match func.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} { return uniffiRustCallAsync( _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call kt::arg_list_lowered(func) %}), {{ func|async_poll(ci) }}, @@ -22,7 +23,7 @@ suspend fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}){% ma // Error FFI converter {%- match func.throws_type() %} {%- when Some(e) %} - {{ e|error_type_name }}.ErrorHandler, + {{ e|type_name(ci) }}.ErrorHandler, {%- when None %} NullCallStatusErrorHandler, {%- endmatch %} @@ -32,14 +33,14 @@ suspend fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}){% ma {%- else %} {%- match func.throws_type() -%} {%- when Some with (throwable) %} -@Throws({{ throwable|error_type_name }}::class) +@Throws({{ throwable|type_name(ci) }}::class) {%- else -%} {%- endmatch -%} {%- match func.return_type() -%} {%- when Some with (return_type) %} -fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}): {{ return_type|type_name }} { +fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}): {{ return_type|type_name(ci) }} { return {{ return_type|lift_fn }}({% call kt::to_ffi_call(func) %}) } {% when None %} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt b/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt index 89546eac92..5e8847e25f 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt @@ -1,7 +1,37 @@ {%- import "macros.kt" as kt %} +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance() + .forEach(Disposable::destroy) + } + } +} + +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + {%- for type_ in ci.iter_types() %} -{%- let type_name = type_|type_name %} +{%- let type_name = type_|type_name(ci) %} {%- let ffi_converter_name = type_|ffi_converter_name %} {%- let canonical_type_name = type_|canonical_name %} {%- let contains_object_references = ci.item_contains_object_references(type_) %} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt b/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt index 11a06f35fe..d0139312b6 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt @@ -7,7 +7,7 @@ {%- macro to_ffi_call(func) -%} {%- match func.throws_type() %} {%- when Some with (e) %} - rustCallWithError({{ e|error_type_name }}) + rustCallWithError({{ e|type_name(ci) }}) {%- else %} rustCall() {%- endmatch %} { _status -> @@ -18,7 +18,7 @@ {%- macro to_ffi_call_with_prefix(prefix, func) %} {%- match func.throws_type() %} {%- when Some with (e) %} - rustCallWithError({{ e|error_type_name }}) + rustCallWithError({{ e|type_name(ci) }}) {%- else %} rustCall() {%- endmatch %} { _status -> @@ -42,9 +42,9 @@ {% macro arg_list_decl(func) %} {%- for arg in func.arguments() -%} - {{ arg.name()|var_name }}: {%- if !ci.is_name_used_as_error(arg|type_name) %} {{ arg|type_name -}} {%- else %} {{ arg|error_type_name -}} {% endif -%} + {{ arg.name()|var_name }}: {{ arg|type_name(ci) }} {%- match arg.default_value() %} - {%- when Some with(literal) %} = {{ literal|render_literal(arg) }} + {%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }} {%- else %} {%- endmatch %} {%- if !loop.last %}, {% endif -%} @@ -53,7 +53,7 @@ {% macro arg_list_protocol(func) %} {%- for arg in func.arguments() -%} - {{ arg.name()|var_name }}: {%- if !ci.is_name_used_as_error(arg|type_name) %} {{ arg|type_name -}} {%- else %} {{ arg|error_type_name -}} {% endif -%} + {{ arg.name()|var_name }}: {{ arg|type_name(ci) }} {%- if !loop.last %}, {% endif -%} {%- endfor %} {%- endmacro %} @@ -75,3 +75,15 @@ this.{{ field.name()|var_name }}{%- if !loop.last %}, {% endif -%} {% endfor -%}) {%- endmacro -%} + +{%- macro docstring_value(maybe_docstring, indent_spaces) %} +{%- match maybe_docstring %} +{%- when Some(docstring) %} +{{ docstring|docstring(indent_spaces) }} +{%- else %} +{%- endmatch %} +{%- endmacro %} + +{%- macro docstring(defn, indent_spaces) %} +{%- call docstring_value(defn.docstring(), indent_spaces) %} +{%- endmacro %} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt b/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt index 9ee4229018..5c43941046 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt @@ -1,6 +1,8 @@ // This file was autogenerated by some hot garbage in the `uniffi` crate. // Trust me, you don't want to mess with it! +{%- call kt::docstring_value(ci.namespace_docstring(), 0) %} + @file:Suppress("NAME_SHADOWING") package {{ config.package_name() }}; @@ -29,12 +31,15 @@ import java.nio.ByteOrder import java.nio.CharBuffer import java.nio.charset.CodingErrorAction import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.withLock {%- for req in self.imports() %} {{ req.render() }} {%- endfor %} {% include "RustBufferTemplate.kt" %} +{% include "HandleMap.kt" %} {% include "FfiConverterTemplate.kt" %} {% include "Helpers.kt" %} diff --git a/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs b/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs index d2e2a6416e..9c93965e35 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::backend::Literal; #[derive(Debug)] pub struct CallbackInterfaceCodeType { diff --git a/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs b/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs index 4f2d196cfa..b91bcbe18f 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal, Type}; +use super::CodeType; +use crate::backend::{Literal, Type}; #[derive(Debug)] pub struct OptionalCodeType { diff --git a/uniffi_bindgen/src/bindings/python/gen_python/custom.rs b/uniffi_bindgen/src/bindings/python/gen_python/custom.rs index 95d0d4364a..f735899f3d 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/custom.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/custom.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::backend::CodeType; +use super::CodeType; #[derive(Debug)] pub struct CustomCodeType { diff --git a/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs b/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs index 3776e109e1..83ce177e07 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::backend::Literal; #[derive(Debug)] pub struct EnumCodeType { diff --git a/uniffi_bindgen/src/bindings/python/gen_python/error.rs b/uniffi_bindgen/src/bindings/python/gen_python/error.rs index fc92de0a38..aa1c0db75e 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/error.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/error.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use crate::backend::{ Literal}; +use super::CodeType; #[derive(Debug)] pub struct ErrorCodeType { diff --git a/uniffi_bindgen/src/bindings/python/gen_python/executor.rs b/uniffi_bindgen/src/bindings/python/gen_python/executor.rs index 2834d9e2ac..be3ba1d791 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/executor.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/executor.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::backend::CodeType; +use super::CodeType; #[derive(Debug)] pub struct ForeignExecutorCodeType; diff --git a/uniffi_bindgen/src/bindings/python/gen_python/external.rs b/uniffi_bindgen/src/bindings/python/gen_python/external.rs index 5a07d269fe..0d19c4bb3c 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/external.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/external.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::backend::CodeType; +use super::CodeType; #[derive(Debug)] pub struct ExternalCodeType { diff --git a/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs b/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs index e43503520e..07ff5cd0d7 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::backend::Literal; use paste::paste; macro_rules! impl_code_type_for_miscellany { diff --git a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index 9199bbefd0..d21707d5e6 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -10,8 +10,9 @@ use serde::{Deserialize, Serialize}; use std::borrow::Borrow; use std::cell::RefCell; use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt::Debug; -use crate::backend::{CodeType, TemplateExpression}; +use crate::backend::TemplateExpression; use crate::interface::*; use crate::BindingsConfig; @@ -26,6 +27,44 @@ mod object; mod primitives; mod record; +/// A trait tor the implementation. +trait CodeType: Debug { + /// The language specific label used to reference this type. This will be used in + /// method signatures and property declarations. + fn type_label(&self) -> String; + + /// A representation of this type label that can be used as part of another + /// identifier. e.g. `read_foo()`, or `FooInternals`. + /// + /// This is especially useful when creating specialized objects or methods to deal + /// with this type only. + fn canonical_name(&self) -> String { + self.type_label() + } + + fn literal(&self, _literal: &Literal) -> String { + unimplemented!("Unimplemented for {}", self.type_label()) + } + + /// Name of the FfiConverter + /// + /// This is the object that contains the lower, write, lift, and read methods for this type. + fn ffi_converter_name(&self) -> String { + format!("FfiConverter{}", self.canonical_name()) + } + + /// A list of imports that are needed if this type is in use. + /// Classes are imported exactly once. + fn imports(&self) -> Option> { + None + } + + /// Function to run at startup + fn initialization_fn(&self) -> Option { + None + } +} + // Taken from Python's `keyword.py` module. static KEYWORDS: Lazy> = Lazy::new(|| { let kwlist = vec![ @@ -311,19 +350,15 @@ impl PythonCodeOracle { FfiType::UInt64 => "ctypes.c_uint64".to_string(), FfiType::Float32 => "ctypes.c_float".to_string(), FfiType::Float64 => "ctypes.c_double".to_string(), - FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(), + FfiType::Handle => "ctypes.c_uint64".to_string(), FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { Some(suffix) => format!("_UniffiRustBuffer{suffix}"), None => "_UniffiRustBuffer".to_string(), }, FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(), FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(), - // Pointer to an `asyncio.EventLoop` instance - FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(), FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(), - FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(), FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(), - FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(), } } @@ -343,7 +378,7 @@ impl PythonCodeOracle { } } -pub trait AsCodeType { +trait AsCodeType { fn as_codetype(&self) -> Box; } @@ -401,43 +436,45 @@ pub mod filters { use super::*; pub use crate::backend::filters::*; - pub fn type_name(as_ct: &impl AsCodeType) -> Result { + pub(super) fn type_name(as_ct: &impl AsCodeType) -> Result { Ok(as_ct.as_codetype().type_label()) } - pub fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result { + pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result { Ok(String::from("_Uniffi") + &as_ct.as_codetype().ffi_converter_name()[3..]) } - pub fn canonical_name(as_ct: &impl AsCodeType) -> Result { + pub(super) fn canonical_name(as_ct: &impl AsCodeType) -> Result { Ok(as_ct.as_codetype().canonical_name()) } - pub fn lift_fn(as_ct: &impl AsCodeType) -> Result { + pub(super) fn lift_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!("{}.lift", ffi_converter_name(as_ct)?)) } - pub fn lower_fn(as_ct: &impl AsCodeType) -> Result { + pub(super) fn check_fn(as_ct: &impl AsCodeType) -> Result { + Ok(format!("{}.check", ffi_converter_name(as_ct)?)) + } + + pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!("{}.lower", ffi_converter_name(as_ct)?)) } - pub fn read_fn(as_ct: &impl AsCodeType) -> Result { + pub(super) fn read_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!("{}.read", ffi_converter_name(as_ct)?)) } - pub fn write_fn(as_ct: &impl AsCodeType) -> Result { + pub(super) fn write_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!("{}.write", ffi_converter_name(as_ct)?)) } - pub fn literal_py(literal: &Literal, as_ct: &impl AsCodeType) -> Result { + pub(super) fn literal_py( + literal: &Literal, + as_ct: &impl AsCodeType, + ) -> Result { Ok(as_ct.as_codetype().literal(literal)) } - pub fn ffi_type(type_: &Type) -> Result { - Ok(type_.into()) - } - - /// Get the Python syntax for representing a given low-level `FfiType`. pub fn ffi_type_name(type_: &FfiType) -> Result { Ok(PythonCodeOracle::ffi_type_label(type_)) } @@ -466,4 +503,17 @@ pub mod filters { pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> { Ok(PythonCodeOracle.object_names(obj)) } + + /// Get the idiomatic Python rendering of docstring + pub fn docstring(docstring: &str, spaces: &i32) -> Result { + let docstring = textwrap::dedent(docstring); + let wrapped = if docstring.lines().count() > 1 { + format!("\"\"\"\n{docstring}\n\"\"\"") + } else { + format!("\"\"\"{docstring}\"\"\"") + }; + + let spaces = usize::try_from(*spaces).unwrap_or_default(); + Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) + } } diff --git a/uniffi_bindgen/src/bindings/python/gen_python/object.rs b/uniffi_bindgen/src/bindings/python/gen_python/object.rs index 4ca7a4686a..1165bb0e54 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/object.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/object.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::backend::Literal; #[derive(Debug)] pub struct ObjectCodeType { diff --git a/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs b/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs index 5922f735fb..4b3edecad4 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::backend::Literal; use crate::interface::Radix; use paste::paste; diff --git a/uniffi_bindgen/src/bindings/python/gen_python/record.rs b/uniffi_bindgen/src/bindings/python/gen_python/record.rs index b883dfe337..df00f98e8b 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/record.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/record.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::backend::Literal; #[derive(Debug)] pub struct RecordCodeType { diff --git a/uniffi_bindgen/src/bindings/python/templates/Async.py b/uniffi_bindgen/src/bindings/python/templates/Async.py index 51bc31b595..f75bfcd821 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Async.py +++ b/uniffi_bindgen/src/bindings/python/templates/Async.py @@ -3,13 +3,13 @@ _UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1 # Stores futures for _uniffi_continuation_callback -_UniffiContinuationPointerManager = _UniffiPointerManager() +_UNIFFI_CONTINUATION_HANDLE_MAP = UniffiHandleMap() # Continuation callback for async functions # lift the return value or error and resolve the future, causing the async function to resume. @_UNIFFI_FUTURE_CONTINUATION_T -def _uniffi_continuation_callback(future_ptr, poll_code): - (eventloop, future) = _UniffiContinuationPointerManager.release_pointer(future_ptr) +def _uniffi_continuation_callback(handle, poll_code): + (eventloop, future) = _UNIFFI_CONTINUATION_HANDLE_MAP.consume_handle(handle) eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code) def _uniffi_set_future_result(future, poll_code): @@ -26,7 +26,7 @@ async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, ffi_poll( rust_future, _uniffi_continuation_callback, - _UniffiContinuationPointerManager.new_pointer((eventloop, future)), + _UNIFFI_CONTINUATION_HANDLE_MAP.new_handle((eventloop, future)), ) poll_code = await future if poll_code == _UNIFFI_RUST_FUTURE_POLL_READY: diff --git a/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py b/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py index 6775e9e132..25d264fc80 100644 --- a/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py +++ b/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py @@ -1,16 +1,20 @@ -class _UniffiConverterBool(_UniffiConverterPrimitive): +class _UniffiConverterBool: @classmethod def check(cls, value): return not not value + @classmethod + def lower(cls, value): + return 1 if value else 0 + + @staticmethod + def lift(value): + return value != 0 + @classmethod def read(cls, buf): return cls.lift(buf.read_u8()) @classmethod - def write_unchecked(cls, value, buf): + def write(cls, value, buf): buf.write_u8(value) - - @staticmethod - def lift(value): - return value != 0 diff --git a/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py b/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py index 196b5b29fa..f649b5e41a 100644 --- a/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py +++ b/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py @@ -7,10 +7,13 @@ def read(buf): return buf.read(size) @staticmethod - def write(value, buf): + def check(value): try: memoryview(value) except TypeError: raise TypeError("a bytes-like object is required, not {!r}".format(type(value).__name__)) + + @staticmethod + def write(value, buf): buf.write_i32(len(value)) buf.write(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py index f9a4768a5c..58210dc1e8 100644 --- a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py +++ b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py @@ -51,10 +51,11 @@ def makeCallAndHandleReturn(): cb = {{ ffi_converter_name }}._handle_map.get(handle) if method == IDX_CALLBACK_FREE: - {{ ffi_converter_name }}._handle_map.remove(handle) - - # Successfull return - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + {{ ffi_converter_name }}._handle_map.consume_handle(handle) + return _UNIFFI_CALLBACK_SUCCESS + if method == IDX_CALLBACK_CLONE: + obj = {{ ffi_converter_name }}._handle_map.get(handle) + buf_ptr[0] = uniffi_lower_into_rust_buffer({{ ffi_converter_name }}, obj) return _UNIFFI_CALLBACK_SUCCESS {% for meth in methods.iter() -%} diff --git a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py index 1b7346ba4c..0d1c31f458 100644 --- a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py +++ b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py @@ -1,48 +1,17 @@ -import threading - -class ConcurrentHandleMap: - """ - A map where inserting, getting and removing data is synchronized with a lock. - """ - - def __init__(self): - # type Handle = int - self._left_map = {} # type: Dict[Handle, Any] - - self._lock = threading.Lock() - self._current_handle = 0 - self._stride = 1 - - def insert(self, obj): - with self._lock: - handle = self._current_handle - self._current_handle += self._stride - self._left_map[handle] = obj - return handle - - def get(self, handle): - with self._lock: - obj = self._left_map.get(handle) - if not obj: - raise InternalError("No callback in handlemap; this is a uniffi bug") - return obj - - def remove(self, handle): - with self._lock: - if handle in self._left_map: - obj = self._left_map.pop(handle) - return obj - -# Magic number for the Rust proxy to call using the same mechanism as every other method, -# to free the callback once it's dropped by Rust. +# Magic numbers for the Rust proxy to call using the same mechanism as every other method. + +# Dec-ref the callback object IDX_CALLBACK_FREE = 0 +# Inc-ref the callback object +IDX_CALLBACK_CLONE = 0x7FFF_FFFF + # Return codes for callback calls _UNIFFI_CALLBACK_SUCCESS = 0 _UNIFFI_CALLBACK_ERROR = 1 _UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 class UniffiCallbackInterfaceFfiConverter: - _handle_map = ConcurrentHandleMap() + _handle_map = UniffiHandleMap() @classmethod def lift(cls, handle): @@ -53,9 +22,13 @@ def read(cls, buf): handle = buf.read_u64() cls.lift(handle) + @classmethod + def check(cls, cb): + pass + @classmethod def lower(cls, cb): - handle = cls._handle_map.insert(cb) + handle = cls._handle_map.new_handle(cb) return handle @classmethod diff --git a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py index dbfa094562..ab9e725a05 100644 --- a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py @@ -4,6 +4,7 @@ {%- let ffi_init_callback = cbi.ffi_init_callback() %} {%- let protocol_name = type_name.clone() %} {%- let methods = cbi.methods() %} +{%- let protocol_docstring = cbi.docstring() %} {% include "Protocol.py" %} {% include "CallbackInterfaceImpl.py" %} diff --git a/uniffi_bindgen/src/bindings/python/templates/CustomType.py b/uniffi_bindgen/src/bindings/python/templates/CustomType.py index 5be6155b84..f7e626c163 100644 --- a/uniffi_bindgen/src/bindings/python/templates/CustomType.py +++ b/uniffi_bindgen/src/bindings/python/templates/CustomType.py @@ -17,6 +17,10 @@ def read(buf): def lift(value): return {{ builtin|ffi_converter_name }}.lift(value) + @staticmethod + def check(value): + return {{ builtin|ffi_converter_name }}.check(value) + @staticmethod def lower(value): return {{ builtin|ffi_converter_name }}.lower(value) @@ -51,6 +55,11 @@ def lift(value): builtin_value = {{ builtin|lift_fn }}(value) return {{ config.into_custom.render("builtin_value") }} + @staticmethod + def check(value): + builtin_value = {{ config.from_custom.render("value") }} + return {{ builtin|check_fn }}(builtin_value) + @staticmethod def lower(value): builtin_value = {{ config.from_custom.render("value") }} diff --git a/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py b/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py index 10974e009d..3bb06e1099 100644 --- a/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py +++ b/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py @@ -12,10 +12,14 @@ def read(buf): return datetime.timedelta(seconds=seconds, microseconds=microseconds) @staticmethod - def write(value, buf): + def check(value): seconds = value.seconds + value.days * 24 * 3600 - nanoseconds = value.microseconds * 1000 if seconds < 0: raise ValueError("Invalid duration, must be non-negative") + + @staticmethod + def write(value, buf): + seconds = value.seconds + value.days * 24 * 3600 + nanoseconds = value.microseconds * 1000 buf.write_i64(seconds) buf.write_u32(nanoseconds) diff --git a/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py b/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py index 84d089baf9..2ae8072f53 100644 --- a/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py @@ -7,20 +7,25 @@ {% if e.is_flat() %} class {{ type_name }}(enum.Enum): - {% for variant in e.variants() -%} + {%- call py::docstring(e, 4) %} + {%- for variant in e.variants() %} {{ variant.name()|enum_variant_py }} = {{ loop.index }} + {%- call py::docstring(variant, 4) %} {% endfor %} {% else %} class {{ type_name }}: + {%- call py::docstring(e, 4) %} def __init__(self): raise RuntimeError("{{ type_name }} cannot be instantiated directly") # Each enum variant is a nested class of the enum itself. {% for variant in e.variants() -%} class {{ variant.name()|enum_variant_py }}: - {% for field in variant.fields() %} - {{- field.name()|var_name }}: "{{- field|type_name }}"; + {%- call py::docstring(variant, 8) %} + + {%- for field in variant.fields() %} + {{ field.name()|var_name }}: "{{ field|type_name }}"; {%- endfor %} @typing.no_type_check @@ -81,6 +86,25 @@ def read(buf): {%- endfor %} raise InternalError("Raw enum value doesn't match any cases") + @staticmethod + def check(value): + {%- if e.variants().is_empty() %} + pass + {%- else %} + {%- for variant in e.variants() %} + {%- if e.is_flat() %} + if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}: + {%- else %} + if value.is_{{ variant.name()|var_name }}(): + {%- endif %} + {%- for field in variant.fields() %} + {{ field|check_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + return + {%- endfor %} + {%- endif %} + + @staticmethod def write(value, buf): {%- for variant in e.variants() %} {%- if e.is_flat() %} diff --git a/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py b/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py index 26a1e6452a..a5ed4df412 100644 --- a/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py @@ -5,6 +5,7 @@ # __dict__. All of this happens in dummy class to avoid polluting the module # namespace. class {{ type_name }}(Exception): + {%- call py::docstring(e, 4) %} pass _UniffiTemp{{ type_name }} = {{ type_name }} @@ -14,10 +15,14 @@ class {{ type_name }}: # type: ignore {%- let variant_type_name = variant.name()|class_name -%} {%- if e.is_flat() %} class {{ variant_type_name }}(_UniffiTemp{{ type_name }}): + {%- call py::docstring(variant, 8) %} + def __repr__(self): return "{{ type_name }}.{{ variant_type_name }}({})".format(repr(str(self))) {%- else %} class {{ variant_type_name }}(_UniffiTemp{{ type_name }}): + {%- call py::docstring(variant, 8) %} + def __init__(self{% for field in variant.fields() %}, {{ field.name()|var_name }}{% endfor %}): {%- if variant.has_fields() %} super().__init__(", ".join([ @@ -59,6 +64,20 @@ def read(buf): {%- endfor %} raise InternalError("Raw enum value doesn't match any cases") + @staticmethod + def check(value): + {%- if e.variants().is_empty() %} + pass + {%- else %} + {%- for variant in e.variants() %} + if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}): + {%- for field in variant.fields() %} + {{ field|check_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + return + {%- endfor %} + {%- endif %} + @staticmethod def write(value, buf): {%- for variant in e.variants() %} diff --git a/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py b/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py index a52107a638..49a1a7286e 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py +++ b/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py @@ -4,5 +4,5 @@ def read(buf): return buf.read_float() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_float(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py b/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py index 772f5080e9..e2084c7b13 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py +++ b/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py @@ -4,5 +4,5 @@ def read(buf): return buf.read_double() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_double(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py b/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py index 6a6932fed0..d1e2b0a448 100644 --- a/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py @@ -9,33 +9,36 @@ _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2 class {{ ffi_converter_name }}: - _pointer_manager = _UniffiPointerManager() + _handle_map = UniffiHandleMap() @classmethod - def lower(cls, eventloop): + def check(cls, eventloop): if not isinstance(eventloop, asyncio.BaseEventLoop): raise TypeError("_uniffi_executor_callback: Expected EventLoop instance") - return cls._pointer_manager.new_pointer(eventloop) + + @classmethod + def lower(cls, eventloop): + return cls._handle_map.new_handle(eventloop) @classmethod def write(cls, eventloop, buf): - buf.write_c_size_t(cls.lower(eventloop)) + buf.write_u64(cls.lower(eventloop)) @classmethod def read(cls, buf): - return cls.lift(buf.read_c_size_t()) + return cls.lift(buf.read_u64()) @classmethod def lift(cls, value): - return cls._pointer_manager.lookup(value) + return cls._handle_map.get(value) @_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T -def _uniffi_executor_callback(eventloop_address, delay, task_ptr, task_data): +def _uniffi_executor_callback(handle, delay, task_ptr, task_data): if task_ptr is None: - {{ ffi_converter_name }}._pointer_manager.release_pointer(eventloop_address) + {{ ffi_converter_name }}._handle_map.consume_handle(handle) return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS else: - eventloop = {{ ffi_converter_name }}._pointer_manager.lookup(eventloop_address) + eventloop = {{ ffi_converter_name }}._handle_map.get(handle) if eventloop.is_closed(): return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED diff --git a/uniffi_bindgen/src/bindings/python/templates/HandleMap.py b/uniffi_bindgen/src/bindings/python/templates/HandleMap.py new file mode 100644 index 0000000000..b2b51e325f --- /dev/null +++ b/uniffi_bindgen/src/bindings/python/templates/HandleMap.py @@ -0,0 +1,67 @@ +UniffiHandle = typing.NewType('UniffiHandle', int) + +# TODO: it would be nice to make this a generic class, however let's wait until python 3.11 is the +# minimum version, so we can do that without having to add a `TypeVar` to the top-level namespace. +class UniffiHandleMap: + """ + Manage handles for objects that are passed across the FFI + + See the `uniffi_core::HandleAlloc` trait for the semantics of each method + """ + + # Generates ids that are likely to be unique for each map + map_id_counter = itertools.count({{ ci.namespace_hash() }}) + + def __init__(self): + self._lock = threading.Lock() + self._map = {} + # Map ID, shifted into the top 16 bits + self._map_id = (next(UniffiHandleMap.map_id_counter) & 0xFFFF) << 48 + # Note: Foreign handles are always odd + self._key_counter = 1 + + def _next_key(self) -> int: + key = self._key_counter + self._key_counter = (self._key_counter + 2) & 0xFFFF_FFFF_FFFF + return key + + def _make_handle(self, key: int) -> int: + return key | self._map_id + + def _key(self, handle: int) -> int: + if (handle & 0xFFFF_0000_0000_0000) != self._map_id: + raise InternalError("Handle map ID mismatch") + return handle & 0xFFFF_FFFF_FFFF + + def new_handle(self, obj: object) -> int: + with self._lock: + key = self._next_key() + self._map[key] = obj + return self._make_handle(key) + + def clone_handle(self, handle: int) -> int: + try: + with self._lock: + obj = self._map[self._key(handle)] + key = self._next_key() + self._map[key] = obj + return self._make_handle(key) + except KeyError: + raise InternalError("handlemap key error: was the handle used after being freed?") + + def get(self, handle: int) -> object: + try: + with self._lock: + return self._map[self._key(handle)] + except KeyError: + raise InternalError("handlemap key error: was the handle used after being freed?") + + def consume_handle(self, handle: int) -> object: + try: + with self._lock: + return self._map.pop(self._key(handle)) + except KeyError: + raise InternalError("handlemap key error: was the handle used after being freed?") + +def uniffi_handle_is_from_rust(handle: int) -> bool: + return (handle & 1) == 0 diff --git a/uniffi_bindgen/src/bindings/python/templates/Helpers.py b/uniffi_bindgen/src/bindings/python/templates/Helpers.py index dca962f176..0569528377 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Helpers.py +++ b/uniffi_bindgen/src/bindings/python/templates/Helpers.py @@ -71,5 +71,5 @@ def _uniffi_check_call_status(error_ffi_converter, call_status): _UNIFFI_FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.POINTER(_UniffiRustBuffer)) # UniFFI future continuation -_UNIFFI_FUTURE_CONTINUATION_T = ctypes.CFUNCTYPE(None, ctypes.c_size_t, ctypes.c_int8) +_UNIFFI_FUTURE_CONTINUATION_T = ctypes.CFUNCTYPE(None, ctypes.c_uint64, ctypes.c_int8) diff --git a/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py b/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py index 99f19dc1c0..befa563384 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py +++ b/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py @@ -8,5 +8,5 @@ def read(buf): return buf.read_i16() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i16(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py b/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py index 3b142c8749..70644f6717 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py +++ b/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py @@ -8,5 +8,5 @@ def read(buf): return buf.read_i32() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i32(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py b/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py index 6e94379cbf..232f127bd6 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py +++ b/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py @@ -8,5 +8,5 @@ def read(buf): return buf.read_i64() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i64(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py b/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py index 732530e3cb..c1de1625e7 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py +++ b/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py @@ -8,5 +8,5 @@ def read(buf): return buf.read_i8() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i8(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py b/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py index 387227ed09..6f413bb7e6 100644 --- a/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py @@ -2,6 +2,12 @@ {%- let value_ffi_converter = value_type|ffi_converter_name %} class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): + @classmethod + def check(cls, items): + for (key, value) in items.items(): + {{ key_ffi_converter }}.check(key) + {{ value_ffi_converter }}.check(value) + @classmethod def write(cls, items, buf): buf.write_i32(len(items)) diff --git a/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py index fac6cd5564..bfcc28be81 100644 --- a/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py @@ -14,7 +14,7 @@ However, when task is NULL this indicates that Rust has dropped the ForeignExecutor and we should decrease the EventLoop refcount. """ -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int8, ctypes.c_size_t, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p) +_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int8, ctypes.c_uint64, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p) """ Function pointer for a Rust task, which a callback function that takes a opaque pointer diff --git a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py index 097fcd4d1d..b9897bcf7e 100644 --- a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py @@ -1,43 +1,49 @@ {%- let obj = ci|get_object_definition(name) %} {%- let (protocol_name, impl_name) = obj|object_names %} {%- let methods = obj.methods() %} +{%- let protocol_docstring = obj.docstring() %} {% include "Protocol.py" %} class {{ impl_name }}: - _pointer: ctypes.c_void_p + {%- call py::docstring(obj, 4) %} + _uniffi_handle: ctypes.c_int64 {%- match obj.primary_constructor() %} {%- when Some with (cons) %} def __init__(self, {% call py::arg_list_decl(cons) -%}): + {%- call py::docstring(cons, 8) %} {%- call py::setup_args_extra_indent(cons) %} - self._pointer = {% call py::to_ffi_call(cons) %} + self._uniffi_handle = {% call py::to_ffi_call(cons) %} {%- when None %} {%- endmatch %} def __del__(self): # In case of partial initialization of instances. - pointer = getattr(self, "_pointer", None) - if pointer is not None: - _rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, pointer) + handle = getattr(self, "_uniffi_handle", None) + if handle is not None: + _rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, handle) + + def uniffi_clone_handle(self): + return _rust_call(_UniffiLib.{{ obj.ffi_object_clone().name() }}, self._uniffi_handle) # Used by alternative constructors or any methods which return this type. @classmethod - def _make_instance_(cls, pointer): + def _make_instance_(cls, handle): # Lightly yucky way to bypass the usual __init__ logic - # and just create a new instance with the required pointer. + # and just create a new instance with the required handle. inst = cls.__new__(cls) - inst._pointer = pointer + inst._uniffi_handle = handle return inst {%- for cons in obj.alternate_constructors() %} @classmethod def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): + {%- call py::docstring(cons, 8) %} {%- call py::setup_args_extra_indent(cons) %} - # Call the (fallible) function before creating any half-baked object instances. - pointer = {% call py::to_ffi_call(cons) %} - return cls._make_instance_(pointer) + uniffi_handle = {% call py::to_ffi_call(cons) %} + return cls._make_instance_(uniffi_handle) {% endfor %} {%- for meth in obj.methods() -%} @@ -55,13 +61,13 @@ def __eq__(self, other: object) -> {{ eq.return_type().unwrap()|type_name }}: if not isinstance(other, {{ type_name }}): return NotImplemented - return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", eq) %}) + return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self.uniffi_clone_handle()", eq) %}) def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}: if not isinstance(other, {{ type_name }}): return NotImplemented - return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", ne) %}) + return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self.uniffi_clone_handle()", ne) %}) {%- when UniffiTrait::Hash { hash } %} {%- call py::method_decl("__hash__", hash) %} {% endmatch %} @@ -76,31 +82,48 @@ def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}: class {{ ffi_converter_name }}: {%- if obj.is_trait_interface() %} - _handle_map = ConcurrentHandleMap() + _handle_map = UniffiHandleMap() {%- endif %} @staticmethod - def lift(value: int): + def lift(value: UniffiHandle): + {%- if obj.is_trait_interface() %} + if uniffi_handle_is_from_rust(value): + return {{ impl_name }}._make_instance_(value) + else: + return {{ ffi_converter_name }}._handle_map.consume_handle(value) + {%- else %} return {{ impl_name }}._make_instance_(value) + {%- endif %} @staticmethod - def lower(value: {{ protocol_name }}): - {%- match obj.imp() %} - {%- when ObjectImpl::Struct %} + def check(value: {{ type_name }}): + {%- if obj.is_trait_interface() %} + pass + {%- else %} if not isinstance(value, {{ impl_name }}): raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__)) - return value._pointer - {%- when ObjectImpl::Trait %} - return {{ ffi_converter_name }}._handle_map.insert(value) - {%- endmatch %} + {%- endif %} + + @staticmethod + def lower(value: {{ type_name }}): + {%- if obj.is_trait_interface() %} + uniffi_clone_handle = getattr(value, 'uniffi_clone_handle', None) + if uniffi_clone_handle is not None: + # If we're wrapping a trait implemented in Rust, return that handle directly rather than + # wrapping it again in Python. + return uniffi_clone_handle() + else: + return {{ ffi_converter_name }}._handle_map.new_handle(value) + {%- else %} + return value.uniffi_clone_handle() + {%- endif %} @classmethod def read(cls, buf: _UniffiRustBuffer): ptr = buf.read_u64() - if ptr == 0: - raise InternalError("Raw pointer value was null") return cls.lift(ptr) @classmethod - def write(cls, value: {{ protocol_name }}, buf: _UniffiRustBuffer): + def write(cls, value: {{ type_name }}, buf: _UniffiRustBuffer): buf.write_u64(cls.lower(value)) diff --git a/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py b/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py index e406c51d49..14b7a0f3af 100644 --- a/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py @@ -1,6 +1,11 @@ {%- let inner_ffi_converter = inner_type|ffi_converter_name %} class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): + @classmethod + def check(cls, value): + if value is not None: + {{ inner_ffi_converter }}.check(value) + @classmethod def write(cls, value, buf): if value is None: diff --git a/uniffi_bindgen/src/bindings/python/templates/PointerManager.py b/uniffi_bindgen/src/bindings/python/templates/PointerManager.py deleted file mode 100644 index 23aa28eab4..0000000000 --- a/uniffi_bindgen/src/bindings/python/templates/PointerManager.py +++ /dev/null @@ -1,68 +0,0 @@ -class _UniffiPointerManagerCPython: - """ - Manage giving out pointers to Python objects on CPython - - This class is used to generate opaque pointers that reference Python objects to pass to Rust. - It assumes a CPython platform. See _UniffiPointerManagerGeneral for the alternative. - """ - - def new_pointer(self, obj): - """ - Get a pointer for an object as a ctypes.c_size_t instance - - Each call to new_pointer() must be balanced with exactly one call to release_pointer() - - This returns a ctypes.c_size_t. This is always the same size as a pointer and can be - interchanged with pointers for FFI function arguments and return values. - """ - # IncRef the object since we're going to pass a pointer to Rust - ctypes.pythonapi.Py_IncRef(ctypes.py_object(obj)) - # id() is the object address on CPython - # (https://docs.python.org/3/library/functions.html#id) - return id(obj) - - def release_pointer(self, address): - py_obj = ctypes.cast(address, ctypes.py_object) - obj = py_obj.value - ctypes.pythonapi.Py_DecRef(py_obj) - return obj - - def lookup(self, address): - return ctypes.cast(address, ctypes.py_object).value - -class _UniffiPointerManagerGeneral: - """ - Manage giving out pointers to Python objects on non-CPython platforms - - This has the same API as _UniffiPointerManagerCPython, but doesn't assume we're running on - CPython and is slightly slower. - - Instead of using real pointers, it maps integer values to objects and returns the keys as - c_size_t values. - """ - - def __init__(self): - self._map = {} - self._lock = threading.Lock() - self._current_handle = 0 - - def new_pointer(self, obj): - with self._lock: - handle = self._current_handle - self._current_handle += 1 - self._map[handle] = obj - return handle - - def release_pointer(self, handle): - with self._lock: - return self._map.pop(handle) - - def lookup(self, handle): - with self._lock: - return self._map[handle] - -# Pick an pointer manager implementation based on the platform -if platform.python_implementation() == 'CPython': - _UniffiPointerManager = _UniffiPointerManagerCPython # type: ignore -else: - _UniffiPointerManager = _UniffiPointerManagerGeneral # type: ignore diff --git a/uniffi_bindgen/src/bindings/python/templates/Protocol.py b/uniffi_bindgen/src/bindings/python/templates/Protocol.py index 63e22769fb..3b7e93596a 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Protocol.py +++ b/uniffi_bindgen/src/bindings/python/templates/Protocol.py @@ -1,6 +1,8 @@ class {{ protocol_name }}(typing.Protocol): + {%- call py::docstring_value(protocol_docstring, 4) %} {%- for meth in methods.iter() %} def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}): + {%- call py::docstring(meth, 8) %} raise NotImplementedError {%- else %} pass diff --git a/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py b/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py index fdffe390c1..a2d843ad5d 100644 --- a/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py @@ -1,7 +1,9 @@ {%- let rec = ci|get_record_definition(name) %} class {{ type_name }}: - {% for field in rec.fields() %} - {{- field.name()|var_name }}: "{{- field|type_name }}"; + {%- call py::docstring(rec, 4) %} + {%- for field in rec.fields() %} + {{ field.name()|var_name }}: "{{ field|type_name }}" + {%- call py::docstring(field, 4) %} {%- endfor %} {%- if rec.has_fields() %} @@ -44,6 +46,16 @@ def read(buf): {%- endfor %} ) + @staticmethod + def check(value): + {%- if rec.fields().is_empty() %} + pass + {%- else %} + {%- for field in rec.fields() %} + {{ field|check_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + {%- endif %} + @staticmethod def write(value, buf): {%- if rec.has_fields() %} diff --git a/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py b/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py index daabd5b4b9..eb09a1e9d5 100644 --- a/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py +++ b/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py @@ -1,25 +1,13 @@ # Types conforming to `_UniffiConverterPrimitive` pass themselves directly over the FFI. class _UniffiConverterPrimitive: - @classmethod - def check(cls, value): - return value - @classmethod def lift(cls, value): return value - + @classmethod def lower(cls, value): - return cls.lowerUnchecked(cls.check(value)) - - @classmethod - def lowerUnchecked(cls, value): return value - @classmethod - def write(cls, value, buf): - cls.write_unchecked(cls.check(value), buf) - class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive): @classmethod def check(cls, value): @@ -31,7 +19,6 @@ def check(cls, value): raise TypeError("__index__ returned non-int (type {})".format(type(value).__name__)) if not cls.VALUE_MIN <= value < cls.VALUE_MAX: raise ValueError("{} requires {} <= value < {}".format(cls.CLASS_NAME, cls.VALUE_MIN, cls.VALUE_MAX)) - return super().check(value) class _UniffiConverterPrimitiveFloat(_UniffiConverterPrimitive): @classmethod @@ -42,18 +29,23 @@ def check(cls, value): raise TypeError("must be real number, not {}".format(type(value).__name__)) if not isinstance(value, float): raise TypeError("__float__ returned non-float (type {})".format(type(value).__name__)) - return super().check(value) # Helper class for wrapper types that will always go through a _UniffiRustBuffer. # Classes should inherit from this and implement the `read` and `write` static methods. class _UniffiConverterRustBuffer: @classmethod def lift(cls, rbuf): - with rbuf.consume_with_stream() as stream: - return cls.read(stream) + return uniffi_lift_from_rust_buffer(cls, rbuf) @classmethod def lower(cls, value): - with _UniffiRustBuffer.alloc_with_builder() as builder: - cls.write(value, builder) - return builder.finalize() + return uniffi_lower_into_rust_buffer(cls, value) + +def uniffi_lift_from_rust_buffer(ffi_converter, rbuf): + with rbuf.consume_with_stream() as stream: + return ffi_converter.read(stream) + +def uniffi_lower_into_rust_buffer(ffi_converter, value): + with _UniffiRustBuffer.alloc_with_builder() as builder: + ffi_converter.write(value, builder) + return builder.finalize() diff --git a/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py b/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py index c317a632fc..35efeb2ab9 100644 --- a/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py @@ -137,9 +137,6 @@ def read_float(self): def read_double(self): return self._unpack_from(8, ">d") - def read_c_size_t(self): - return self._unpack_from(ctypes.sizeof(ctypes.c_size_t) , "@N") - class _UniffiRustBufferBuilder: """ Helper for structured writing of bytes into a _UniffiRustBuffer. @@ -206,6 +203,3 @@ def write_float(self, v): def write_double(self, v): self._pack_into(8, ">d", v) - - def write_c_size_t(self, v): - self._pack_into(ctypes.sizeof(ctypes.c_size_t) , "@N", v) diff --git a/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py b/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py index 3c9f5a4596..c69a36f178 100644 --- a/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py @@ -1,6 +1,11 @@ {%- let inner_ffi_converter = inner_type|ffi_converter_name %} class {{ ffi_converter_name}}(_UniffiConverterRustBuffer): + @classmethod + def check(cls, value): + for item in value: + {{ inner_ffi_converter }}.check(item) + @classmethod def write(cls, value, buf): items = len(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/StringHelper.py b/uniffi_bindgen/src/bindings/python/templates/StringHelper.py index 40890b6abc..80a014c888 100644 --- a/uniffi_bindgen/src/bindings/python/templates/StringHelper.py +++ b/uniffi_bindgen/src/bindings/python/templates/StringHelper.py @@ -15,7 +15,6 @@ def read(buf): @staticmethod def write(value, buf): - value = _UniffiConverterString.check(value) utf8_bytes = value.encode("utf-8") buf.write_i32(len(utf8_bytes)) buf.write(utf8_bytes) @@ -27,7 +26,6 @@ def lift(buf): @staticmethod def lower(value): - value = _UniffiConverterString.check(value) with _UniffiRustBuffer.alloc_with_builder() as builder: builder.write(value.encode("utf-8")) return builder.finalize() diff --git a/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py b/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py index 8402f6095d..9acbb2fee9 100644 --- a/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py +++ b/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py @@ -17,6 +17,10 @@ def read(buf): else: return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) - datetime.timedelta(seconds=-seconds, microseconds=microseconds) + @staticmethod + def check(value): + pass + @staticmethod def write(value, buf): if value >= datetime.datetime.fromtimestamp(0, datetime.timezone.utc): diff --git a/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py b/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py index f258b60a1c..45e5ac7b5d 100644 --- a/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py @@ -1,6 +1,8 @@ {%- if func.is_async() %} def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): + {%- call py::docstring(func, 4) %} + {%- call py::setup_args(func) %} return _uniffi_rust_call_async( _UniffiLib.{{ func.ffi_func().name() }}({% call py::arg_list_lowered(func) %}), _UniffiLib.{{func.ffi_rust_future_poll(ci) }}, @@ -27,11 +29,13 @@ def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): {%- when Some with (return_type) %} def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}": + {%- call py::docstring(func, 4) %} {%- call py::setup_args(func) %} return {{ return_type|lift_fn }}({% call py::to_ffi_call(func) %}) {% when None %} def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): + {%- call py::docstring(func, 4) %} {%- call py::setup_args(func) %} {% call py::to_ffi_call(func) %} {% endmatch %} diff --git a/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py b/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py index 081c6731ce..039bf76162 100644 --- a/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py +++ b/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py @@ -8,5 +8,5 @@ def read(buf): return buf.read_u16() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u16(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py b/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py index b80e75177d..1650bf9b60 100644 --- a/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py +++ b/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py @@ -8,5 +8,5 @@ def read(buf): return buf.read_u32() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u32(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py b/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py index 4b87e58547..f354545e26 100644 --- a/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py +++ b/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py @@ -8,5 +8,5 @@ def read(buf): return buf.read_u64() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u64(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py b/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py index 33026706f2..cee130b4d9 100644 --- a/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py +++ b/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py @@ -8,5 +8,5 @@ def read(buf): return buf.read_u8() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u8(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/macros.py b/uniffi_bindgen/src/bindings/python/templates/macros.py index ef3b1bb94d..009f45395a 100644 --- a/uniffi_bindgen/src/bindings/python/templates/macros.py +++ b/uniffi_bindgen/src/bindings/python/templates/macros.py @@ -37,6 +37,19 @@ {%- endfor %} {%- endmacro -%} +{%- macro docstring_value(maybe_docstring, indent_spaces) %} +{%- match maybe_docstring %} +{%- when Some(docstring) %} +{{ docstring|docstring(indent_spaces) }} +{{ "" }} +{%- else %} +{%- endmatch %} +{%- endmacro %} + +{%- macro docstring(defn, indent_spaces) %} +{%- call docstring_value(defn.docstring(), indent_spaces) %} +{%- endmacro %} + {#- // Arglist as used in Python declarations of methods, functions and constructors. // Note the var_name and type_name filters. @@ -70,6 +83,7 @@ #} {%- macro setup_args(func) %} {%- for arg in func.arguments() %} + {{ arg|check_fn }}({{ arg.name()|var_name }}) {%- match arg.default_value() %} {%- when None %} {%- when Some with(literal) %} @@ -85,6 +99,7 @@ #} {%- macro setup_args_extra_indent(func) %} {%- for arg in func.arguments() %} + {{ arg|check_fn }}({{ arg.name()|var_name }}) {%- match arg.default_value() %} {%- when None %} {%- when Some with(literal) %} @@ -101,10 +116,11 @@ {% if meth.is_async() %} def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): + {%- call docstring(meth, 8) %} {%- call setup_args_extra_indent(meth) %} return _uniffi_rust_call_async( _UniffiLib.{{ meth.ffi_func().name() }}( - self._pointer, {% call arg_list_lowered(meth) %} + self.uniffi_clone_handle(), {% call arg_list_lowered(meth) %} ), _UniffiLib.{{ meth.ffi_rust_future_poll(ci) }}, _UniffiLib.{{ meth.ffi_rust_future_complete(ci) }}, @@ -131,16 +147,18 @@ def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): {%- when Some with (return_type) %} def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}": + {%- call docstring(meth, 8) %} {%- call setup_args_extra_indent(meth) %} return {{ return_type|lift_fn }}( - {% call to_ffi_call_with_prefix("self._pointer", meth) %} + {% call to_ffi_call_with_prefix("self.uniffi_clone_handle()", meth) %} ) {%- when None %} def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): + {%- call docstring(meth, 8) %} {%- call setup_args_extra_indent(meth) %} - {% call to_ffi_call_with_prefix("self._pointer", meth) %} + {% call to_ffi_call_with_prefix("self.uniffi_clone_handle()", meth) %} {% endmatch %} {% endif %} diff --git a/uniffi_bindgen/src/bindings/python/templates/wrapper.py b/uniffi_bindgen/src/bindings/python/templates/wrapper.py index 24c3290ff7..f9a95500de 100644 --- a/uniffi_bindgen/src/bindings/python/templates/wrapper.py +++ b/uniffi_bindgen/src/bindings/python/templates/wrapper.py @@ -1,3 +1,5 @@ +{%- call py::docstring_value(ci.namespace_docstring(), 0) %} + # This file was autogenerated by some hot garbage in the `uniffi` crate. # Trust me, you don't want to mess with it! @@ -17,9 +19,11 @@ import sys import ctypes import enum +import itertools import struct import contextlib import datetime +import threading import typing {%- if ci.has_async_fns() %} import asyncio @@ -33,8 +37,8 @@ _DEFAULT = object() {% include "RustBufferTemplate.py" %} +{% include "HandleMap.py" %} {% include "Helpers.py" %} -{% include "PointerManager.py" %} {% include "RustBufferHelper.py" %} # Contains loading, initialization code, and the FFI Function declarations. diff --git a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index e6defe65cc..37db4bc614 100644 --- a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -150,7 +150,7 @@ mod filters { FfiType::UInt64 => ":uint64".to_string(), FfiType::Float32 => ":float".to_string(), FfiType::Float64 => ":double".to_string(), - FfiType::RustArcPtr(_) => ":pointer".to_string(), + FfiType::Handle => ":uint64".to_string(), FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(), FfiType::ForeignBytes => "ForeignBytes".to_string(), // Callback interfaces are not yet implemented, but this needs to return something in @@ -159,12 +159,7 @@ mod filters { FfiType::ForeignExecutorCallback => { unimplemented!("Foreign executors are not implemented") } - FfiType::ForeignExecutorHandle => { - unimplemented!("Foreign executors are not implemented") - } - FfiType::RustFutureHandle - | FfiType::RustFutureContinuationCallback - | FfiType::RustFutureContinuationData => { + FfiType::RustFutureContinuationCallback => { unimplemented!("Async functions are not implemented") } }) @@ -270,6 +265,22 @@ mod filters { }) } + pub fn check_rb(nm: &str, type_: &Type) -> Result { + Ok(match type_ { + Type::Object { name, .. } => format!("({}._uniffi_check {nm})", class_name_rb(name)?), + Type::Enum { .. } + | Type::Record { .. } + | Type::Optional { .. } + | Type::Sequence { .. } + | Type::Map { .. } => format!( + "RustBuffer.check_{}({})", + class_name_rb(&canonical_name(type_))?, + nm + ), + _ => "".to_owned(), + }) + } + pub fn lower_rb(nm: &str, type_: &Type) -> Result { Ok(match type_ { Type::Int8 diff --git a/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb b/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb index 677c5c729b..efeea1362d 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb @@ -1,54 +1,64 @@ class {{ obj.name()|class_name_rb }} - # A private helper for initializing instances of the class from a raw pointer, + # A private helper for initializing instances of the class from a handle, # bypassing any initialization logic and ensuring they are GC'd properly. - def self._uniffi_allocate(pointer) - pointer.autorelease = false + def self._uniffi_allocate(handle) inst = allocate - inst.instance_variable_set :@pointer, pointer - ObjectSpace.define_finalizer(inst, _uniffi_define_finalizer_by_pointer(pointer, inst.object_id)) + inst.instance_variable_set :@handle, handle + ObjectSpace.define_finalizer(inst, _uniffi_define_finalizer_by_handle(handle, inst.object_id)) return inst end # A private helper for registering an object finalizer. # N.B. it's important that this does not capture a reference - # to the actual instance, only its underlying pointer. - def self._uniffi_define_finalizer_by_pointer(pointer, object_id) + # to the actual instance, only its underlying handle. + def self._uniffi_define_finalizer_by_handle(handle, object_id) Proc.new do |_id| {{ ci.namespace()|class_name_rb }}.rust_call( :{{ obj.ffi_object_free().name() }}, - pointer + handle ) end end - # A private helper for lowering instances into a raw pointer. + # Private helpers for lowering instances into a handle. + # This does an explicit typecheck, because accidentally lowering a different type of # object in a place where this type is expected, could lead to memory unsafety. - def self._uniffi_lower(inst) + def self._uniffi_check(inst) if not inst.is_a? self raise TypeError.new "Expected a {{ obj.name()|class_name_rb }} instance, got #{inst}" end - return inst.instance_variable_get :@pointer + end + + def self._uniffi_lower(inst) + return inst._uniffi_clone_handle() + end + + def _uniffi_clone_handle() + return {{ ci.namespace()|class_name_rb }}.rust_call( + :{{ obj.ffi_object_clone().name() }}, + @handle + ) end {%- match obj.primary_constructor() %} {%- when Some with (cons) %} def initialize({% call rb::arg_list_decl(cons) -%}) - {%- call rb::coerce_args_extra_indent(cons) %} - pointer = {% call rb::to_ffi_call(cons) %} - @pointer = pointer - ObjectSpace.define_finalizer(self, self.class._uniffi_define_finalizer_by_pointer(pointer, self.object_id)) + {%- call rb::setup_args_extra_indent(cons) %} + handle = {% call rb::to_ffi_call(cons) %} + @handle = handle + ObjectSpace.define_finalizer(self, self.class._uniffi_define_finalizer_by_handle(handle, self.object_id)) end {%- when None %} {%- endmatch %} {% for cons in obj.alternate_constructors() -%} def self.{{ cons.name()|fn_name_rb }}({% call rb::arg_list_decl(cons) %}) - {%- call rb::coerce_args_extra_indent(cons) %} + {%- call rb::setup_args_extra_indent(cons) %} # Call the (fallible) function before creating any half-baked object instances. # Lightly yucky way to bypass the usual "initialize" logic - # and just create a new instance with the required pointer. + # and just create a new instance with the required handle. return _uniffi_allocate({% call rb::to_ffi_call(cons) %}) end {% endfor %} @@ -58,15 +68,15 @@ def self.{{ cons.name()|fn_name_rb }}({% call rb::arg_list_decl(cons) %}) {%- when Some with (return_type) -%} def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %}) - {%- call rb::coerce_args_extra_indent(meth) %} - result = {% call rb::to_ffi_call_with_prefix("@pointer", meth) %} + {%- call rb::setup_args_extra_indent(meth) %} + result = {% call rb::to_ffi_call_with_prefix("_uniffi_clone_handle()", meth) %} return {{ "result"|lift_rb(return_type) }} end {%- when None -%} def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %}) - {%- call rb::coerce_args_extra_indent(meth) %} - {% call rb::to_ffi_call_with_prefix("@pointer", meth) %} + {%- call rb::setup_args_extra_indent(meth) %} + {% call rb::to_ffi_call_with_prefix("_uniffi_clone_handle()", meth) %} end {% endmatch %} {% endfor %} diff --git a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb index 8749139116..24f5d83ff6 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb @@ -163,8 +163,8 @@ def write_{{ canonical_type_name }}(v) # The Object type {{ object_name }}. def write_{{ canonical_type_name }}(obj) - pointer = {{ object_name|class_name_rb}}._uniffi_lower obj - pack_into(8, 'Q>', pointer.address) + handle = {{ object_name|class_name_rb}}._uniffi_lower obj + pack_into(8, 'Q>', handle) end {% when Type::Enum { name: enum_name, module_path } -%} diff --git a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb index b085dddf15..ed593672d7 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb @@ -154,8 +154,8 @@ def read{{ canonical_type_name }} # The Object type {{ object_name }}. def read{{ canonical_type_name }} - pointer = FFI::Pointer.new unpack_from 8, 'Q>' - return {{ object_name|class_name_rb }}._uniffi_allocate(pointer) + handle = unpack_from 8, 'Q>' + return {{ object_name|class_name_rb }}._uniffi_allocate(handle) end {% when Type::Enum { name, module_path } -%} diff --git a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb index 0194c9666d..154867e7ee 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb @@ -128,6 +128,12 @@ def consumeInto{{ canonical_type_name }} {%- let rec = ci|get_record_definition(record_name) -%} # The Record type {{ record_name }}. + def self.check_{{ canonical_type_name }}(v) + {%- for field in rec.fields() %} + {{ "v.{}"|format(field.name()|var_name_rb)|check_rb(field.as_type().borrow()) }} + {%- endfor %} + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) @@ -146,6 +152,19 @@ def consumeInto{{ canonical_type_name }} {%- let e = ci|get_enum_definition(enum_name) -%} # The Enum type {{ enum_name }}. + def self.check_{{ canonical_type_name }}(v) + {%- if !e.is_flat() %} + {%- for variant in e.variants() %} + if v.{{ variant.name()|var_name_rb }}? + {%- for field in variant.fields() %} + {{ "v.{}"|format(field.name())|check_rb(field.as_type().borrow()) }} + {%- endfor %} + return + end + {%- endfor %} + {%- endif %} + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) @@ -162,6 +181,12 @@ def consumeInto{{ canonical_type_name }} {% when Type::Optional { inner_type } -%} # The Optional type for {{ canonical_name(inner_type) }}. + + def self.check_{{ canonical_type_name }}(v) + if not v.nil? + {{ "v"|check_rb(inner_type.borrow()) }} + end + end def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| @@ -179,6 +204,12 @@ def consumeInto{{ canonical_type_name }} {% when Type::Sequence { inner_type } -%} # The Sequence type for {{ canonical_name(inner_type) }}. + def self.check_{{ canonical_type_name }}(v) + v.each do |item| + {{ "item"|check_rb(inner_type.borrow()) }} + end + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) @@ -195,6 +226,13 @@ def consumeInto{{ canonical_type_name }} {% when Type::Map { key_type: k, value_type: inner_type } -%} # The Map type for {{ canonical_name(inner_type) }}. + def self.check_{{ canonical_type_name }}(v) + v.each do |k, v| + {{ "k"|check_rb(k.borrow()) }} + {{ "v"|check_rb(inner_type.borrow()) }} + end + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) diff --git a/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb b/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb index 13214cf31b..b6dce0effa 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb @@ -2,7 +2,7 @@ {%- when Some with (return_type) %} def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%}) - {%- call rb::coerce_args(func) %} + {%- call rb::setup_args(func) %} result = {% call rb::to_ffi_call(func) %} return {{ "result"|lift_rb(return_type) }} end @@ -10,7 +10,7 @@ def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%}) {% when None %} def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%}) - {%- call rb::coerce_args(func) %} + {%- call rb::setup_args(func) %} {% call rb::to_ffi_call(func) %} end {% endmatch %} diff --git a/uniffi_bindgen/src/bindings/ruby/templates/macros.rb b/uniffi_bindgen/src/bindings/ruby/templates/macros.rb index 8dc3e5e613..e9962f5831 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/macros.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/macros.rb @@ -60,14 +60,16 @@ [{%- for arg in func.arguments() -%}{{ arg.type_().borrow()|type_ffi }}, {% endfor -%} RustCallStatus.by_ref] {%- endmacro -%} -{%- macro coerce_args(func) %} +{%- macro setup_args(func) %} {%- for arg in func.arguments() %} - {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) -}} + {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) }} + {{ arg.name()|check_rb(arg.as_type().borrow()) }} {% endfor -%} {%- endmacro -%} -{%- macro coerce_args_extra_indent(func) %} - {%- for arg in func.arguments() %} +{%- macro setup_args_extra_indent(meth) %} + {%- for arg in meth.arguments() %} {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) }} + {{ arg.name()|check_rb(arg.as_type().borrow()) }} {%- endfor %} {%- endmacro -%} diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs index b25e3fb6dc..dab89e0259 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::backend::CodeType; +use super::CodeType; #[derive(Debug)] pub struct CallbackInterfaceCodeType { diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs index f3f5b54e71..8e6dddf3f9 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal, Type}; +use super::CodeType; +use crate::backend::{Literal, Type}; #[derive(Debug)] pub struct OptionalCodeType { diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs index 77569c5485..f4591b6eb6 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::backend::CodeType; +use super::CodeType; #[derive(Debug)] pub struct CustomCodeType { diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs index f7a246a9d6..14377ed9de 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::backend::Literal; #[derive(Debug)] pub struct EnumCodeType { diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs index 73a373d8d9..b488b004cf 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::backend::CodeType; +use super::CodeType; #[derive(Debug)] pub struct ForeignExecutorCodeType; diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs index 4cc3ff2707..0b6728ba84 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::backend::CodeType; +use super::CodeType; #[derive(Debug)] pub struct ExternalCodeType { diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs index 9c92b2f162..c45091c80a 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::backend::CodeType; +use super::CodeType; #[derive(Debug)] pub struct TimestampCodeType; diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index 1ba385d361..868bdd8ac7 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -6,6 +6,7 @@ use once_cell::sync::Lazy; use std::borrow::Borrow; use std::cell::RefCell; use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt::Debug; use anyhow::{Context, Result}; use askama::Template; @@ -13,7 +14,7 @@ use heck::{ToLowerCamelCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use super::Bindings; -use crate::backend::{CodeType, TemplateExpression}; +use crate::backend::TemplateExpression; use crate::interface::*; use crate::BindingsConfig; @@ -28,6 +29,65 @@ mod object; mod primitives; mod record; +/// A trait tor the implementation. +trait CodeType: Debug { + /// The language specific label used to reference this type. This will be used in + /// method signatures and property declarations. + fn type_label(&self) -> String; + + /// A representation of this type label that can be used as part of another + /// identifier. e.g. `read_foo()`, or `FooInternals`. + /// + /// This is especially useful when creating specialized objects or methods to deal + /// with this type only. + fn canonical_name(&self) -> String { + self.type_label() + } + + fn literal(&self, _literal: &Literal) -> String { + unimplemented!("Unimplemented for {}", self.type_label()) + } + + /// Name of the FfiConverter + /// + /// This is the object that contains the lower, write, lift, and read methods for this type. + fn ffi_converter_name(&self) -> String { + format!("FfiConverter{}", self.canonical_name()) + } + + // XXX - the below should be removed and replace with the ffi_converter_name reference in the template. + /// An expression for lowering a value into something we can pass over the FFI. + fn lower(&self) -> String { + format!("{}.lower", self.ffi_converter_name()) + } + + /// An expression for writing a value into a byte buffer. + fn write(&self) -> String { + format!("{}.write", self.ffi_converter_name()) + } + + /// An expression for lifting a value from something we received over the FFI. + fn lift(&self) -> String { + format!("{}.lift", self.ffi_converter_name()) + } + + /// An expression for reading a value from a byte buffer. + fn read(&self) -> String { + format!("{}.read", self.ffi_converter_name()) + } + + /// A list of imports that are needed if this type is in use. + /// Classes are imported exactly once. + fn imports(&self) -> Option> { + None + } + + /// Function to run at startup + fn initialization_fn(&self) -> Option { + None + } +} + /// From static KEYWORDS: Lazy> = Lazy::new(|| { [ @@ -136,6 +196,7 @@ pub struct Config { ffi_module_filename: Option, generate_module_map: Option, omit_argument_labels: Option, + generate_immutable_records: Option, #[serde(default)] custom_types: HashMap, } @@ -201,6 +262,11 @@ impl Config { pub fn omit_argument_labels(&self) -> bool { self.omit_argument_labels.unwrap_or(false) } + + /// Whether to generate immutable records (`let` instead of `var`) + pub fn generate_immutable_records(&self) -> bool { + self.generate_immutable_records.unwrap_or(false) + } } impl BindingsConfig for Config { @@ -455,16 +521,17 @@ impl SwiftCodeOracle { FfiType::UInt64 => "UInt64".into(), FfiType::Float32 => "Float".into(), FfiType::Float64 => "Double".into(), - FfiType::RustArcPtr(_) => "UnsafeMutableRawPointer".into(), + // Other bindings use an type alias for `Handle`, but this isn't so easy with Swift: + // * When multiple crates are built together, all swift files get merged into a + // single module which can lead to namespace conflicts. + // * `fileprivate` type aliases can't be used in a public API, even if the actual + // type is public. + FfiType::Handle => "UInt64".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), FfiType::ForeignBytes => "ForeignBytes".into(), FfiType::ForeignCallback => "ForeignCallback".into(), - FfiType::ForeignExecutorHandle => "Int".into(), FfiType::ForeignExecutorCallback => "ForeignExecutorCallback".into(), FfiType::RustFutureContinuationCallback => "UniFfiRustFutureContinuation".into(), - FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { - "UnsafeMutableRawPointer".into() - } } } @@ -472,9 +539,7 @@ impl SwiftCodeOracle { match ffi_type { FfiType::ForeignCallback | FfiType::ForeignExecutorCallback - | FfiType::RustFutureHandle - | FfiType::RustFutureContinuationCallback - | FfiType::RustFutureContinuationData => { + | FfiType::RustFutureContinuationCallback => { format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type)) } _ => self.ffi_type_label_raw(ffi_type), @@ -571,18 +636,14 @@ pub mod filters { FfiType::UInt64 => "uint64_t".into(), FfiType::Float32 => "float".into(), FfiType::Float64 => "double".into(), - FfiType::RustArcPtr(_) => "void*_Nonnull".into(), + FfiType::Handle => "uint64_t".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), FfiType::ForeignBytes => "ForeignBytes".into(), FfiType::ForeignCallback => "ForeignCallback _Nonnull".into(), FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(), - FfiType::ForeignExecutorHandle => "size_t".into(), FfiType::RustFutureContinuationCallback => { "UniFfiRustFutureContinuation _Nonnull".into() } - FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { - "void* _Nonnull".into() - } }) } @@ -617,6 +678,15 @@ pub mod filters { Ok(oracle().enum_variant_name(nm)) } + /// Get the idiomatic Swift rendering of docstring + pub fn docstring(docstring: &str, spaces: &i32) -> Result { + let middle = textwrap::indent(&textwrap::dedent(docstring), " * "); + let wrapped = format!("/**\n{middle}\n */"); + + let spaces = usize::try_from(*spaces).unwrap_or_default(); + Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) + } + pub fn error_handler(result: &ResultType) -> Result { Ok(match &result.throws_type { Some(t) => format!("{}.lift", ffi_converter_name(t)?), diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs index 02f2567d97..ea231dd740 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs @@ -2,7 +2,8 @@ * 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::{backend::CodeType, interface::ObjectImpl}; +use super::CodeType; +use crate::interface::ObjectImpl; #[derive(Debug)] pub struct ObjectCodeType { diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs index e2286c4329..86424658a3 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs @@ -2,7 +2,8 @@ * 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::backend::{CodeType, Literal}; +use super::CodeType; +use crate::backend::Literal; use crate::interface::{Radix, Type}; use paste::paste; diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs index d7cd54960c..401109011f 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::backend::CodeType; +use super::CodeType; #[derive(Debug)] pub struct RecordCodeType { diff --git a/uniffi_bindgen/src/bindings/swift/templates/Async.swift b/uniffi_bindgen/src/bindings/swift/templates/Async.swift index f947408182..f7446ca5e0 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Async.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Async.swift @@ -1,11 +1,13 @@ private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0 private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 +fileprivate var UNIFFI_CONTINUATION_HANDLE_MAP = UniffiHandleMap>() + fileprivate func uniffiRustCallAsync( - rustFutureFunc: () -> UnsafeMutableRawPointer, - pollFunc: (UnsafeMutableRawPointer, @escaping UniFfiRustFutureContinuation, UnsafeMutableRawPointer) -> (), - completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer) -> F, - freeFunc: (UnsafeMutableRawPointer) -> (), + rustFutureFunc: () -> UInt64, + pollFunc: (UInt64, @escaping UniFfiRustFutureContinuation, UInt64) -> (), + completeFunc: (UInt64, UnsafeMutablePointer) -> F, + freeFunc: (UInt64) -> (), liftFunc: (F) throws -> T, errorHandler: ((RustBuffer) throws -> Error)? ) async throws -> T { @@ -19,7 +21,7 @@ fileprivate func uniffiRustCallAsync( var pollResult: Int8; repeat { pollResult = await withUnsafeContinuation { - pollFunc(rustFuture, uniffiFutureContinuationCallback, ContinuationHolder($0).toOpaque()) + pollFunc(rustFuture, uniffiFutureContinuationCallback, UNIFFI_CONTINUATION_HANDLE_MAP.newHandle(obj: $0)) } } while pollResult != UNIFFI_RUST_FUTURE_POLL_READY @@ -31,28 +33,6 @@ fileprivate func uniffiRustCallAsync( // Callback handlers for an async calls. These are invoked by Rust when the future is ready. They // lift the return value or error and resume the suspended function. -fileprivate func uniffiFutureContinuationCallback(ptr: UnsafeMutableRawPointer, pollResult: Int8) { - ContinuationHolder.fromOpaque(ptr).resume(pollResult) -} - -// Wraps UnsafeContinuation in a class so that we can use reference counting when passing it across -// the FFI -fileprivate class ContinuationHolder { - let continuation: UnsafeContinuation - - init(_ continuation: UnsafeContinuation) { - self.continuation = continuation - } - - func resume(_ pollResult: Int8) { - self.continuation.resume(returning: pollResult) - } - - func toOpaque() -> UnsafeMutableRawPointer { - return Unmanaged.passRetained(self).toOpaque() - } - - static func fromOpaque(_ ptr: UnsafeRawPointer) -> ContinuationHolder { - return Unmanaged.fromOpaque(ptr).takeRetainedValue() - } +fileprivate func uniffiFutureContinuationCallback(handle: UInt64, pollResult: Int8) { + UNIFFI_CONTINUATION_HANDLE_MAP.consumeHandle(handle: handle).resume(returning: pollResult) } diff --git a/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h b/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h index 87698e359f..587eab591b 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h @@ -60,7 +60,7 @@ typedef struct RustCallStatus { #endif // def UNIFFI_SHARED_H // Continuation callback for UniFFI Futures -typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t); +typedef void (*UniFfiRustFutureContinuation)(uint64_t, int8_t); // Scaffolding functions {%- for func in ci.iter_ffi_function_definitions() %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift index 157da46128..d23cfe18a1 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift @@ -3,11 +3,11 @@ // Declaration and FfiConverters for {{ type_name }} Callback Interface fileprivate let {{ callback_handler }} : ForeignCallback = - { (handle: UniFFICallbackHandle, method: Int32, argsData: UnsafePointer, argsLen: Int32, out_buf: UnsafeMutablePointer) -> Int32 in + { (handle: UInt64, method: Int32, argsData: UnsafePointer, argsLen: Int32, outBuf: UnsafeMutablePointer) -> Int32 in {% for meth in methods.iter() -%} {%- let method_name = format!("invoke_{}", meth.name())|fn_name %} - func {{ method_name }}(_ swiftCallbackInterface: {{ type_name }}, _ argsData: UnsafePointer, _ argsLen: Int32, _ out_buf: UnsafeMutablePointer) throws -> Int32 { + func {{ method_name }}(_ swiftCallbackInterface: {{ type_name }}, _ argsData: UnsafePointer, _ argsLen: Int32, _ outBuf: UnsafeMutablePointer) throws -> Int32 { {%- if meth.arguments().len() > 0 %} var reader = createReader(data: Data(bytes: argsData, count: Int(argsLen))) {%- endif %} @@ -23,7 +23,7 @@ fileprivate let {{ callback_handler }} : ForeignCallback = ) var writer = [UInt8]() {{ return_type|write_fn }}(result, into: &writer) - out_buf.pointee = RustBuffer(bytes: writer) + outBuf.pointee = RustBuffer(bytes: writer) return UNIFFI_CALLBACK_SUCCESS } {%- when None %} @@ -45,7 +45,7 @@ fileprivate let {{ callback_handler }} : ForeignCallback = do { return try makeCall() } catch let error as {{ error_type|type_name }} { - out_buf.pointee = {{ error_type|lower_fn }}(error) + outBuf.pointee = {{ error_type|lower_fn }}(error) return UNIFFI_CALLBACK_ERROR } {%- endmatch %} @@ -55,21 +55,20 @@ fileprivate let {{ callback_handler }} : ForeignCallback = switch method { case IDX_CALLBACK_FREE: - {{ ffi_converter_name }}.handleMap.remove(handle: handle) - // Sucessful return - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + let _ = {{ ffi_converter_name }}.handleMap.consumeHandle(handle: handle) + return UNIFFI_CALLBACK_SUCCESS + case IDX_CALLBACK_CLONE: + let obj = {{ ffi_converter_name }}.handleMap.get(handle: handle) + outBuf.pointee = {{ ffi_converter_name }}.lowerIntoRustBuffer(obj) return UNIFFI_CALLBACK_SUCCESS {% for meth in methods.iter() -%} {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} case {{ loop.index }}: - guard let cb = {{ ffi_converter_name }}.handleMap.get(handle: handle) else { - out_buf.pointee = {{ Type::String.borrow()|lower_fn }}("No callback in handlemap; this is a Uniffi bug") - return UNIFFI_CALLBACK_UNEXPECTED_ERROR - } do { - return try {{ method_name }}(cb, argsData, argsLen, out_buf) + let cb = {{ ffi_converter_name }}.handleMap.get(handle: handle) + return try {{ method_name }}(cb, argsData, argsLen, outBuf) } catch let error { - out_buf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) + outBuf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) return UNIFFI_CALLBACK_UNEXPECTED_ERROR } {% endfor %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift index d03b7ccb3f..aeb3ea9c63 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift @@ -1,63 +1,8 @@ -fileprivate extension NSLock { - func withLock(f: () throws -> T) rethrows -> T { - self.lock() - defer { self.unlock() } - return try f() - } -} +// Magic numbers for the Rust proxy to call using the same mechanism as every other method. -fileprivate typealias UniFFICallbackHandle = UInt64 -fileprivate class UniFFICallbackHandleMap { - private var leftMap: [UniFFICallbackHandle: T] = [:] - private var counter: [UniFFICallbackHandle: UInt64] = [:] - private var rightMap: [ObjectIdentifier: UniFFICallbackHandle] = [:] - - private let lock = NSLock() - private var currentHandle: UniFFICallbackHandle = 1 - private let stride: UniFFICallbackHandle = 1 - - func insert(obj: T) -> UniFFICallbackHandle { - lock.withLock { - let id = ObjectIdentifier(obj as AnyObject) - let handle = rightMap[id] ?? { - currentHandle += stride - let handle = currentHandle - leftMap[handle] = obj - rightMap[id] = handle - return handle - }() - counter[handle] = (counter[handle] ?? 0) + 1 - return handle - } - } - - func get(handle: UniFFICallbackHandle) -> T? { - lock.withLock { - leftMap[handle] - } - } - - func delete(handle: UniFFICallbackHandle) { - remove(handle: handle) - } - - @discardableResult - func remove(handle: UniFFICallbackHandle) -> T? { - lock.withLock { - defer { counter[handle] = (counter[handle] ?? 1) - 1 } - guard counter[handle] == 1 else { return leftMap[handle] } - let obj = leftMap.removeValue(forKey: handle) - if let obj = obj { - rightMap.removeValue(forKey: ObjectIdentifier(obj as AnyObject)) - } - return obj - } - } -} - -// Magic number for the Rust proxy to call using the same mechanism as every other method, -// to free the callback once it's dropped by Rust. private let IDX_CALLBACK_FREE: Int32 = 0 +private let IDX_CALLBACK_CLONE: Int32 = 0x7FFF_FFFF; + // Callback return codes private let UNIFFI_CALLBACK_SUCCESS: Int32 = 0 private let UNIFFI_CALLBACK_ERROR: Int32 = 1 diff --git a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift index f9e75b2a3f..cc27580ae8 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -3,6 +3,7 @@ {%- let callback_init = format!("uniffiCallbackInit{}", name) %} {%- let methods = cbi.methods() %} {%- let protocol_name = type_name.clone() %} +{%- let protocol_docstring = cbi.docstring() %} {%- let ffi_init_callback = cbi.ffi_init_callback() %} {% include "Protocol.swift" %} @@ -10,28 +11,24 @@ // FfiConverter protocol for callback interfaces fileprivate struct {{ ffi_converter_name }} { - fileprivate static var handleMap = UniFFICallbackHandleMap<{{ type_name }}>() + fileprivate static var handleMap = UniffiHandleMap<{{ type_name }}>() } extension {{ ffi_converter_name }} : FfiConverter { typealias SwiftType = {{ type_name }} - // We can use Handle as the FfiType because it's a typealias to UInt64 - typealias FfiType = UniFFICallbackHandle + typealias FfiType = UInt64 - public static func lift(_ handle: UniFFICallbackHandle) throws -> SwiftType { - guard let callback = handleMap.get(handle: handle) else { - throw UniffiInternalError.unexpectedStaleHandle - } - return callback + public static func lift(_ handle: UInt64) throws -> SwiftType { + return handleMap.get(handle: handle) } public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { - let handle: UniFFICallbackHandle = try readInt(&buf) + let handle: UInt64 = try readInt(&buf) return try lift(handle) } - public static func lower(_ v: SwiftType) -> UniFFICallbackHandle { - return handleMap.insert(obj: v) + public static func lower(_ v: SwiftType) -> UInt64 { + return handleMap.newHandle(obj: v) } public static func write(_ v: SwiftType, into buf: inout [UInt8]) { diff --git a/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift index 99f45290cc..55648b9a9f 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift @@ -1,7 +1,9 @@ // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +{%- call swift::docstring(e, 0) %} public enum {{ type_name }} { {% for variant in e.variants() %} + {%- call swift::docstring(variant, 4) %} case {{ variant.name()|enum_variant_swift_quoted }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%} {% endfor %} } diff --git a/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift index 786091395b..7fa6238609 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift @@ -1,13 +1,15 @@ +{%- call swift::docstring(e, 0) %} public enum {{ type_name }} { {% if e.is_flat() %} {% for variant in e.variants() %} - // Simple error enums only carry a message + {%- call swift::docstring(variant, 4) %} case {{ variant.name()|class_name }}(message: String) {% endfor %} {%- else %} {% for variant in e.variants() %} + {%- call swift::docstring(variant, 4) %} case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%} {% endfor %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift b/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift new file mode 100644 index 0000000000..35a46fc6e3 --- /dev/null +++ b/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift @@ -0,0 +1,79 @@ +// Generate map IDs that are likely to be unique +fileprivate var uniffiMapIdCounter: UInt64 = {{ ci.namespace_hash() }} & 0xFFFF +fileprivate func uniffiNextMapId() -> UInt64 { + let mapId = uniffiMapIdCounter + uniffiMapIdCounter = (uniffiMapIdCounter + 1) & 0xFFFF + return mapId +} + +// Manage handles for objects that are passed across the FFI +// +// See the `uniffi_core::HandleAlloc` trait for the semantics of each method +fileprivate class UniffiHandleMap { + + // Map ID, shifted into the top 16 bits + private let mapId: UInt64 = uniffiNextMapId() << 48 + private let lock: NSLock = NSLock() + private var map: [UInt64: T] = [:] + // Note: foreign handles are always odd + private var keyCounter: UInt64 = 1 + + private func nextKey() -> UInt64 { + let key = keyCounter + keyCounter = (keyCounter + 2) & 0xFFFF_FFFF_FFFF + return key + } + + private func makeHandle(_ key: UInt64) -> UInt64 { + return key | mapId + } + + private func key(_ handle: UInt64) -> UInt64 { + if (handle & 0xFFFF_0000_0000_0000 != mapId) { + fatalError("Handle map ID mismatch") + } + return handle & 0xFFFF_FFFF_FFFF + } + + func newHandle(obj: T) -> UInt64 { + lock.withLock { + let key = nextKey() + map[key] = obj + return makeHandle(key) + } + } + + func get(handle: UInt64) -> T { + lock.withLock { + guard let obj = map[key(handle)] else { + fatalError("handlemap key error: was the handle used after being freed?") + } + return obj + } + } + + func cloneHandle(handle: UInt64) -> UInt64 { + lock.withLock { + guard let obj = map[key(handle)] else { + fatalError("handlemap key error: was the handle used after being freed?") + } + let key = nextKey() + map[key] = obj + return makeHandle(key) + } + } + + @discardableResult + func consumeHandle(handle: UInt64) -> T { + lock.withLock { + guard let obj = map.removeValue(forKey: key(handle)) else { + fatalError("handlemap key error: was the handle used after being freed?") + } + return obj + } + } +} + +fileprivate func uniffiHandleIsFromRust(_ handle: UInt64) -> Bool { + return (handle & 1) == 0 +} diff --git a/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift b/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift index 31b2dadfc2..14754d27e2 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift @@ -99,3 +99,11 @@ private func uniffiCheckCallStatus( throw UniffiInternalError.unexpectedRustCallStatusCode } } + +fileprivate extension NSLock { + func withLock(f: () throws -> T) rethrows -> T { + self.lock() + defer { self.unlock() } + return try f() + } +} diff --git a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift index e39fbeff97..7db1f56f66 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -1,9 +1,11 @@ {%- let obj = ci|get_object_definition(name) %} {%- let (protocol_name, impl_class_name) = obj|object_names %} {%- let methods = obj.methods() %} +{%- let protocol_docstring = obj.docstring() %} {% include "Protocol.swift" %} +{%- call swift::docstring(obj, 0) %} public class {{ impl_class_name }}: {%- for tm in obj.uniffi_traits() %} {%- match tm %} @@ -19,31 +21,36 @@ public class {{ impl_class_name }}: {%- endmatch %} {%- endfor %} {{ protocol_name }} { - fileprivate let pointer: UnsafeMutableRawPointer + fileprivate let handle: UInt64 // TODO: We'd like this to be `private` but for Swifty reasons, // we can't implement `FfiConverter` without making this `required` and we can't // make it `required` without making it `public`. - required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { - self.pointer = pointer + required init(handle: UInt64) { + self.handle = handle + } + + public func uniffiCloneHandle() -> UInt64 { + return try! rustCall { {{ obj.ffi_object_clone().name() }}(self.handle, $0) } } {%- match obj.primary_constructor() %} {%- when Some with (cons) %} + {%- call swift::docstring(cons, 4) %} public convenience init({% call swift::arg_list_decl(cons) -%}) {% call swift::throws(cons) %} { - self.init(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %}) + self.init(handle: {% call swift::to_ffi_call(cons) %}) } {%- when None %} {%- endmatch %} deinit { - try! rustCall { {{ obj.ffi_object_free().name() }}(pointer, $0) } + try! rustCall { {{ obj.ffi_object_free().name() }}(handle, $0) } } {% for cons in obj.alternate_constructors() %} - + {%- call swift::docstring(cons, 4) %} public static func {{ cons.name()|fn_name }}({% call swift::arg_list_decl(cons) %}) {% call swift::throws(cons) %} -> {{ impl_class_name }} { - return {{ impl_class_name }}(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %}) + return {{ impl_class_name }}(handle: {% call swift::to_ffi_call(cons) %}) } {% endfor %} @@ -51,12 +58,12 @@ public class {{ impl_class_name }}: {# // TODO: Maybe merge the two templates (i.e the one with a return type and the one without) #} {% for meth in obj.methods() -%} {%- if meth.is_async() %} - + {%- call swift::docstring(meth, 4) %} public func {{ meth.name()|fn_name }}({%- call swift::arg_list_decl(meth) -%}) async {% call swift::throws(meth) %}{% match meth.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} { return {% call swift::try(meth) %} await uniffiRustCallAsync( rustFutureFunc: { {{ meth.ffi_func().name() }}( - self.pointer + self.uniffiCloneHandle() {%- for arg in meth.arguments() -%} , {{ arg|lower_fn }}({{ arg.name()|var_name }}) @@ -86,17 +93,17 @@ public class {{ impl_class_name }}: {%- match meth.return_type() -%} {%- when Some with (return_type) %} - + {%- call swift::docstring(meth, 4) %} public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} -> {{ return_type|type_name }} { return {% call swift::try(meth) %} {{ return_type|lift_fn }}( - {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %} + {% call swift::to_ffi_call_with_prefix("self.uniffiCloneHandle()", meth) %} ) } {%- when None %} - + {%- call swift::docstring(meth, 4) %} public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} { - {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %} + {% call swift::to_ffi_call_with_prefix("self.uniffiCloneHandle()", meth) %} } {%- endmatch -%} @@ -108,25 +115,25 @@ public class {{ impl_class_name }}: {%- when UniffiTrait::Display { fmt } %} public var description: String { return {% call swift::try(fmt) %} {{ fmt.return_type().unwrap()|lift_fn }}( - {% call swift::to_ffi_call_with_prefix("self.pointer", fmt) %} + {% call swift::to_ffi_call_with_prefix("self.uniffiCloneHandle()", fmt) %} ) } {%- when UniffiTrait::Debug { fmt } %} public var debugDescription: String { return {% call swift::try(fmt) %} {{ fmt.return_type().unwrap()|lift_fn }}( - {% call swift::to_ffi_call_with_prefix("self.pointer", fmt) %} + {% call swift::to_ffi_call_with_prefix("self.uniffiCloneHandle()", fmt) %} ) } {%- when UniffiTrait::Eq { eq, ne } %} public static func == (lhs: {{ impl_class_name }}, other: {{ impl_class_name }}) -> Bool { return {% call swift::try(eq) %} {{ eq.return_type().unwrap()|lift_fn }}( - {% call swift::to_ffi_call_with_prefix("lhs.pointer", eq) %} + {% call swift::to_ffi_call_with_prefix("lhs.uniffiCloneHandle()", eq) %} ) } {%- when UniffiTrait::Hash { hash } %} public func hash(into hasher: inout Hasher) { let val = {% call swift::try(hash) %} {{ hash.return_type().unwrap()|lift_fn }}( - {% call swift::to_ffi_call_with_prefix("self.pointer", hash) %} + {% call swift::to_ffi_call_with_prefix("self.uniffiCloneHandle()", hash) %} ) hasher.combine(val) } @@ -145,43 +152,44 @@ public class {{ impl_class_name }}: public struct {{ ffi_converter_name }}: FfiConverter { {%- if obj.is_trait_interface() %} - fileprivate static var handleMap = UniFFICallbackHandleMap<{{ type_name }}>() + fileprivate static var handleMap = UniffiHandleMap<{{ type_name }}>() {%- endif %} - typealias FfiType = UnsafeMutableRawPointer + typealias FfiType = UInt64 typealias SwiftType = {{ type_name }} - public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { - return {{ impl_class_name }}(unsafeFromRawPointer: pointer) + public static func lift(_ handle: UInt64) throws -> {{ type_name }} { + {%- if obj.is_trait_interface() %} + if uniffiHandleIsFromRust(handle) { + return {{ impl_class_name }}(handle: handle) + } else { + return handleMap.consumeHandle(handle: handle) + } + {%- else %} + return {{ impl_class_name }}(handle: handle) + {%- endif %} } - public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { - {%- match obj.imp() %} - {%- when ObjectImpl::Struct %} - return value.pointer - {%- when ObjectImpl::Trait %} - guard let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: handleMap.insert(obj: value))) else { - fatalError("Cast to UnsafeMutableRawPointer failed") + public static func lower(_ value: {{ type_name }}) -> UInt64 { + {%- if obj.is_trait_interface() %} + if let rustImpl = value as? {{ impl_class_name }} { + // If we're wrapping a trait implemented in Rust, return that handle directly rather + // than wrapping it again in Swift. + return rustImpl.uniffiCloneHandle() + } else { + return handleMap.newHandle(obj: value) } - return ptr - {%- endmatch %} + {%- else %} + return value.uniffiCloneHandle() + {%- endif %} } public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { - let v: UInt64 = try readInt(&buf) - // The Rust code won't compile if a pointer won't fit in a UInt64. - // We have to go via `UInt` because that's the thing that's the size of a pointer. - let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) - if (ptr == nil) { - throw UniffiInternalError.unexpectedNullPointer - } - return try lift(ptr!) + return try lift(try readInt(&buf)) } public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { - // This fiddling is because `Int` is the thing that's the same size as a pointer. - // The Rust code won't compile if a pointer won't fit in a `UInt64`. - writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + writeInt(&buf, lower(value)) } } @@ -189,10 +197,10 @@ public struct {{ ffi_converter_name }}: FfiConverter { We always write these public functions just in case the enum is used as an external type by another crate. #} -public func {{ ffi_converter_name }}_lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { - return try {{ ffi_converter_name }}.lift(pointer) +public func {{ ffi_converter_name }}_lift(_ handle: UInt64) throws -> {{ type_name }} { + return try {{ ffi_converter_name }}.lift(handle) } -public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { +public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> UInt64 { return {{ ffi_converter_name }}.lower(value) } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift b/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift index 9fb2766de2..7df953558a 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift @@ -1,5 +1,7 @@ +{%- call swift::docstring_value(protocol_docstring, 0) %} public protocol {{ protocol_name }} : AnyObject { {% for meth in methods.iter() -%} + {%- call swift::docstring(meth, 4) %} func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::async(meth) -%}{% call swift::throws(meth) -%} {%- match meth.return_type() -%} {%- when Some with (return_type) %} -> {{ return_type|type_name -}} diff --git a/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift index d3968b2071..3b94b92dc2 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift @@ -1,7 +1,9 @@ {%- let rec = ci|get_record_definition(name) %} +{%- call swift::docstring(rec, 0) %} public struct {{ type_name }} { {%- for field in rec.fields() %} - public var {{ field.name()|var_name }}: {{ field|type_name }} + {%- call swift::docstring(field, 4) %} + public {% if config.generate_immutable_records() %}let{% else %}var{% endif %} {{ field.name()|var_name }}: {{ field|type_name }} {%- endfor %} // Default memberwise initializers are never public by default, so we diff --git a/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift index 2f737b6635..052fd8e177 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift @@ -147,6 +147,24 @@ fileprivate protocol FfiConverter { static func write(_ value: SwiftType, into buf: inout [UInt8]) } +fileprivate extension FfiConverter { + static func liftFromRustBuffer(_ buf: RustBuffer) throws -> SwiftType { + var reader = createReader(data: Data(rustBuffer: buf)) + let value = try read(from: &reader) + if hasRemaining(reader) { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } + + static func lowerIntoRustBuffer(_ value: SwiftType) -> RustBuffer { + var writer = createWriter() + write(value, into: &writer) + return RustBuffer(bytes: writer) + } +} + // Types conforming to `Primitive` pass themselves directly over the FFI. fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { } @@ -166,18 +184,10 @@ fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustB extension FfiConverterRustBuffer { public static func lift(_ buf: RustBuffer) throws -> SwiftType { - var reader = createReader(data: Data(rustBuffer: buf)) - let value = try read(from: &reader) - if hasRemaining(reader) { - throw UniffiInternalError.incompleteData - } - buf.deallocate() - return value + return try liftFromRustBuffer(buf) } public static func lower(_ value: SwiftType) -> RustBuffer { - var writer = createWriter() - write(value, into: &writer) - return RustBuffer(bytes: writer) + return lowerIntoRustBuffer(value) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift index a2c6311931..a258aa7bb6 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift @@ -1,5 +1,6 @@ {%- if func.is_async() %} +{%- call swift::docstring(func, 0) %} public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) async {% call swift::throws(func) %}{% match func.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} { return {% call swift::try(func) %} await uniffiRustCallAsync( rustFutureFunc: { @@ -32,6 +33,7 @@ public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) a {%- match func.return_type() -%} {%- when Some with (return_type) %} +{%- call swift::docstring(func, 0) %} public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) {% call swift::throws(func) %} -> {{ return_type|type_name }} { return {% call swift::try(func) %} {{ return_type|lift_fn }}( {% call swift::to_ffi_call(func) %} @@ -40,6 +42,7 @@ public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) { {%- when None %} +{%- call swift::docstring(func, 0) %} public func {{ func.name()|fn_name }}({% call swift::arg_list_decl(func) %}) {% call swift::throws(func) %} { {% call swift::to_ffi_call(func) %} } diff --git a/uniffi_bindgen/src/bindings/swift/templates/macros.swift b/uniffi_bindgen/src/bindings/swift/templates/macros.swift index bcf6938639..800fc8fbab 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/macros.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/macros.swift @@ -87,3 +87,15 @@ {%- macro try(func) %} {%- if func.throws() %}try {% else %}try! {% endif %} {%- endmacro -%} + +{%- macro docstring_value(maybe_docstring, indent_spaces) %} +{%- match maybe_docstring %} +{%- when Some(docstring) %} +{{ docstring|docstring(indent_spaces) }} +{%- else %} +{%- endmatch %} +{%- endmacro %} + +{%- macro docstring(defn, indent_spaces) %} +{%- call docstring_value(defn.docstring(), indent_spaces) %} +{%- endmacro %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift b/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift index c34d348efb..7286137409 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift @@ -1,5 +1,8 @@ // This file was autogenerated by some hot garbage in the `uniffi` crate. // Trust me, you don't want to mess with it! + +{%- call swift::docstring_value(ci.namespace_docstring(), 0) %} + {%- import "macros.swift" as swift %} import Foundation {%- for imported_class in self.imports() %} @@ -14,6 +17,7 @@ import {{ config.ffi_module_name() }} #endif {% include "RustBufferTemplate.swift" %} +{% include "HandleMap.swift" %} {% include "Helpers.swift" %} // Public interface members begin here. diff --git a/uniffi_bindgen/src/interface/callbacks.rs b/uniffi_bindgen/src/interface/callbacks.rs index 9bafce25de..9573e07932 100644 --- a/uniffi_bindgen/src/interface/callbacks.rs +++ b/uniffi_bindgen/src/interface/callbacks.rs @@ -52,18 +52,11 @@ pub struct CallbackInterface { // avoids a weird circular dependency in the calculation. #[checksum_ignore] pub(super) ffi_init_callback: FfiFunction, + #[checksum_ignore] + pub(super) docstring: Option, } impl CallbackInterface { - pub fn new(name: String, module_path: String) -> CallbackInterface { - CallbackInterface { - name, - module_path, - methods: Default::default(), - ffi_init_callback: Default::default(), - } - } - pub fn name(&self) -> &str { &self.name } @@ -83,6 +76,10 @@ impl CallbackInterface { pub fn iter_types(&self) -> TypeIterator<'_> { Box::new(self.methods.iter().flat_map(Method::iter_types)) } + + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } } impl AsType for CallbackInterface { @@ -94,6 +91,20 @@ impl AsType for CallbackInterface { } } +impl TryFrom for CallbackInterface { + type Error = anyhow::Error; + + fn try_from(meta: uniffi_meta::CallbackInterfaceMetadata) -> anyhow::Result { + Ok(Self { + name: meta.name, + module_path: meta.module_path, + methods: Default::default(), + ffi_init_callback: Default::default(), + docstring: meta.docstring.clone(), + }) + } +} + #[cfg(test)] mod test { use super::super::ComponentInterface; @@ -140,4 +151,21 @@ mod test { assert_eq!(callbacks_two.methods()[0].name(), "two"); assert_eq!(callbacks_two.methods()[1].name(), "too"); } + + #[test] + fn test_docstring_callback_interface() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + callback interface Testing { }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_callback_interface_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/uniffi_bindgen/src/interface/enum_.rs b/uniffi_bindgen/src/interface/enum_.rs index 82baf1dd50..2a7a72dc77 100644 --- a/uniffi_bindgen/src/interface/enum_.rs +++ b/uniffi_bindgen/src/interface/enum_.rs @@ -189,6 +189,8 @@ pub struct Enum { // * For an Enum not used as an error but which has no variants with data, `flat` will be // false when generating the scaffolding but `true` when generating bindings. pub(super) flat: bool, + #[checksum_ignore] + pub(super) docstring: Option, } impl Enum { @@ -208,6 +210,10 @@ impl Enum { Box::new(self.variants.iter().flat_map(Variant::iter_types)) } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + // Sadly can't use TryFrom due to the 'is_flat' complication. pub fn try_from_meta(meta: uniffi_meta::EnumMetadata, flat: bool) -> Result { // This is messy - error enums are considered "flat" if the user @@ -224,6 +230,7 @@ impl Enum { .map(TryInto::try_into) .collect::>()?, flat, + docstring: meta.docstring.clone(), }) } } @@ -244,6 +251,8 @@ impl AsType for Enum { pub struct Variant { pub(super) name: String, pub(super) fields: Vec, + #[checksum_ignore] + pub(super) docstring: Option, } impl Variant { @@ -259,6 +268,10 @@ impl Variant { !self.fields.is_empty() } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn iter_types(&self) -> TypeIterator<'_> { Box::new(self.fields.iter().flat_map(Field::iter_types)) } @@ -275,6 +288,7 @@ impl TryFrom for Variant { .into_iter() .map(TryInto::try_into) .collect::>()?, + docstring: meta.docstring.clone(), }) } } @@ -564,4 +578,76 @@ mod test { vec!["Normal", "Error"] ); } + + #[test] + fn test_docstring_enum() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + enum Testing { "foo" }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_enum_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_enum_variant() { + const UDL: &str = r#" + namespace test{}; + enum Testing { + /// informative docstring + "foo" + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_enum_definition("Testing").unwrap().variants()[0] + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_associated_enum() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + [Enum] + interface Testing { }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_enum_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_associated_enum_variant() { + const UDL: &str = r#" + namespace test{}; + [Enum] + interface Testing { + /// informative docstring + testing(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_enum_definition("Testing").unwrap().variants()[0] + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/uniffi_bindgen/src/interface/ffi.rs b/uniffi_bindgen/src/interface/ffi.rs index 8f23ccbb90..a0a84f0b3a 100644 --- a/uniffi_bindgen/src/interface/ffi.rs +++ b/uniffi_bindgen/src/interface/ffi.rs @@ -33,11 +33,8 @@ pub enum FfiType { Int64, Float32, Float64, - /// A `*const c_void` pointer to a rust-owned `Arc`. - /// If you've got one of these, you must call the appropriate rust function to free it. - /// The templates will generate a unique `free` function for each T. - /// The inner string references the name of the `T` type. - RustArcPtr(String), + /// 64-bit handle. + Handle, /// A byte buffer allocated by rust, and owned by whoever currently holds it. /// If you've got one of these, you must either call the appropriate rust function to free it /// or pass it to someone that will. @@ -49,16 +46,10 @@ pub enum FfiType { ForeignBytes, /// Pointer to a callback function that handles all callbacks on the foreign language side. ForeignCallback, - /// Pointer-sized opaque handle that represents a foreign executor. Foreign bindings can - /// either use an actual pointer or a usized integer. - ForeignExecutorHandle, /// Pointer to the callback function that's invoked to schedule calls with a ForeignExecutor ForeignExecutorCallback, - /// Pointer to a Rust future - RustFutureHandle, /// Continuation function for a Rust future RustFutureContinuationCallback, - RustFutureContinuationData, // TODO: you can imagine a richer structural typesystem here, e.g. `Ref` or something. // We don't need that yet and it's possible we never will, so it isn't here for now. } @@ -90,11 +81,14 @@ impl From<&Type> for FfiType { // Byte strings are also always owned rust values. // We might add a separate type for borrowed byte strings in future as well. Type::Bytes => FfiType::RustBuffer(None), - // Objects are pointers to an Arc<> - Type::Object { name, .. } => FfiType::RustArcPtr(name.to_owned()), - // Callback interfaces are passed as opaque integer handles. - Type::CallbackInterface { .. } => FfiType::UInt64, - Type::ForeignExecutor => FfiType::ForeignExecutorHandle, + // Object types interfaces are passed as opaque handles. + Type::Object { .. } + | Type::CallbackInterface { .. } + | Type::ForeignExecutor + | Type::External { + kind: ExternalKind::Interface, + .. + } => FfiType::Handle, // Other types are serialized into a bytebuffer and deserialized on the other side. Type::Enum { .. } | Type::Record { .. } @@ -103,11 +97,8 @@ impl From<&Type> for FfiType { | Type::Map { .. } | Type::Timestamp | Type::Duration => FfiType::RustBuffer(None), - Type::External { - name, - kind: ExternalKind::Interface, - .. - } => FfiType::RustArcPtr(name.clone()), + // External data classes are also serialized as a buffer, but need a module name to + // make imports work. Type::External { name, kind: ExternalKind::DataClass, @@ -150,6 +141,29 @@ pub struct FfiFunction { } impl FfiFunction { + pub fn ffi_clone(name: String) -> Self { + Self { + name, + arguments: vec![FfiArgument { + name: "handle".to_owned(), + type_: FfiType::Handle, + }], + return_type: Some(FfiType::Handle), + ..Default::default() + } + } + + pub fn ffi_free(name: String) -> Self { + Self { + name, + arguments: vec![FfiArgument { + name: "handle".to_owned(), + type_: FfiType::Handle, + }], + ..Default::default() + } + } + pub fn callback_init(module_path: &str, trait_name: &str) -> Self { Self { name: uniffi_meta::init_callback_fn_symbol_name(module_path, trait_name), @@ -194,7 +208,7 @@ impl FfiFunction { ) { self.arguments = args.into_iter().collect(); if self.is_async() { - self.return_type = Some(FfiType::RustFutureHandle); + self.return_type = Some(FfiType::Handle); self.has_rust_call_status_arg = false; } else { self.return_type = return_type; diff --git a/uniffi_bindgen/src/interface/function.rs b/uniffi_bindgen/src/interface/function.rs index 2d18288c1c..2692b4bcc9 100644 --- a/uniffi_bindgen/src/interface/function.rs +++ b/uniffi_bindgen/src/interface/function.rs @@ -59,6 +59,8 @@ pub struct Function { // avoids a weird circular dependency in the calculation. #[checksum_ignore] pub(super) ffi_func: FfiFunction, + #[checksum_ignore] + pub(super) docstring: Option, pub(super) throws: Option, pub(super) checksum_fn_name: String, // Force a checksum value, or we'll fallback to the trait. @@ -128,6 +130,10 @@ impl Function { .chain(self.return_type.iter().flat_map(Type::iter_types)), ) } + + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } } impl From for Argument { @@ -163,6 +169,7 @@ impl From for Function { arguments, return_type, ffi_func, + docstring: meta.docstring.clone(), throws: meta.throws, checksum_fn_name, checksum: meta.checksum, @@ -364,4 +371,22 @@ mod test { ); Ok(()) } + + #[test] + fn test_docstring_function() { + const UDL: &str = r#" + namespace test { + /// informative docstring + void testing(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_function_definition("testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/uniffi_bindgen/src/interface/mod.rs b/uniffi_bindgen/src/interface/mod.rs index c56a3cd65c..9281147b27 100644 --- a/uniffi_bindgen/src/interface/mod.rs +++ b/uniffi_bindgen/src/interface/mod.rs @@ -45,7 +45,8 @@ //! * Error messages and general developer experience leave a lot to be desired. use std::{ - collections::{btree_map::Entry, BTreeMap, BTreeSet, HashSet}, + collections::{btree_map::Entry, hash_map::DefaultHasher, BTreeMap, BTreeSet, HashSet}, + hash::{Hash, Hasher}, iter, }; @@ -139,6 +140,11 @@ impl ComponentInterface { self.types.namespace ); } + + if group.namespace_docstring.is_some() { + self.types.namespace_docstring = group.namespace_docstring.clone(); + } + // Unconditionally add the String type, which is used by the panic handling self.types.add_known_type(&uniffi_meta::Type::String)?; crate::macro_metadata::add_group_to_ci(self, group)?; @@ -153,6 +159,19 @@ impl ComponentInterface { &self.types.namespace.name } + pub fn namespace_docstring(&self) -> Option<&str> { + self.types.namespace_docstring.as_deref() + } + + /// Get the checksum value for our namespace. + /// + /// This is highly likely to be unique for each `ComponentInterface` + pub fn namespace_hash(&self) -> u32 { + let mut hasher = DefaultHasher::new(); + self.types.namespace.name.hash(&mut hasher); + hasher.finish() as u32 + } + pub fn uniffi_contract_version(&self) -> u32 { // This is set by the scripts in the version-mismatch fixture let force_version = std::env::var("UNIFFI_FORCE_CONTRACT_VERSION"); @@ -310,8 +329,17 @@ impl ComponentInterface { /// This is important to know in language bindings that cannot integrate object types /// tightly with the host GC, and hence need to perform manual destruction of objects. pub fn item_contains_object_references(&self, item: &Type) -> bool { - self.iter_types_in_item(item) - .any(|t| matches!(t, Type::Object { .. })) + // this is surely broken for external records with object refs? + self.iter_types_in_item(item).any(|t| { + matches!( + t, + Type::Object { .. } + | Type::External { + kind: ExternalKind::Interface, + .. + } + ) + }) } /// Check whether the given item contains any (possibly nested) unsigned types @@ -443,15 +471,15 @@ impl ComponentInterface { arguments: vec![ FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }, FfiArgument { name: "callback".to_owned(), type_: FfiType::RustFutureContinuationCallback, }, FfiArgument { - name: "callback_data".to_owned(), - type_: FfiType::RustFutureContinuationData, + name: "continuation_data".to_owned(), + type_: FfiType::Handle, }, ], return_type: None, @@ -469,7 +497,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: return_ffi_type, has_rust_call_status_arg: true, @@ -484,7 +512,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: None, has_rust_call_status_arg: false, @@ -499,7 +527,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: None, has_rust_call_status_arg: false, @@ -521,7 +549,7 @@ impl ComponentInterface { FfiType::Int64 => format!("ffi_{namespace}_{base_name}_i64"), FfiType::Float32 => format!("ffi_{namespace}_{base_name}_f32"), FfiType::Float64 => format!("ffi_{namespace}_{base_name}_f64"), - FfiType::RustArcPtr(_) => format!("ffi_{namespace}_{base_name}_pointer"), + FfiType::Handle => format!("ffi_{namespace}_{base_name}_handle"), FfiType::RustBuffer(_) => format!("ffi_{namespace}_{base_name}_rust_buffer"), _ => unimplemented!("FFI return type: {t:?}"), }, @@ -622,9 +650,9 @@ impl ComponentInterface { Some(FfiType::Int64), Some(FfiType::Float32), Some(FfiType::Float64), - // RustBuffer and RustArcPtr have an inner field which doesn't affect the rust future + Some(FfiType::Handle), + // RustBuffer has an inner field which doesn't affect the rust future // complete scaffolding function, so we just use a placeholder value here. - Some(FfiType::RustArcPtr("".to_owned())), Some(FfiType::RustBuffer(None)), None, ]; @@ -869,31 +897,6 @@ impl ComponentInterface { bail!("Conflicting type definition for \"{}\"", f.name()); } } - - for ty in self.iter_types() { - match ty { - Type::Object { name, .. } => { - ensure!( - self.objects.iter().any(|o| o.name == *name), - "Object `{name}` has no definition" - ); - } - Type::Record { name, .. } => { - ensure!( - self.records.contains_key(name), - "Record `{name}` has no definition", - ); - } - Type::Enum { name, .. } => { - ensure!( - self.enums.contains_key(name), - "Enum `{name}` has no definition", - ); - } - _ => {} - } - } - Ok(()) } @@ -1078,20 +1081,24 @@ mod test { let err = ComponentInterface::from_webidl(UDL2, "crate_name").unwrap_err(); assert_eq!( err.to_string(), - "Mismatching definition for enum `Testing`!\nexisting definition: Enum { + "Mismatching definition for enum `Testing`! +existing definition: Enum { name: \"Testing\", module_path: \"crate_name\", variants: [ Variant { name: \"one\", fields: [], + docstring: None, }, Variant { name: \"two\", fields: [], + docstring: None, }, ], flat: true, + docstring: None, }, new definition: Enum { name: \"Testing\", @@ -1100,13 +1107,16 @@ new definition: Enum { Variant { name: \"three\", fields: [], + docstring: None, }, Variant { name: \"four\", fields: [], + docstring: None, }, ], flat: true, + docstring: None, }", ); @@ -1220,4 +1230,25 @@ new definition: Enum { imp: ObjectImpl::Struct, })); } + + #[test] + fn test_docstring_namespace() { + const UDL: &str = r#" + /// informative docstring + namespace test{}; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.namespace_docstring().unwrap(), "informative docstring"); + } + + #[test] + fn test_multiline_docstring() { + const UDL: &str = r#" + /// informative + /// docstring + namespace test{}; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.namespace_docstring().unwrap(), "informative\ndocstring"); + } } diff --git a/uniffi_bindgen/src/interface/object.rs b/uniffi_bindgen/src/interface/object.rs index d79e7fccb1..4efa7d1a25 100644 --- a/uniffi_bindgen/src/interface/object.rs +++ b/uniffi_bindgen/src/interface/object.rs @@ -57,8 +57,6 @@ //! # Ok::<(), anyhow::Error>(()) //! ``` -use std::iter; - use anyhow::Result; use uniffi_meta::Checksum; @@ -99,10 +97,14 @@ pub struct Object { // hash value we're trying to calculate here, so excluding it // avoids a weird circular dependency in the calculation. #[checksum_ignore] + pub(super) ffi_func_clone: FfiFunction, + #[checksum_ignore] pub(super) ffi_func_free: FfiFunction, // Ffi function to initialize the foreign callback for trait interfaces #[checksum_ignore] pub(super) ffi_init_callback: Option, + #[checksum_ignore] + pub(super) docstring: Option, } impl Object { @@ -158,6 +160,10 @@ impl Object { self.uniffi_traits.iter().collect() } + pub fn ffi_object_clone(&self) -> &FfiFunction { + &self.ffi_func_clone + } + pub fn ffi_object_free(&self) -> &FfiFunction { &self.ffi_func_free } @@ -168,8 +174,13 @@ impl Object { .unwrap_or_else(|| panic!("No ffi_init_callback set for {}", &self.name)) } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn iter_ffi_function_definitions(&self) -> impl Iterator { - iter::once(&self.ffi_func_free) + [&self.ffi_func_clone, &self.ffi_func_free] + .into_iter() .chain(&self.ffi_init_callback) .chain(self.constructors.iter().map(|f| &f.ffi_func)) .chain(self.methods.iter().map(|f| &f.ffi_func)) @@ -189,8 +200,8 @@ impl Object { pub fn derive_ffi_funcs(&mut self) -> Result<()> { assert!(!self.ffi_func_free.name().is_empty()); self.ffi_func_free.arguments = vec![FfiArgument { - name: "ptr".to_string(), - type_: FfiType::RustArcPtr(self.name.to_string()), + name: "handle".to_string(), + type_: FfiType::Handle, }]; self.ffi_func_free.return_type = None; self.ffi_func_free.is_object_free_function = true; @@ -236,7 +247,8 @@ impl AsType for Object { impl From for Object { fn from(meta: uniffi_meta::ObjectMetadata) -> Self { - let ffi_free_name = meta.free_ffi_symbol_name(); + let ffi_func_clone = FfiFunction::ffi_clone(meta.clone_ffi_symbol_name()); + let ffi_func_free = FfiFunction::ffi_free(meta.free_ffi_symbol_name()); Object { module_path: meta.module_path, name: meta.name, @@ -244,11 +256,10 @@ impl From for Object { constructors: Default::default(), methods: Default::default(), uniffi_traits: Default::default(), - ffi_func_free: FfiFunction { - name: ffi_free_name, - ..Default::default() - }, + ffi_func_clone, + ffi_func_free, ffi_init_callback: None, + docstring: meta.docstring.clone(), } } } @@ -291,6 +302,8 @@ pub struct Constructor { // avoids a weird circular dependency in the calculation. #[checksum_ignore] pub(super) ffi_func: FfiFunction, + #[checksum_ignore] + pub(super) docstring: Option, pub(super) throws: Option, pub(super) checksum_fn_name: String, // Force a checksum value, or we'll fallback to the trait. @@ -335,6 +348,10 @@ impl Constructor { self.throws.as_ref() } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn is_primary_constructor(&self) -> bool { self.name == "new" } @@ -342,7 +359,7 @@ impl Constructor { fn derive_ffi_func(&mut self) { assert!(!self.ffi_func.name().is_empty()); self.ffi_func.arguments = self.arguments.iter().map(Into::into).collect(); - self.ffi_func.return_type = Some(FfiType::RustArcPtr(self.object_name.clone())); + self.ffi_func.return_type = Some(FfiType::Handle); } pub fn iter_types(&self) -> TypeIterator<'_> { @@ -366,6 +383,7 @@ impl From for Constructor { object_module_path: meta.module_path, arguments, ffi_func, + docstring: meta.docstring.clone(), throws: meta.throws.map(Into::into), checksum_fn_name, checksum: meta.checksum, @@ -394,6 +412,8 @@ pub struct Method { // avoids a weird circular dependency in the calculation. #[checksum_ignore] pub(super) ffi_func: FfiFunction, + #[checksum_ignore] + pub(super) docstring: Option, pub(super) throws: Option, pub(super) takes_self_by_arc: bool, pub(super) checksum_fn_name: String, @@ -419,7 +439,7 @@ impl Method { // hence `arguments` and `full_arguments` are different. pub fn full_arguments(&self) -> Vec { vec![Argument { - name: "ptr".to_string(), + name: "handle".to_string(), // TODO: ideally we'd get this via `ci.resolve_type_expression` so that it // is contained in the proper `TypeUniverse`, but this works for now. type_: Type::Object { @@ -464,6 +484,10 @@ impl Method { self.throws.as_ref() } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn takes_self_by_arc(&self) -> bool { self.takes_self_by_arc } @@ -510,6 +534,7 @@ impl From for Method { arguments, return_type, ffi_func, + docstring: meta.docstring.clone(), throws: meta.throws.map(Into::into), takes_self_by_arc: meta.takes_self_by_arc, checksum_fn_name, @@ -535,6 +560,7 @@ impl From for Method { is_async: false, arguments, return_type, + docstring: meta.docstring.clone(), throws: meta.throws.map(Into::into), takes_self_by_arc: meta.takes_self_by_arc, checksum_fn_name, @@ -789,4 +815,62 @@ mod test { "Trait interfaces can not have constructors: \"new\"" ); } + + #[test] + fn test_docstring_object() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + interface Testing { }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_object_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_constructor() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + /// informative docstring + constructor(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_object_definition("Testing") + .unwrap() + .primary_constructor() + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_method() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + /// informative docstring + void testing(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_object_definition("Testing") + .unwrap() + .get_method("testing") + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/uniffi_bindgen/src/interface/record.rs b/uniffi_bindgen/src/interface/record.rs index 15f79aea2f..e9a6004189 100644 --- a/uniffi_bindgen/src/interface/record.rs +++ b/uniffi_bindgen/src/interface/record.rs @@ -60,6 +60,8 @@ pub struct Record { pub(super) name: String, pub(super) module_path: String, pub(super) fields: Vec, + #[checksum_ignore] + pub(super) docstring: Option, } impl Record { @@ -71,6 +73,10 @@ impl Record { &self.fields } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn iter_types(&self) -> TypeIterator<'_> { Box::new(self.fields.iter().flat_map(Field::iter_types)) } @@ -101,6 +107,7 @@ impl TryFrom for Record { .into_iter() .map(TryInto::try_into) .collect::>()?, + docstring: meta.docstring.clone(), }) } } @@ -111,6 +118,8 @@ pub struct Field { pub(super) name: String, pub(super) type_: Type, pub(super) default: Option, + #[checksum_ignore] + pub(super) docstring: Option, } impl Field { @@ -122,6 +131,10 @@ impl Field { self.default.as_ref() } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn iter_types(&self) -> TypeIterator<'_> { self.type_.iter_types() } @@ -144,6 +157,7 @@ impl TryFrom for Field { name, type_, default, + docstring: meta.docstring.clone(), }) } } @@ -231,4 +245,39 @@ mod test { .iter_types() .any(|t| matches!(t, Type::Record { name, .. } if name == "Testing"))); } + + #[test] + fn test_docstring_record() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + dictionary Testing { }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_record_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_record_field() { + const UDL: &str = r#" + namespace test{}; + dictionary Testing { + /// informative docstring + i32 testing; + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_record_definition("Testing").unwrap().fields()[0] + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/uniffi_bindgen/src/interface/universe.rs b/uniffi_bindgen/src/interface/universe.rs index e69d86e44f..72cd0099f1 100644 --- a/uniffi_bindgen/src/interface/universe.rs +++ b/uniffi_bindgen/src/interface/universe.rs @@ -25,6 +25,7 @@ pub use uniffi_meta::{AsType, ExternalKind, NamespaceMetadata, ObjectImpl, Type, pub(crate) struct TypeUniverse { /// The unique prefixes that we'll use for namespacing when exposing this component's API. pub namespace: NamespaceMetadata, + pub namespace_docstring: Option, // Named type definitions (including aliases). type_definitions: HashMap, diff --git a/uniffi_bindgen/src/lib.rs b/uniffi_bindgen/src/lib.rs index 51e35235a7..93eb23a782 100644 --- a/uniffi_bindgen/src/lib.rs +++ b/uniffi_bindgen/src/lib.rs @@ -58,9 +58,8 @@ //! //! ### 3) Generate and include component scaffolding from the UDL file //! -//! First you will need to install `uniffi-bindgen` on your system using `cargo install uniffi_bindgen`. -//! Then add to your crate `uniffi_build` under `[build-dependencies]`. -//! Finally, add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding` +//! Add to your crate `uniffi_build` under `[build-dependencies]`, +//! then add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding` //! to process your `.udl` file. This will generate some Rust code to be included in the top-level source //! code of your crate. If your UDL file is named `example.udl`, then your build script would call: //! @@ -77,12 +76,13 @@ //! //! ### 4) Generate foreign language bindings for the library //! -//! The `uniffi-bindgen` utility provides a command-line tool that can produce code to +//! You will need ensure a local `uniffi-bindgen` - see +//! This utility provides a command-line tool that can produce code to //! consume the Rust library in any of several supported languages. //! It is done by calling (in kotlin for example): //! //! ```text -//! uniffi-bindgen --language kotlin ./src/example.udl +//! cargo run --bin -p uniffi-bindgen --language kotlin ./src/example.udl //! ``` //! //! This will produce a file `example.kt` in the same directory as the .udl file, containing kotlin bindings diff --git a/uniffi_bindgen/src/macro_metadata/ci.rs b/uniffi_bindgen/src/macro_metadata/ci.rs index 7ce6c3a70b..16992f2a3b 100644 --- a/uniffi_bindgen/src/macro_metadata/ci.rs +++ b/uniffi_bindgen/src/macro_metadata/ci.rs @@ -117,10 +117,7 @@ fn add_item_to_ci(iface: &mut ComponentInterface, item: Metadata) -> anyhow::Res module_path: meta.module_path.clone(), name: meta.name.clone(), })?; - iface.add_callback_interface_definition(CallbackInterface::new( - meta.name, - meta.module_path, - )); + iface.add_callback_interface_definition(CallbackInterface::try_from(meta)?); } Metadata::TraitMethod(meta) => { iface.add_trait_method_meta(meta)?; diff --git a/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs index 7be11554a4..1f4e03af47 100644 --- a/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs @@ -29,11 +29,11 @@ pub extern "C" fn {{ cbi.ffi_init_callback().name() }}(callback: uniffi::Foreign #[doc(hidden)] #[derive(Debug)] struct {{ trait_impl }} { - handle: u64 + handle: ::uniffi::Handle } impl {{ trait_impl }} { - fn new(handle: u64) -> Self { + fn new(handle: ::uniffi::Handle) -> Self { Self { handle } } } diff --git a/uniffi_build/Cargo.toml b/uniffi_build/Cargo.toml index 2c3057ded7..acd051ca6d 100644 --- a/uniffi_build/Cargo.toml +++ b/uniffi_build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_build" -version = "0.25.0" +version = "0.25.2" authors = ["Firefox Sync Team "] description = "a multi-language bindings generator for rust (build script helpers)" documentation = "https://mozilla.github.io/uniffi-rs" @@ -13,7 +13,7 @@ keywords = ["ffi", "bindgen"] [dependencies] anyhow = "1" camino = "1.0.8" -uniffi_bindgen = { path = "../uniffi_bindgen", default-features = false, version = "=0.25.0" } +uniffi_bindgen = { path = "../uniffi_bindgen", default-features = false, version = "=0.25.2" } [features] default = [] diff --git a/uniffi_checksum_derive/Cargo.toml b/uniffi_checksum_derive/Cargo.toml index d76b7c418b..b4c748b6ea 100644 --- a/uniffi_checksum_derive/Cargo.toml +++ b/uniffi_checksum_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_checksum_derive" -version = "0.25.0" +version = "0.25.2" authors = ["Firefox Sync Team "] description = "a multi-language bindings generator for rust (checksum custom derive)" documentation = "https://mozilla.github.io/uniffi-rs" diff --git a/uniffi_core/Cargo.toml b/uniffi_core/Cargo.toml index fa50e0c295..a84aef2072 100644 --- a/uniffi_core/Cargo.toml +++ b/uniffi_core/Cargo.toml @@ -4,7 +4,7 @@ description = "a multi-language bindings generator for rust (runtime support cod documentation = "https://mozilla.github.io/uniffi-rs" homepage = "https://mozilla.github.io/uniffi-rs" repository = "https://github.com/mozilla/uniffi-rs" -version = "0.25.0" +version = "0.25.2" authors = ["Firefox Sync Team "] license = "MPL-2.0" edition = "2021" diff --git a/uniffi_core/src/ffi/callbackinterface.rs b/uniffi_core/src/ffi/callbackinterface.rs index 7be66880bb..ebb6fa1092 100644 --- a/uniffi_core/src/ffi/callbackinterface.rs +++ b/uniffi_core/src/ffi/callbackinterface.rs @@ -113,12 +113,13 @@ //! type and then returns to client code. //! -use crate::{ForeignCallback, ForeignCallbackCell, Lift, LiftReturn, RustBuffer}; +use crate::{ForeignCallback, ForeignCallbackCell, Handle, Lift, LiftReturn, RustBuffer}; use std::fmt; -/// The method index used by the Drop trait to communicate to the foreign language side that Rust has finished with it, -/// and it can be deleted from the handle map. +/// Free the foreign callback object. pub const IDX_CALLBACK_FREE: u32 = 0; +/// Clone the foreign callback object and get a new handle. +pub const IDX_CALLBACK_CLONE: u32 = i32::MAX as u32; /// Result of a foreign callback invocation #[repr(i32)] @@ -167,7 +168,7 @@ impl ForeignCallbackInternals { } /// Invoke a callback interface method on the foreign side and return the result - pub fn invoke_callback(&self, handle: u64, method: u32, args: RustBuffer) -> R + pub fn invoke_callback(&self, handle: Handle, method: u32, args: RustBuffer) -> R where R: LiftReturn, { diff --git a/uniffi_core/src/ffi/ffidefault.rs b/uniffi_core/src/ffi/ffidefault.rs index 1f86f6b13b..be5f0dd796 100644 --- a/uniffi_core/src/ffi/ffidefault.rs +++ b/uniffi_core/src/ffi/ffidefault.rs @@ -39,9 +39,9 @@ impl FfiDefault for () { fn ffi_default() {} } -impl FfiDefault for *const std::ffi::c_void { +impl FfiDefault for crate::Handle { fn ffi_default() -> Self { - std::ptr::null() + Self::default() } } @@ -51,12 +51,6 @@ impl FfiDefault for crate::RustBuffer { } } -impl FfiDefault for crate::ForeignExecutorHandle { - fn ffi_default() -> Self { - Self(std::ptr::null()) - } -} - impl FfiDefault for Option { fn ffi_default() -> Self { None diff --git a/uniffi_core/src/ffi/foreigncallbacks.rs b/uniffi_core/src/ffi/foreigncallbacks.rs index ac2463cd8e..1746489e30 100644 --- a/uniffi_core/src/ffi/foreigncallbacks.rs +++ b/uniffi_core/src/ffi/foreigncallbacks.rs @@ -10,7 +10,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; -use crate::{ForeignExecutorHandle, RustBuffer, RustTaskCallback}; +use crate::{Handle, RustBuffer, RustTaskCallback}; /// ForeignCallback is the Rust representation of a foreign language function. /// It is the basis for all callbacks interfaces. It is registered exactly once per callback interface, @@ -30,7 +30,7 @@ use crate::{ForeignExecutorHandle, RustBuffer, RustTaskCallback}; /// * Callbacks return one of the `CallbackResult` values /// Note: The output buffer might still contain 0 bytes of data. pub type ForeignCallback = unsafe extern "C" fn( - handle: u64, + handle: Handle, method: u32, args_data: *const u8, args_len: i32, @@ -50,7 +50,7 @@ pub type ForeignCallback = unsafe extern "C" fn( /// /// The callback should return one of the `ForeignExecutorCallbackResult` values. pub type ForeignExecutorCallback = extern "C" fn( - executor: ForeignExecutorHandle, + executor: Handle, delay: u32, task: Option, task_data: *const (), diff --git a/uniffi_core/src/ffi/foreignexecutor.rs b/uniffi_core/src/ffi/foreignexecutor.rs index 7b1cb9bd80..3f0f826644 100644 --- a/uniffi_core/src/ffi/foreignexecutor.rs +++ b/uniffi_core/src/ffi/foreignexecutor.rs @@ -6,20 +6,7 @@ use std::panic; -use crate::{ForeignExecutorCallback, ForeignExecutorCallbackCell}; - -/// Opaque handle for a foreign task executor. -/// -/// Foreign code can either use an actual pointer, or use an integer value casted to it. -#[repr(transparent)] -#[derive(Clone, Copy, Debug)] -pub struct ForeignExecutorHandle(pub(crate) *const ()); - -// Implement Send + Sync for `ForeignExecutor`. The foreign bindings code is responsible for -// making the `ForeignExecutorCallback` thread-safe. -unsafe impl Send for ForeignExecutorHandle {} - -unsafe impl Sync for ForeignExecutorHandle {} +use crate::{ForeignExecutorCallback, ForeignExecutorCallbackCell, Handle}; /// Result code returned by `ForeignExecutorCallback` #[repr(i8)] @@ -95,11 +82,11 @@ pub fn foreign_executor_callback_set(callback: ForeignExecutorCallback) { /// Schedule Rust calls using a foreign executor #[derive(Debug)] pub struct ForeignExecutor { - pub(crate) handle: ForeignExecutorHandle, + pub(crate) handle: Handle, } impl ForeignExecutor { - pub fn new(executor: ForeignExecutorHandle) -> Self { + pub fn new(executor: Handle) -> Self { Self { handle: executor } } @@ -162,11 +149,11 @@ impl ForeignExecutor { /// Low-level schedule interface /// /// When using this function, take care to ensure that the `ForeignExecutor` that holds the -/// `ForeignExecutorHandle` has not been dropped. +/// `Handle` has not been dropped. /// /// Returns true if the callback was successfully scheduled pub(crate) fn schedule_raw( - handle: ForeignExecutorHandle, + handle: Handle, delay: u32, callback: RustTaskCallback, data: *const (), @@ -219,6 +206,7 @@ pub use test::MockEventLoop; #[cfg(test)] mod test { use super::*; + use crate::HandleAlloc; use std::{ future::Future, pin::Pin, @@ -264,11 +252,9 @@ mod test { }) } - /// Create a new ForeignExecutorHandle - pub fn new_handle(self: &Arc) -> ForeignExecutorHandle { - // To keep the memory management simple, we simply leak an arc reference for this. We - // only create a handful of these in the tests so there's no need for proper cleanup. - ForeignExecutorHandle(Arc::into_raw(Arc::clone(self)) as *const ()) + /// Create a new Handle + pub fn new_handle(self: &Arc) -> Handle { + >::new_handle(Arc::clone(self)) } pub fn new_executor(self: &Arc) -> ForeignExecutor { @@ -314,13 +300,13 @@ mod test { // `ForeignExecutorCallback` that we install for testing extern "C" fn mock_executor_callback( - handle: ForeignExecutorHandle, + handle: Handle, delay: u32, task: Option, task_data: *const (), ) -> i8 { - let eventloop = handle.0 as *const MockEventLoop; - let mut inner = unsafe { (*eventloop).inner.lock().unwrap() }; + let eventloop = >::get_arc(handle); + let mut inner = eventloop.inner.lock().unwrap(); if inner.is_shutdown { ForeignExecutorCallbackResult::Cancelled as i8 } else { diff --git a/uniffi_core/src/ffi/handle.rs b/uniffi_core/src/ffi/handle.rs new file mode 100644 index 0000000000..0baa99fd62 --- /dev/null +++ b/uniffi_core/src/ffi/handle.rs @@ -0,0 +1,59 @@ +/* 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/. */ + +/// Object handle +/// +/// Handles opaque `u64` values used to pass objects across the FFI, both for objects implemented in +/// Rust and ones implemented in the foreign language. +/// +/// Rust handles are generated by leaking a raw pointer +/// Foreign handles are generated with a handle map that only generates odd values. +/// For all currently supported architectures and hopefully any ones we add in the future: +/// * 0 is an invalid value. +/// * The lowest bit will always be set for foreign handles and never set for Rust ones (since the +/// leaked pointer will be aligned). +/// +/// Foreign handles for internal bindings languages look like this: +/// * Bits 0-48 are the map key. Keys are always odd, so the lowest bit is always set. +/// * Bits 48-64 are the map identifier, used to detect using a handle with the wrong map +/// +/// External bindings authors are free to use any form they want for their handles, as long the lowest bit is set. +/// In particular, they don't need to implement the map identifier. +/// +/// Rust handles are mainly managed is through the [crate::HandleAlloc] trait. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct Handle(u64); + +impl Handle { + pub fn from_pointer(ptr: *const T) -> Self { + Self(ptr as u64) + } + + pub fn as_pointer(&self) -> *const T { + self.0 as *const T + } + + pub fn from_raw(raw: u64) -> Option { + if raw == 0 { + None + } else { + Some(Self(raw)) + } + } + + pub fn from_raw_unchecked(raw: u64) -> Self { + Self(raw) + } + + pub fn as_raw(&self) -> u64 { + self.0 + } + + /// Check if a handle belongs to the foreign side of the FFI + pub fn is_foreign(&self) -> bool { + // Foreign handles have the lowest bit set + (self.0 & 1) == 1 + } +} diff --git a/uniffi_core/src/ffi/mod.rs b/uniffi_core/src/ffi/mod.rs index b606323297..f7176671bf 100644 --- a/uniffi_core/src/ffi/mod.rs +++ b/uniffi_core/src/ffi/mod.rs @@ -9,6 +9,7 @@ pub mod ffidefault; pub mod foreignbytes; pub mod foreigncallbacks; pub mod foreignexecutor; +pub mod handle; pub mod rustbuffer; pub mod rustcalls; pub mod rustfuture; @@ -18,6 +19,7 @@ pub use ffidefault::FfiDefault; pub use foreignbytes::*; pub use foreigncallbacks::*; pub use foreignexecutor::*; +pub use handle::*; pub use rustbuffer::*; pub use rustcalls::*; pub use rustfuture::*; diff --git a/uniffi_core/src/ffi/rustfuture.rs b/uniffi_core/src/ffi/rustfuture.rs deleted file mode 100644 index 0c1a24174b..0000000000 --- a/uniffi_core/src/ffi/rustfuture.rs +++ /dev/null @@ -1,735 +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/. */ - -//! [`RustFuture`] represents a [`Future`] that can be sent to the foreign code over FFI. -//! -//! This type is not instantiated directly, but via the procedural macros, such as `#[uniffi::export]`. -//! -//! # The big picture -//! -//! We implement async foreign functions using a simplified version of the Future API: -//! -//! 0. At startup, register a [RustFutureContinuationCallback] by calling -//! rust_future_continuation_callback_set. -//! 1. Call the scaffolding function to get a [RustFutureHandle] -//! 2a. In a loop: -//! - Call [rust_future_poll] -//! - Suspend the function until the [rust_future_poll] continuation function is called -//! - If the continuation was function was called with [RustFuturePoll::Ready], then break -//! otherwise continue. -//! 2b. If the async function is cancelled, then call [rust_future_cancel]. This causes the -//! continuation function to be called with [RustFuturePoll::Ready] and the [RustFuture] to -//! enter a cancelled state. -//! 3. Call [rust_future_complete] to get the result of the future. -//! 4. Call [rust_future_free] to free the future, ideally in a finally block. This: -//! - Releases any resources held by the future -//! - Calls any continuation callbacks that have not been called yet -//! -//! Note: Technically, the foreign code calls the scaffolding versions of the `rust_future_*` -//! functions. These are generated by the scaffolding macro, specially prefixed, and extern "C", -//! and manually monomorphized in the case of [rust_future_complete]. See -//! `uniffi_macros/src/setup_scaffolding.rs` for details. -//! -//! ## How does `Future` work exactly? -//! -//! A [`Future`] in Rust does nothing. When calling an async function, it just -//! returns a `Future` but nothing has happened yet. To start the computation, -//! the future must be polled. It returns [`Poll::Ready(r)`][`Poll::Ready`] if -//! the result is ready, [`Poll::Pending`] otherwise. `Poll::Pending` basically -//! means: -//! -//! > Please, try to poll me later, maybe the result will be ready! -//! -//! This model is very different than what other languages do, but it can actually -//! be translated quite easily, fortunately for us! -//! -//! But… wait a minute… who is responsible to poll the `Future` if a `Future` does -//! nothing? Well, it's _the executor_. The executor is responsible _to drive_ the -//! `Future`: that's where they are polled. -//! -//! But… wait another minute… how does the executor know when to poll a [`Future`]? -//! Does it poll them randomly in an endless loop? Well, no, actually it depends -//! on the executor! A well-designed `Future` and executor work as follows. -//! Normally, when [`Future::poll`] is called, a [`Context`] argument is -//! passed to it. It contains a [`Waker`]. The [`Waker`] is built on top of a -//! [`RawWaker`] which implements whatever is necessary. Usually, a waker will -//! signal the executor to poll a particular `Future`. A `Future` will clone -//! or pass-by-ref the waker to somewhere, as a callback, a completion, a -//! function, or anything, to the system that is responsible to notify when a -//! task is completed. So, to recap, the waker is _not_ responsible for waking the -//! `Future`, it _is_ responsible for _signaling_ the executor that a particular -//! `Future` should be polled again. That's why the documentation of -//! [`Poll::Pending`] specifies: -//! -//! > When a function returns `Pending`, the function must also ensure that the -//! > current task is scheduled to be awoken when progress can be made. -//! -//! “awakening” is done by using the `Waker`. -//! -//! [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html -//! [`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll -//! [`Pol::Ready`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready -//! [`Poll::Pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending -//! [`Context`]: https://doc.rust-lang.org/std/task/struct.Context.html -//! [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html -//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html - -use std::{ - future::Future, - marker::PhantomData, - mem, - ops::Deref, - panic, - pin::Pin, - sync::{Arc, Mutex}, - task::{Context, Poll, Wake}, -}; - -use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus}; - -/// Result code for [rust_future_poll]. This is passed to the continuation function. -#[repr(i8)] -#[derive(Debug, PartialEq, Eq)] -pub enum RustFuturePoll { - /// The future is ready and is waiting for [rust_future_complete] to be called - Ready = 0, - /// The future might be ready and [rust_future_poll] should be called again - MaybeReady = 1, -} - -/// Foreign callback that's passed to [rust_future_poll] -/// -/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again -/// to continue progress on the future. -pub type RustFutureContinuationCallback = extern "C" fn(callback_data: *const (), RustFuturePoll); - -/// Opaque handle for a Rust future that's stored by the foreign language code -#[repr(transparent)] -pub struct RustFutureHandle(*const ()); - -// === Public FFI API === - -/// Create a new [RustFutureHandle] -/// -/// For each exported async function, UniFFI will create a scaffolding function that uses this to -/// create the [RustFutureHandle] to pass to the foreign code. -pub fn rust_future_new(future: F, tag: UT) -> RustFutureHandle -where - // F is the future type returned by the exported async function. It needs to be Send + `static - // since it will move between threads for an indeterminate amount of time as the foreign - // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`, - // since we synchronize all access to the values. - F: Future + Send + 'static, - // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send + - // 'static for the same reason as F. - T: LowerReturn + Send + 'static, - // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. - UT: Send + 'static, -{ - // Create a RustFuture and coerce to `Arc`, which is what we use to - // implement the FFI - let future_ffi = RustFuture::new(future, tag) as Arc>; - // Box the Arc, to convert the wide pointer into a normal sized pointer so that we can pass it - // to the foreign code. - let boxed_ffi = Box::new(future_ffi); - // We can now create a RustFutureHandle - RustFutureHandle(Box::into_raw(boxed_ffi) as *mut ()) -} - -/// Poll a Rust future -/// -/// When the future is ready to progress the continuation will be called with the `data` value and -/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called -/// exactly once. -/// -/// # Safety -/// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_poll( - handle: RustFutureHandle, - callback: RustFutureContinuationCallback, - data: *const (), -) { - let future = &*(handle.0 as *mut Arc>); - future.clone().ffi_poll(callback, data) -} - -/// Cancel a Rust future -/// -/// Any current and future continuations will be immediately called with RustFuturePoll::Ready. -/// -/// This is needed for languages like Swift, which continuation to wait for the continuation to be -/// called when tasks are cancelled. -/// -/// # Safety -/// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_cancel(handle: RustFutureHandle) { - let future = &*(handle.0 as *mut Arc>); - future.clone().ffi_cancel() -} - -/// Complete a Rust future -/// -/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for -/// each supported FFI type. -/// -/// # Safety -/// -/// - The [RustFutureHandle] must not previously have been passed to [rust_future_free] -/// - The `T` param must correctly correspond to the [rust_future_new] call. It must -/// be `>::ReturnType` -pub unsafe fn rust_future_complete( - handle: RustFutureHandle, - out_status: &mut RustCallStatus, -) -> ReturnType { - let future = &*(handle.0 as *mut Arc>); - future.ffi_complete(out_status) -} - -/// Free a Rust future, dropping the strong reference and releasing all references held by the -/// future. -/// -/// # Safety -/// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_free(handle: RustFutureHandle) { - let future = Box::from_raw(handle.0 as *mut Arc>); - future.ffi_free() -} - -/// Thread-safe storage for [RustFutureContinuationCallback] data -/// -/// The basic guarantee is that all data pointers passed in are passed out exactly once to the -/// foreign continuation callback. This enables us to uphold the [rust_future_poll] guarantee. -/// -/// [ContinuationDataCell] also tracks cancellation, which is closely tied to continuation data. -#[derive(Debug)] -enum ContinuationDataCell { - /// No continuations set, neither wake() nor cancel() called. - Empty, - /// `wake()` was called when there was no continuation set. The next time `store` is called, - /// the continuation should be immediately invoked with `RustFuturePoll::MaybeReady` - Waked, - /// The future has been cancelled, any future `store` calls should immediately result in the - /// continuation being called with `RustFuturePoll::Ready`. - Cancelled, - /// Continuation set, the next time `wake()` is called is called, we should invoke it. - Set(RustFutureContinuationCallback, *const ()), -} - -impl ContinuationDataCell { - fn new() -> Self { - Self::Empty - } - - /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or - /// `Cancelled` state, call the continuation immediately with the data. - fn store(&mut self, callback: RustFutureContinuationCallback, data: *const ()) { - match self { - Self::Empty => *self = Self::Set(callback, data), - Self::Set(old_callback, old_data) => { - log::error!( - "store: observed `Self::Set` state. Is poll() being called from multiple threads at once?" - ); - old_callback(*old_data, RustFuturePoll::Ready); - *self = Self::Set(callback, data); - } - Self::Waked => { - *self = Self::Empty; - callback(data, RustFuturePoll::MaybeReady); - } - Self::Cancelled => { - callback(data, RustFuturePoll::Ready); - } - } - } - - fn wake(&mut self) { - match self { - // If we had a continuation set, then call it and transition to the `Empty` state. - Self::Set(callback, old_data) => { - let old_data = *old_data; - let callback = *callback; - *self = Self::Empty; - callback(old_data, RustFuturePoll::MaybeReady); - } - // If we were in the `Empty` state, then transition to `Waked`. The next time `store` - // is called, we will immediately call the continuation. - Self::Empty => *self = Self::Waked, - // This is a no-op if we were in the `Cancelled` or `Waked` state. - _ => (), - } - } - - fn cancel(&mut self) { - if let Self::Set(callback, old_data) = mem::replace(self, Self::Cancelled) { - callback(old_data, RustFuturePoll::Ready); - } - } - - fn is_cancelled(&self) -> bool { - matches!(self, Self::Cancelled) - } -} - -// ContinuationDataCell is Send + Sync as long we handle the *const () pointer correctly - -unsafe impl Send for ContinuationDataCell {} -unsafe impl Sync for ContinuationDataCell {} - -/// Wraps the actual future we're polling -struct WrappedFuture -where - // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, - T: LowerReturn + Send + 'static, - UT: Send + 'static, -{ - // Note: this could be a single enum, but that would make it easy to mess up the future pinning - // guarantee. For example you might want to call `std::mem::take()` to try to get the result, - // but if the future happened to be stored that would move and break all internal references. - future: Option, - result: Option>, -} - -impl WrappedFuture -where - // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, - T: LowerReturn + Send + 'static, - UT: Send + 'static, -{ - fn new(future: F) -> Self { - Self { - future: Some(future), - result: None, - } - } - - // Poll the future and check if it's ready or not - fn poll(&mut self, context: &mut Context<'_>) -> bool { - if self.result.is_some() { - true - } else if let Some(future) = &mut self.future { - // SAFETY: We can call Pin::new_unchecked because: - // - This is the only time we get a &mut to `self.future` - // - We never poll the future after it's moved (for example by using take()) - // - We never move RustFuture, which contains us. - // - RustFuture is private to this module so no other code can move it. - let pinned = unsafe { Pin::new_unchecked(future) }; - // Run the poll and lift the result if it's ready - let mut out_status = RustCallStatus::default(); - let result: Option> = rust_call_with_out_status( - &mut out_status, - // This closure uses a `&mut F` value, which means it's not UnwindSafe by - // default. If the future panics, it may be in an invalid state. - // - // However, we can safely use `AssertUnwindSafe` since a panic will lead the `None` - // case below and we will never poll the future again. - panic::AssertUnwindSafe(|| match pinned.poll(context) { - Poll::Pending => Ok(Poll::Pending), - Poll::Ready(v) => T::lower_return(v).map(Poll::Ready), - }), - ); - match result { - Some(Poll::Pending) => false, - Some(Poll::Ready(v)) => { - self.future = None; - self.result = Some(Ok(v)); - true - } - None => { - self.future = None; - self.result = Some(Err(out_status)); - true - } - } - } else { - log::error!("poll with neither future nor result set"); - true - } - } - - fn complete(&mut self, out_status: &mut RustCallStatus) -> T::ReturnType { - let mut return_value = T::ReturnType::ffi_default(); - match self.result.take() { - Some(Ok(v)) => return_value = v, - Some(Err(call_status)) => *out_status = call_status, - None => *out_status = RustCallStatus::cancelled(), - } - self.free(); - return_value - } - - fn free(&mut self) { - self.future = None; - self.result = None; - } -} - -// If F and T are Send, then WrappedFuture is too -// -// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising -// that we will treat the raw pointer properly, for example by not returning it twice. -unsafe impl Send for WrappedFuture -where - // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, - T: LowerReturn + Send + 'static, - UT: Send + 'static, -{ -} - -/// Future that the foreign code is awaiting -struct RustFuture -where - // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, - T: LowerReturn + Send + 'static, - UT: Send + 'static, -{ - // This Mutex should never block if our code is working correctly, since there should not be - // multiple threads calling [Self::poll] and/or [Self::complete] at the same time. - future: Mutex>, - continuation_data: Mutex, - // UT is used as the generic parameter for [LowerReturn]. - // Let's model this with PhantomData as a function that inputs a UT value. - _phantom: PhantomData ()>, -} - -impl RustFuture -where - // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, - T: LowerReturn + Send + 'static, - UT: Send + 'static, -{ - fn new(future: F, _tag: UT) -> Arc { - Arc::new(Self { - future: Mutex::new(WrappedFuture::new(future)), - continuation_data: Mutex::new(ContinuationDataCell::new()), - _phantom: PhantomData, - }) - } - - fn poll(self: Arc, callback: RustFutureContinuationCallback, data: *const ()) { - let ready = self.is_cancelled() || { - let mut locked = self.future.lock().unwrap(); - let waker: std::task::Waker = Arc::clone(&self).into(); - locked.poll(&mut Context::from_waker(&waker)) - }; - if ready { - callback(data, RustFuturePoll::Ready) - } else { - self.continuation_data.lock().unwrap().store(callback, data); - } - } - - fn is_cancelled(&self) -> bool { - self.continuation_data.lock().unwrap().is_cancelled() - } - - fn wake(&self) { - self.continuation_data.lock().unwrap().wake(); - } - - fn cancel(&self) { - self.continuation_data.lock().unwrap().cancel(); - } - - fn complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { - self.future.lock().unwrap().complete(call_status) - } - - fn free(self: Arc) { - // Call cancel() to send any leftover data to the continuation callback - self.continuation_data.lock().unwrap().cancel(); - // Ensure we drop our inner future, releasing all held references - self.future.lock().unwrap().free(); - } -} - -impl Wake for RustFuture -where - // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, - T: LowerReturn + Send + 'static, - UT: Send + 'static, -{ - fn wake(self: Arc) { - self.deref().wake() - } - - fn wake_by_ref(self: &Arc) { - self.deref().wake() - } -} - -/// RustFuture FFI trait. This allows `Arc>` to be cast to -/// `Arc>`, which is needed to implement the public FFI API. In particular, this -/// allows you to use RustFuture functionality without knowing the concrete Future type, which is -/// unnamable. -/// -/// This is parametrized on the ReturnType rather than the `T` directly, to reduce the number of -/// scaffolding functions we need to generate. If it was parametrized on `T`, then we would need -/// to create a poll, cancel, complete, and free scaffolding function for each exported async -/// function. That would add ~1kb binary size per exported function based on a quick estimate on a -/// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and -/// only create those functions for each of the 13 possible FFI return types. -#[doc(hidden)] -trait RustFutureFfi { - fn ffi_poll(self: Arc, callback: RustFutureContinuationCallback, data: *const ()); - fn ffi_cancel(&self); - fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType; - fn ffi_free(self: Arc); -} - -impl RustFutureFfi for RustFuture -where - // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, - T: LowerReturn + Send + 'static, - UT: Send + 'static, -{ - fn ffi_poll(self: Arc, callback: RustFutureContinuationCallback, data: *const ()) { - self.poll(callback, data) - } - - fn ffi_cancel(&self) { - self.cancel() - } - - fn ffi_complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { - self.complete(call_status) - } - - fn ffi_free(self: Arc) { - self.free(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode}; - use once_cell::sync::OnceCell; - use std::task::Waker; - - // Sender/Receiver pair that we use for testing - struct Channel { - result: Option>, - waker: Option, - } - - struct Sender(Arc>); - - impl Sender { - fn wake(&self) { - let inner = self.0.lock().unwrap(); - if let Some(waker) = &inner.waker { - waker.wake_by_ref(); - } - } - - fn send(&self, value: Result) { - let mut inner = self.0.lock().unwrap(); - if inner.result.replace(value).is_some() { - panic!("value already sent"); - } - if let Some(waker) = &inner.waker { - waker.wake_by_ref(); - } - } - } - - struct Receiver(Arc>); - - impl Future for Receiver { - type Output = Result; - - fn poll( - self: Pin<&mut Self>, - context: &mut Context<'_>, - ) -> Poll> { - let mut inner = self.0.lock().unwrap(); - match &inner.result { - Some(v) => Poll::Ready(v.clone()), - None => { - inner.waker = Some(context.waker().clone()); - Poll::Pending - } - } - } - } - - // Create a sender and rust future that we can use for testing - fn channel() -> (Sender, Arc>) { - let channel = Arc::new(Mutex::new(Channel { - result: None, - waker: None, - })); - let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag); - (Sender(channel), rust_future) - } - - /// Poll a Rust future and get an OnceCell that's set when the continuation is called - fn poll(rust_future: &Arc>) -> Arc> { - let cell = Arc::new(OnceCell::new()); - let cell_ptr = Arc::into_raw(cell.clone()) as *const (); - rust_future.clone().ffi_poll(poll_continuation, cell_ptr); - cell - } - - extern "C" fn poll_continuation(data: *const (), code: RustFuturePoll) { - let cell = unsafe { Arc::from_raw(data as *const OnceCell) }; - cell.set(code).expect("Error setting OnceCell"); - } - - fn complete(rust_future: Arc>) -> (RustBuffer, RustCallStatus) { - let mut out_status_code = RustCallStatus::default(); - let return_value = rust_future.ffi_complete(&mut out_status_code); - (return_value, out_status_code) - } - - #[test] - fn test_success() { - let (sender, rust_future) = channel(); - - // Test polling the rust future before it's ready - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), None); - sender.wake(); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); - - // Test polling the rust future when it's ready - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), None); - sender.send(Ok("All done".into())); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); - - // Future polls should immediately return ready - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - - // Complete the future - let (return_buf, call_status) = complete(rust_future); - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!( - >::try_lift(return_buf).unwrap(), - "All done" - ); - } - - #[test] - fn test_error() { - let (sender, rust_future) = channel(); - - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), None); - sender.send(Err("Something went wrong".into())); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); - - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - - let (_, call_status) = complete(rust_future); - assert_eq!(call_status.code, RustCallStatusCode::Error); - unsafe { - assert_eq!( - >::try_lift_from_rust_buffer( - call_status.error_buf.assume_init() - ) - .unwrap(), - TestError::from("Something went wrong"), - ) - } - } - - // Once `complete` is called, the inner future should be released, even if wakers still hold a - // reference to the RustFuture - #[test] - fn test_cancel() { - let (_sender, rust_future) = channel(); - - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), None); - rust_future.ffi_cancel(); - // Cancellation should immediately invoke the callback with RustFuturePoll::Ready - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - - // Future polls should immediately invoke the callback with RustFuturePoll::Ready - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - - let (_, call_status) = complete(rust_future); - assert_eq!(call_status.code, RustCallStatusCode::Cancelled); - } - - // Once `free` is called, the inner future should be released, even if wakers still hold a - // reference to the RustFuture - #[test] - fn test_release_future() { - let (sender, rust_future) = channel(); - // Create a weak reference to the channel to use to check if rust_future has dropped its - // future. - let channel_weak = Arc::downgrade(&sender.0); - drop(sender); - // Create an extra ref to rust_future, simulating a waker that still holds a reference to - // it - let rust_future2 = rust_future.clone(); - - // Complete the rust future - rust_future.ffi_free(); - // Even though rust_future is still alive, the channel shouldn't be - assert!(Arc::strong_count(&rust_future2) > 0); - assert_eq!(channel_weak.strong_count(), 0); - assert!(channel_weak.upgrade().is_none()); - } - - // If `free` is called with a continuation still stored, we should call it them then. - // - // This shouldn't happen in practice, but it seems like good defensive programming - #[test] - fn test_complete_with_stored_continuation() { - let (_sender, rust_future) = channel(); - - let continuation_result = poll(&rust_future); - rust_future.ffi_free(); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - } - - // Test what happens if we see a `wake()` call while we're polling the future. This can - // happen, for example, with futures that are handled by a tokio thread pool. We should - // schedule another poll of the future in this case. - #[test] - fn test_wake_during_poll() { - let mut first_time = true; - let future = std::future::poll_fn(move |ctx| { - if first_time { - first_time = false; - // Wake the future while we are in the middle of polling it - ctx.waker().clone().wake(); - Poll::Pending - } else { - // The second time we're polled, we're ready - Poll::Ready("All done".to_owned()) - } - }); - let rust_future: Arc> = - RustFuture::new(future, crate::UniFfiTag); - let continuation_result = poll(&rust_future); - // The continuation function should called immediately - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); - // A second poll should finish the future - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - let (return_buf, call_status) = complete(rust_future); - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!( - >::try_lift(return_buf).unwrap(), - "All done" - ); - } -} diff --git a/uniffi_core/src/ffi/rustfuture/future.rs b/uniffi_core/src/ffi/rustfuture/future.rs new file mode 100644 index 0000000000..861751d3db --- /dev/null +++ b/uniffi_core/src/ffi/rustfuture/future.rs @@ -0,0 +1,326 @@ +/* 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/. */ + +//! [`RustFuture`] represents a [`Future`] that can be sent to the foreign code over FFI. +//! +//! This type is not instantiated directly, but via the procedural macros, such as `#[uniffi::export]`. +//! +//! # The big picture +//! +//! We implement async foreign functions using a simplified version of the Future API: +//! +//! 0. At startup, register a [RustFutureContinuationCallback] by calling +//! rust_future_continuation_callback_set. +//! 1. Call the scaffolding function to get a [Handle] +//! 2a. In a loop: +//! - Call [rust_future_poll] +//! - Suspend the function until the [rust_future_poll] continuation function is called +//! - If the continuation was function was called with [RustFuturePoll::Ready], then break +//! otherwise continue. +//! 2b. If the async function is cancelled, then call [rust_future_cancel]. This causes the +//! continuation function to be called with [RustFuturePoll::Ready] and the [RustFuture] to +//! enter a cancelled state. +//! 3. Call [rust_future_complete] to get the result of the future. +//! 4. Call [rust_future_free] to free the future, ideally in a finally block. This: +//! - Releases any resources held by the future +//! - Calls any continuation callbacks that have not been called yet +//! +//! Note: Technically, the foreign code calls the scaffolding versions of the `rust_future_*` +//! functions. These are generated by the scaffolding macro, specially prefixed, and extern "C", +//! and manually monomorphized in the case of [rust_future_complete]. See +//! `uniffi_macros/src/setup_scaffolding.rs` for details. +//! +//! ## How does `Future` work exactly? +//! +//! A [`Future`] in Rust does nothing. When calling an async function, it just +//! returns a `Future` but nothing has happened yet. To start the computation, +//! the future must be polled. It returns [`Poll::Ready(r)`][`Poll::Ready`] if +//! the result is ready, [`Poll::Pending`] otherwise. `Poll::Pending` basically +//! means: +//! +//! > Please, try to poll me later, maybe the result will be ready! +//! +//! This model is very different than what other languages do, but it can actually +//! be translated quite easily, fortunately for us! +//! +//! But… wait a minute… who is responsible to poll the `Future` if a `Future` does +//! nothing? Well, it's _the executor_. The executor is responsible _to drive_ the +//! `Future`: that's where they are polled. +//! +//! But… wait another minute… how does the executor know when to poll a [`Future`]? +//! Does it poll them randomly in an endless loop? Well, no, actually it depends +//! on the executor! A well-designed `Future` and executor work as follows. +//! Normally, when [`Future::poll`] is called, a [`Context`] argument is +//! passed to it. It contains a [`Waker`]. The [`Waker`] is built on top of a +//! [`RawWaker`] which implements whatever is necessary. Usually, a waker will +//! signal the executor to poll a particular `Future`. A `Future` will clone +//! or pass-by-ref the waker to somewhere, as a callback, a completion, a +//! function, or anything, to the system that is responsible to notify when a +//! task is completed. So, to recap, the waker is _not_ responsible for waking the +//! `Future`, it _is_ responsible for _signaling_ the executor that a particular +//! `Future` should be polled again. That's why the documentation of +//! [`Poll::Pending`] specifies: +//! +//! > When a function returns `Pending`, the function must also ensure that the +//! > current task is scheduled to be awoken when progress can be made. +//! +//! “awakening” is done by using the `Waker`. +//! +//! [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html +//! [`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll +//! [`Pol::Ready`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready +//! [`Poll::Pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending +//! [`Context`]: https://doc.rust-lang.org/std/task/struct.Context.html +//! [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html +//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html + +use std::{ + future::Future, + marker::PhantomData, + ops::Deref, + panic, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Wake}, +}; + +use super::{RustFutureContinuationCallback, RustFuturePoll, Scheduler}; +use crate::{rust_call_with_out_status, FfiDefault, Handle, LowerReturn, RustCallStatus}; + +/// Wraps the actual future we're polling +struct WrappedFuture +where + // See the [RustFuture] struct for an explanation of these trait bounds + F: Future + Send + 'static, + T: LowerReturn + Send + 'static, + UT: Send + 'static, +{ + // Note: this could be a single enum, but that would make it easy to mess up the future pinning + // guarantee. For example you might want to call `std::mem::take()` to try to get the result, + // but if the future happened to be stored that would move and break all internal references. + future: Option, + result: Option>, +} + +impl WrappedFuture +where + // See the [RustFuture] struct for an explanation of these trait bounds + F: Future + Send + 'static, + T: LowerReturn + Send + 'static, + UT: Send + 'static, +{ + fn new(future: F) -> Self { + Self { + future: Some(future), + result: None, + } + } + + // Poll the future and check if it's ready or not + fn poll(&mut self, context: &mut Context<'_>) -> bool { + if self.result.is_some() { + true + } else if let Some(future) = &mut self.future { + // SAFETY: We can call Pin::new_unchecked because: + // - This is the only time we get a &mut to `self.future` + // - We never poll the future after it's moved (for example by using take()) + // - We never move RustFuture, which contains us. + // - RustFuture is private to this module so no other code can move it. + let pinned = unsafe { Pin::new_unchecked(future) }; + // Run the poll and lift the result if it's ready + let mut out_status = RustCallStatus::default(); + let result: Option> = rust_call_with_out_status( + &mut out_status, + // This closure uses a `&mut F` value, which means it's not UnwindSafe by + // default. If the future panics, it may be in an invalid state. + // + // However, we can safely use `AssertUnwindSafe` since a panic will lead the `None` + // case below and we will never poll the future again. + panic::AssertUnwindSafe(|| match pinned.poll(context) { + Poll::Pending => Ok(Poll::Pending), + Poll::Ready(v) => T::lower_return(v).map(Poll::Ready), + }), + ); + match result { + Some(Poll::Pending) => false, + Some(Poll::Ready(v)) => { + self.future = None; + self.result = Some(Ok(v)); + true + } + None => { + self.future = None; + self.result = Some(Err(out_status)); + true + } + } + } else { + log::error!("poll with neither future nor result set"); + true + } + } + + fn complete(&mut self, out_status: &mut RustCallStatus) -> T::ReturnType { + let mut return_value = T::ReturnType::ffi_default(); + match self.result.take() { + Some(Ok(v)) => return_value = v, + Some(Err(call_status)) => *out_status = call_status, + None => *out_status = RustCallStatus::cancelled(), + } + self.free(); + return_value + } + + fn free(&mut self) { + self.future = None; + self.result = None; + } +} + +// If F and T are Send, then WrappedFuture is too +// +// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising +// that we will treat the raw pointer properly, for example by not returning it twice. +unsafe impl Send for WrappedFuture +where + // See the [RustFuture] struct for an explanation of these trait bounds + F: Future + Send + 'static, + T: LowerReturn + Send + 'static, + UT: Send + 'static, +{ +} + +/// Future that the foreign code is awaiting +pub(super) struct RustFuture +where + // F is the future type returned by the exported async function. It needs to be Send + `static + // since it will move between threads for an indeterminate amount of time as the foreign + // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`, + // since we synchronize all access to the values. + F: Future + Send + 'static, + // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send + + // 'static for the same reason as F. + T: LowerReturn + Send + 'static, + // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. + UT: Send + 'static, +{ + // This Mutex should never block if our code is working correctly, since there should not be + // multiple threads calling [Self::poll] and/or [Self::complete] at the same time. + future: Mutex>, + continuation_data: Mutex, + // UT is used as the generic parameter for [LowerReturn]. + // Let's model this with PhantomData as a function that inputs a UT value. + _phantom: PhantomData ()>, +} + +impl RustFuture +where + // See the [RustFuture] struct for an explanation of these trait bounds + F: Future + Send + 'static, + T: LowerReturn + Send + 'static, + UT: Send + 'static, +{ + pub(super) fn new(future: F, _tag: UT) -> Arc { + Arc::new(Self { + future: Mutex::new(WrappedFuture::new(future)), + continuation_data: Mutex::new(Scheduler::new()), + _phantom: PhantomData, + }) + } + + pub(super) fn poll(self: Arc, callback: RustFutureContinuationCallback, data: Handle) { + let ready = self.is_cancelled() || { + let mut locked = self.future.lock().unwrap(); + let waker: std::task::Waker = Arc::clone(&self).into(); + locked.poll(&mut Context::from_waker(&waker)) + }; + if ready { + callback(data, RustFuturePoll::Ready) + } else { + self.continuation_data.lock().unwrap().store(callback, data); + } + } + + pub(super) fn is_cancelled(&self) -> bool { + self.continuation_data.lock().unwrap().is_cancelled() + } + + pub(super) fn wake(&self) { + self.continuation_data.lock().unwrap().wake(); + } + + pub(super) fn cancel(&self) { + self.continuation_data.lock().unwrap().cancel(); + } + + pub(super) fn complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { + self.future.lock().unwrap().complete(call_status) + } + + pub(super) fn free(self: Arc) { + // Call cancel() to send any leftover data to the continuation callback + self.continuation_data.lock().unwrap().cancel(); + // Ensure we drop our inner future, releasing all held references + self.future.lock().unwrap().free(); + } +} + +impl Wake for RustFuture +where + // See the [RustFuture] struct for an explanation of these trait bounds + F: Future + Send + 'static, + T: LowerReturn + Send + 'static, + UT: Send + 'static, +{ + fn wake(self: Arc) { + self.deref().wake() + } + + fn wake_by_ref(self: &Arc) { + self.deref().wake() + } +} + +/// RustFuture FFI trait. This allows `Arc>` to be cast to +/// `Arc>`, which is needed to implement the public FFI API. In particular, this +/// allows you to use RustFuture functionality without knowing the concrete Future type, which is +/// unnamable. +/// +/// This is parametrized on the ReturnType rather than the `T` directly, to reduce the number of +/// scaffolding functions we need to generate. If it was parametrized on `T`, then we would need +/// to create a poll, cancel, complete, and free scaffolding function for each exported async +/// function. That would add ~1kb binary size per exported function based on a quick estimate on a +/// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and +/// only create those functions for each of the 13 possible FFI return types. +#[doc(hidden)] +pub trait RustFutureFfi: Send + Sync { + fn ffi_poll(self: Arc, callback: RustFutureContinuationCallback, data: Handle); + fn ffi_cancel(&self); + fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType; + fn ffi_free(self: Arc); +} + +impl RustFutureFfi for RustFuture +where + // See the [RustFuture] struct for an explanation of these trait bounds + F: Future + Send + 'static, + T: LowerReturn + Send + 'static, + UT: Send + 'static, +{ + fn ffi_poll(self: Arc, callback: RustFutureContinuationCallback, data: Handle) { + self.poll(callback, data) + } + + fn ffi_cancel(&self) { + self.cancel() + } + + fn ffi_complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { + self.complete(call_status) + } + + fn ffi_free(self: Arc) { + self.free(); + } +} diff --git a/uniffi_core/src/ffi/rustfuture/mod.rs b/uniffi_core/src/ffi/rustfuture/mod.rs new file mode 100644 index 0000000000..2ed7218b45 --- /dev/null +++ b/uniffi_core/src/ffi/rustfuture/mod.rs @@ -0,0 +1,137 @@ +/* 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 std::{future::Future, sync::Arc}; + +mod future; +mod scheduler; +use future::*; +use scheduler::*; + +#[cfg(test)] +mod tests; + +use crate::{derive_ffi_traits, Handle, HandleAlloc, LowerReturn, RustCallStatus}; + +/// Result code for [rust_future_poll]. This is passed to the continuation function. +#[repr(i8)] +#[derive(Debug, PartialEq, Eq)] +pub enum RustFuturePoll { + /// The future is ready and is waiting for [rust_future_complete] to be called + Ready = 0, + /// The future might be ready and [rust_future_poll] should be called again + MaybeReady = 1, +} + +/// Foreign callback that's passed to [rust_future_poll] +/// +/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again +/// to continue progress on the future. +pub type RustFutureContinuationCallback = extern "C" fn(callback_data: Handle, RustFuturePoll); + +// === Public FFI API === + +/// Create a new [Handle] +/// +/// For each exported async function, UniFFI will create a scaffolding function that uses this to +/// create the [Handle] to pass to the foreign code. +pub fn rust_future_new(future: F, tag: UT) -> Handle +where + // See the [RustFuture] struct for an explanation of these trait bounds + F: Future + Send + 'static, + T: LowerReturn + Send + 'static, + UT: Send + 'static, + // Needed to create a Handle + dyn RustFutureFfi: HandleAlloc, +{ + // Create a RustFuture and coerce to `Arc`, which is what we use to + // implement the FFI + as HandleAlloc>::new_handle( + RustFuture::new(future, tag) as Arc> + ) +} + +/// Poll a Rust future +/// +/// When the future is ready to progress the continuation will be called with the `data` value and +/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called +/// exactly once. +/// +/// # Safety +/// +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_poll( + handle: Handle, + callback: RustFutureContinuationCallback, + data: Handle, +) where + dyn RustFutureFfi: HandleAlloc, +{ + as HandleAlloc>::get_arc(handle).ffi_poll(callback, data) +} + +/// Cancel a Rust future +/// +/// Any current and future continuations will be immediately called with RustFuturePoll::Ready. +/// +/// This is needed for languages like Swift, which continuation to wait for the continuation to be +/// called when tasks are cancelled. +/// +/// # Safety +/// +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_cancel(handle: Handle) +where + dyn RustFutureFfi: HandleAlloc, +{ + as HandleAlloc>::get_arc(handle).ffi_cancel() +} + +/// Complete a Rust future +/// +/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for +/// each supported FFI type. +/// +/// # Safety +/// +/// - The [Handle] must not previously have been passed to [rust_future_free] +/// - The `T` param must correctly correspond to the [rust_future_new] call. It must +/// be `>::ReturnType` +pub unsafe fn rust_future_complete( + handle: Handle, + out_status: &mut RustCallStatus, +) -> ReturnType +where + dyn RustFutureFfi: HandleAlloc, +{ + as HandleAlloc>::get_arc(handle).ffi_complete(out_status) +} + +/// Free a Rust future, dropping the strong reference and releasing all references held by the +/// future. +/// +/// # Safety +/// +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_free(handle: Handle) +where + dyn RustFutureFfi: HandleAlloc, +{ + as HandleAlloc>::consume_handle(handle).ffi_free() +} + +// Derive HandleAlloc for dyn RustFutureFfi for all FFI return types +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi<()>); diff --git a/uniffi_core/src/ffi/rustfuture/scheduler.rs b/uniffi_core/src/ffi/rustfuture/scheduler.rs new file mode 100644 index 0000000000..2de19c5d1a --- /dev/null +++ b/uniffi_core/src/ffi/rustfuture/scheduler.rs @@ -0,0 +1,89 @@ +/* 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 std::mem; + +use super::{Handle, RustFutureContinuationCallback, RustFuturePoll}; + +/// Schedules a [crate::RustFuture] by managing the continuation data +/// +/// This struct manages the continuation callback and data that comes from the foreign side. It +/// is responsible for calling the continuation callback when the future is ready to be woken up. +/// +/// The basic guarantees are: +/// +/// * Each callback will be invoked exactly once, with its associated data. +/// * If `wake()` is called, the callback will be invoked to wake up the future -- either +/// immediately or the next time we get a callback. +/// * If `cancel()` is called, the same will happen and the schedule will stay in the cancelled +/// state, invoking any future callbacks as soon as they're stored. + +#[derive(Debug)] +pub(super) enum Scheduler { + /// No continuations set, neither wake() nor cancel() called. + Empty, + /// `wake()` was called when there was no continuation set. The next time `store` is called, + /// the continuation should be immediately invoked with `RustFuturePoll::MaybeReady` + Waked, + /// The future has been cancelled, any future `store` calls should immediately result in the + /// continuation being called with `RustFuturePoll::Ready`. + Cancelled, + /// Continuation set, the next time `wake()` is called is called, we should invoke it. + Set(RustFutureContinuationCallback, Handle), +} + +impl Scheduler { + pub(super) fn new() -> Self { + Self::Empty + } + + /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or + /// `Cancelled` state, call the continuation immediately with the data. + pub(super) fn store(&mut self, callback: RustFutureContinuationCallback, data: Handle) { + match self { + Self::Empty => *self = Self::Set(callback, data), + Self::Set(old_callback, old_data) => { + log::error!( + "store: observed `Self::Set` state. Is poll() being called from multiple threads at once?" + ); + old_callback(*old_data, RustFuturePoll::Ready); + *self = Self::Set(callback, data); + } + Self::Waked => { + *self = Self::Empty; + callback(data, RustFuturePoll::MaybeReady); + } + Self::Cancelled => { + callback(data, RustFuturePoll::Ready); + } + } + } + + pub(super) fn wake(&mut self) { + match self { + // If we had a continuation set, then call it and transition to the `Empty` state. + Self::Set(callback, old_data) => { + let old_data = *old_data; + let callback = *callback; + *self = Self::Empty; + callback(old_data, RustFuturePoll::MaybeReady); + } + // If we were in the `Empty` state, then transition to `Waked`. The next time `store` + // is called, we will immediately call the continuation. + Self::Empty => *self = Self::Waked, + // This is a no-op if we were in the `Cancelled` or `Waked` state. + _ => (), + } + } + + pub(super) fn cancel(&mut self) { + if let Self::Set(callback, old_data) = mem::replace(self, Self::Cancelled) { + callback(old_data, RustFuturePoll::Ready); + } + } + + pub(super) fn is_cancelled(&self) -> bool { + matches!(self, Self::Cancelled) + } +} diff --git a/uniffi_core/src/ffi/rustfuture/tests.rs b/uniffi_core/src/ffi/rustfuture/tests.rs new file mode 100644 index 0000000000..83f74831a3 --- /dev/null +++ b/uniffi_core/src/ffi/rustfuture/tests.rs @@ -0,0 +1,224 @@ +use once_cell::sync::OnceCell; +use std::{ + future::Future, + panic, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Waker}, +}; + +use super::*; +use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode}; + +// Sender/Receiver pair that we use for testing +struct Channel { + result: Option>, + waker: Option, +} + +struct Sender(Arc>); + +impl Sender { + fn wake(&self) { + let inner = self.0.lock().unwrap(); + if let Some(waker) = &inner.waker { + waker.wake_by_ref(); + } + } + + fn send(&self, value: Result) { + let mut inner = self.0.lock().unwrap(); + if inner.result.replace(value).is_some() { + panic!("value already sent"); + } + if let Some(waker) = &inner.waker { + waker.wake_by_ref(); + } + } +} + +struct Receiver(Arc>); + +impl Future for Receiver { + type Output = Result; + + fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll> { + let mut inner = self.0.lock().unwrap(); + match &inner.result { + Some(v) => Poll::Ready(v.clone()), + None => { + inner.waker = Some(context.waker().clone()); + Poll::Pending + } + } + } +} + +// Create a sender and rust future that we can use for testing +fn channel() -> (Sender, Arc>) { + let channel = Arc::new(Mutex::new(Channel { + result: None, + waker: None, + })); + let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag); + (Sender(channel), rust_future) +} + +/// Poll a Rust future and get an OnceCell that's set when the continuation is called +fn poll(rust_future: &Arc>) -> Arc> { + let cell = Arc::new(OnceCell::new()); + let handle = + as HandleAlloc>::new_handle(Arc::clone(&cell)); + rust_future.clone().ffi_poll(poll_continuation, handle); + cell +} + +extern "C" fn poll_continuation(data: Handle, code: RustFuturePoll) { + let cell = as HandleAlloc>::get_arc(data); + cell.set(code).expect("Error setting OnceCell"); +} + +fn complete(rust_future: Arc>) -> (RustBuffer, RustCallStatus) { + let mut out_status_code = RustCallStatus::default(); + let return_value = rust_future.ffi_complete(&mut out_status_code); + (return_value, out_status_code) +} + +#[test] +fn test_success() { + let (sender, rust_future) = channel(); + + // Test polling the rust future before it's ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.wake(); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + // Test polling the rust future when it's ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.send(Ok("All done".into())); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + // Future polls should immediately return ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + // Complete the future + let (return_buf, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Success); + assert_eq!( + >::try_lift(return_buf).unwrap(), + "All done" + ); +} + +#[test] +fn test_error() { + let (sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.send(Err("Something went wrong".into())); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + let (_, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Error); + unsafe { + assert_eq!( + >::try_lift_from_rust_buffer( + call_status.error_buf.assume_init() + ) + .unwrap(), + TestError::from("Something went wrong"), + ) + } +} + +// Once `complete` is called, the inner future should be released, even if wakers still hold a +// reference to the RustFuture +#[test] +fn test_cancel() { + let (_sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + rust_future.ffi_cancel(); + // Cancellation should immediately invoke the callback with RustFuturePoll::Ready + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + // Future polls should immediately invoke the callback with RustFuturePoll::Ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + let (_, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Cancelled); +} + +// Once `free` is called, the inner future should be released, even if wakers still hold a +// reference to the RustFuture +#[test] +fn test_release_future() { + let (sender, rust_future) = channel(); + // Create a weak reference to the channel to use to check if rust_future has dropped its + // future. + let channel_weak = Arc::downgrade(&sender.0); + drop(sender); + // Create an extra ref to rust_future, simulating a waker that still holds a reference to + // it + let rust_future2 = rust_future.clone(); + + // Complete the rust future + rust_future.ffi_free(); + // Even though rust_future is still alive, the channel shouldn't be + assert!(Arc::strong_count(&rust_future2) > 0); + assert_eq!(channel_weak.strong_count(), 0); + assert!(channel_weak.upgrade().is_none()); +} + +// If `free` is called with a continuation still stored, we should call it them then. +// +// This shouldn't happen in practice, but it seems like good defensive programming +#[test] +fn test_complete_with_stored_continuation() { + let (_sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + rust_future.ffi_free(); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); +} + +// Test what happens if we see a `wake()` call while we're polling the future. This can +// happen, for example, with futures that are handled by a tokio thread pool. We should +// schedule another poll of the future in this case. +#[test] +fn test_wake_during_poll() { + let mut first_time = true; + let future = std::future::poll_fn(move |ctx| { + if first_time { + first_time = false; + // Wake the future while we are in the middle of polling it + ctx.waker().clone().wake(); + Poll::Pending + } else { + // The second time we're polled, we're ready + Poll::Ready("All done".to_owned()) + } + }); + let rust_future: Arc> = RustFuture::new(future, crate::UniFfiTag); + let continuation_result = poll(&rust_future); + // The continuation function should called immediately + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + // A second poll should finish the future + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + let (return_buf, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Success); + assert_eq!( + >::try_lift(return_buf).unwrap(), + "All done" + ); +} diff --git a/uniffi_core/src/ffi_converter_impls.rs b/uniffi_core/src/ffi_converter_impls.rs index af18f3873b..2460f87cb0 100644 --- a/uniffi_core/src/ffi_converter_impls.rs +++ b/uniffi_core/src/ffi_converter_impls.rs @@ -23,7 +23,7 @@ /// "UT" means an abitrary `UniFfiTag` type. use crate::{ check_remaining, derive_ffi_traits, ffi_converter_rust_buffer_lift_and_lower, metadata, - ConvertError, FfiConverter, ForeignExecutor, Lift, LiftReturn, Lower, LowerReturn, + ConvertError, FfiConverter, ForeignExecutor, Handle, Lift, LiftReturn, Lower, LowerReturn, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError, }; use anyhow::bail; @@ -411,7 +411,7 @@ where /// The foreign bindings may use an actual pointer to the executor object, or a usized integer /// handle. unsafe impl FfiConverter for ForeignExecutor { - type FfiType = crate::ForeignExecutorHandle; + type FfiType = Handle; // Passing these back to the foreign bindings is currently not supported fn lower(executor: Self) -> Self::FfiType { @@ -419,13 +419,7 @@ unsafe impl FfiConverter for ForeignExecutor { } fn write(executor: Self, buf: &mut Vec) { - // Use native endian when writing these values, so they can be casted to pointer values - match std::mem::size_of::() { - // Use native endian when reading these values, so they can be casted to pointer values - 4 => buf.put_u32_ne(executor.handle.0 as u32), - 8 => buf.put_u64_ne(executor.handle.0 as u64), - n => panic!("Invalid usize width: {n}"), - }; + buf.put_u64(executor.handle.as_raw()) } fn try_lift(executor: Self::FfiType) -> Result { @@ -433,13 +427,7 @@ unsafe impl FfiConverter for ForeignExecutor { } fn try_read(buf: &mut &[u8]) -> Result { - let usize_val = match std::mem::size_of::() { - // Use native endian when reading these values, so they can be casted to pointer values - 4 => buf.get_u32_ne() as usize, - 8 => buf.get_u64_ne() as usize, - n => panic!("Invalid usize width: {n}"), - }; - >::try_lift(crate::ForeignExecutorHandle(usize_val as *const ())) + >::try_lift(Handle::from_raw(buf.get_u64()).unwrap()) } const TYPE_ID_META: MetadataBuffer = diff --git a/uniffi_core/src/ffi_converter_traits.rs b/uniffi_core/src/ffi_converter_traits.rs index 3b5914e32f..ced4fbf399 100644 --- a/uniffi_core/src/ffi_converter_traits.rs +++ b/uniffi_core/src/ffi_converter_traits.rs @@ -51,7 +51,9 @@ use std::{borrow::Borrow, sync::Arc}; use anyhow::bail; use bytes::Buf; -use crate::{FfiDefault, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError}; +use crate::{ + FfiDefault, Handle, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError, +}; /// Generalized FFI conversions /// @@ -351,6 +353,66 @@ pub trait ConvertError: Sized { fn try_convert_unexpected_callback_error(e: UnexpectedUniFFICallbackError) -> Result; } +/// Manage handles for `Arc` instances +/// +/// Handles are used to manage objects that are passed across the FFI. They general usage is: +/// +/// * Rust creates an `Arc<>` +/// * Rust uses `new_handle` to create a handle that represents the Arc reference +/// * Rust passes the handle to the foreign code as a `u64` +/// * The foreign code passes the handle back to `Rust` to refer to the object: +/// * Handle are usually passed as borrowed values. When an FFI function inputs a handle as an +/// argument, the foreign code simply passes a copy of the `u64` to Rust, which calls `get_arc` +/// to get a new `Arc<>` clone for it. +/// * Handles are returned as owned values. When an FFI function returns a handle, the foreign +/// code either stops using the handle after returning it or calls `clone_handle` and returns +/// the clone. TODO: actually start doing this (#1797) +/// * Eventually the foreign code may destroy their handle by passing it into a "free" FFI +/// function. This functions input an owned handle and consume it. +/// +/// The foreign code also defines their own handles. These represent foreign objects that are +/// passed to Rust. Using foreign handles is essentially the same as above, but in reverse. +/// +/// Handles must always be `Send` and the objects they reference must always be `Sync`. +/// This means that it must be safe to send handles to other threads and use them there. +/// +/// Note: this only needs to be derived for unsized types, there's a blanket impl for `T: Sized`. +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +/// `&T` using the Arc. +pub unsafe trait HandleAlloc: Send + Sync { + /// Create a new handle for an Arc value + /// + /// Use this to lower an Arc into a handle value before passing it across the FFI. + /// The newly-created handle will have reference count = 1. + fn new_handle(value: Arc) -> Handle; + + /// Clone a handle + /// + /// This creates a new handle from an existing one. + /// It's used when the foreign code wants to pass back an owned handle and still keep a copy + /// for themselves. + fn clone_handle(handle: Handle) -> Handle; + + /// Get a clone of the `Arc<>` using a "borrowed" handle. + /// + /// Take care that the handle can not be destroyed between when it's passed and when + /// `get_arc()` is called. #1797 is a cautionary tale. + fn get_arc(handle: Handle) -> Arc { + Self::consume_handle(Self::clone_handle(handle)) + } + + /// Consume a handle, getting back the initial `Arc<>` + fn consume_handle(handle: Handle) -> Arc; +} + /// Derive FFI traits /// /// This can be used to derive: @@ -463,4 +525,46 @@ macro_rules! derive_ffi_traits { } } }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? HandleAlloc<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + unsafe impl $(<$($generic),*>)* $crate::HandleAlloc<$ut> for $ty $(where $($where)*)* + { + // To implement HandleAlloc for an unsized type, wrap it with a second Arc which + // converts the wide pointer into a normal pointer. + + fn new_handle(value: ::std::sync::Arc) -> $crate::Handle { + $crate::Handle::from_pointer(::std::sync::Arc::into_raw(::std::sync::Arc::new(value))) + } + + fn clone_handle(handle: $crate::Handle) -> $crate::Handle { + unsafe { + ::std::sync::Arc::<::std::sync::Arc>::increment_strong_count(handle.as_pointer::<::std::sync::Arc>()); + } + handle + } + + fn consume_handle(handle: $crate::Handle) -> ::std::sync::Arc { + unsafe { + ::std::sync::Arc::::clone( + &std::sync::Arc::<::std::sync::Arc::>::from_raw(handle.as_pointer::<::std::sync::Arc>()) + ) + } + } + } + }; +} + +unsafe impl HandleAlloc for T { + fn new_handle(value: Arc) -> Handle { + Handle::from_pointer(Arc::into_raw(value)) + } + + fn clone_handle(handle: Handle) -> Handle { + unsafe { Arc::increment_strong_count(handle.as_pointer::()) }; + handle + } + + fn consume_handle(handle: Handle) -> Arc { + unsafe { Arc::from_raw(handle.as_pointer()) } + } } diff --git a/uniffi_core/src/lib.rs b/uniffi_core/src/lib.rs index 9003b08f9b..3ae2983eab 100644 --- a/uniffi_core/src/lib.rs +++ b/uniffi_core/src/lib.rs @@ -45,7 +45,8 @@ pub mod metadata; pub use ffi::*; pub use ffi_converter_traits::{ - ConvertError, FfiConverter, FfiConverterArc, Lift, LiftRef, LiftReturn, Lower, LowerReturn, + ConvertError, FfiConverter, FfiConverterArc, HandleAlloc, Lift, LiftRef, LiftReturn, Lower, + LowerReturn, }; pub use metadata::*; diff --git a/uniffi_macros/Cargo.toml b/uniffi_macros/Cargo.toml index 1e2ebd4299..cf9baa89fe 100644 --- a/uniffi_macros/Cargo.toml +++ b/uniffi_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_macros" -version = "0.25.0" +version = "0.25.2" authors = ["Firefox Sync Team "] description = "a multi-language bindings generator for rust (convenience macros)" documentation = "https://mozilla.github.io/uniffi-rs" @@ -23,8 +23,8 @@ quote = "1.0" serde = "1.0.136" syn = { version = "2.0", features = ["full", "visit-mut"] } toml = "0.5.9" -uniffi_build = { path = "../uniffi_build", version = "=0.25.0" } -uniffi_meta = { path = "../uniffi_meta", version = "=0.25.0" } +uniffi_build = { path = "../uniffi_build", version = "=0.25.2" } +uniffi_meta = { path = "../uniffi_meta", version = "=0.25.2" } [features] default = [] diff --git a/uniffi_macros/src/error.rs b/uniffi_macros/src/error.rs index cb25df5d68..a2ee7cf603 100644 --- a/uniffi_macros/src/error.rs +++ b/uniffi_macros/src/error.rs @@ -87,6 +87,7 @@ fn flat_error_ffi_converter_impl( ) -> TokenStream { let name = ident_to_string(ident); let lower_impl_spec = tagged_impl_header("Lower", ident, udl_mode); + let lift_impl_spec = tagged_impl_header("Lift", ident, udl_mode); let derive_ffi_traits = derive_ffi_traits(ident, udl_mode, &["ConvertError"]); let mod_path = match mod_path() { Ok(p) => p, @@ -127,8 +128,7 @@ fn flat_error_ffi_converter_impl( } }; - let lift_impl = implement_lift.then(|| { - let lift_impl_spec = tagged_impl_header("Lift", ident, udl_mode); + let lift_impl = if implement_lift { let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { let v_ident = &v.ident; let idx = Index::from(i + 1); @@ -157,7 +157,31 @@ fn flat_error_ffi_converter_impl( } } - }); + } else { + quote! { + // Lifting flat errors is not currently supported, but we still define the trait so + // that dicts containing flat errors don't cause compile errors (see ReturnOnlyDict in + // coverall.rs). + // + // Note: it would be better to not derive `Lift` for dictionaries containing flat + // errors, but getting the trait bounds and derived impls there would be much harder. + // For now, we just fail at runtime. + #[automatically_derived] + unsafe #lift_impl_spec { + type FfiType = ::uniffi::RustBuffer; + + fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result { + panic!("Can't lift flat errors") + } + + fn try_lift(v: ::uniffi::RustBuffer) -> ::uniffi::deps::anyhow::Result { + panic!("Can't lift flat errors") + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = >::TYPE_ID_META; + } + } + }; quote! { #lower_impl diff --git a/uniffi_macros/src/export.rs b/uniffi_macros/src/export.rs index 7d3f54da93..364e54470d 100644 --- a/uniffi_macros/src/export.rs +++ b/uniffi_macros/src/export.rs @@ -22,6 +22,7 @@ use self::{ use crate::util::{ident_to_string, mod_path}; pub use attributes::ExportAttributeArguments; pub use callback_interface::ffi_converter_callback_interface_impl; +pub use trait_interface::alter_trait; // TODO(jplatte): Ensure no generics, … // TODO(jplatte): Aggregate errors instead of short-circuiting, wherever possible @@ -76,7 +77,7 @@ pub(crate) fn expand_export( } => { let trait_name = ident_to_string(&self_ident); let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name); - let trait_impl = callback_interface::trait_impl(&mod_path, &self_ident, &items) + let trait_impl = callback_interface::trait_impl(&mod_path, &self_ident, &items, false) .unwrap_or_else(|e| e.into_compile_error()); let metadata_items = callback_interface::metadata_items(&self_ident, &items, &mod_path) .unwrap_or_else(|e| vec![e.into_compile_error()]); @@ -101,6 +102,14 @@ pub(crate) fn expand_export( } } +/// Alter the tokens wrapped with the `[uniffi::export]` if needed +pub fn alter_input(item: &Item) -> TokenStream { + match item { + Item::Trait(item_trait) => alter_trait(item_trait), + _ => quote! { #item }, + } +} + /// Rewrite Self type alias usage in an impl block to the type itself. /// /// For example, diff --git a/uniffi_macros/src/export/callback_interface.rs b/uniffi_macros/src/export/callback_interface.rs index 85a8b0663a..13fc769d87 100644 --- a/uniffi_macros/src/export/callback_interface.rs +++ b/uniffi_macros/src/export/callback_interface.rs @@ -16,6 +16,7 @@ pub(super) fn trait_impl( mod_path: &str, trait_ident: &Ident, items: &[ImplItem], + trait_interface: bool, ) -> syn::Result { let trait_name = ident_to_string(trait_ident); let trait_impl_ident = trait_impl_ident(&trait_name); @@ -32,6 +33,20 @@ pub(super) fn trait_impl( _ => unreachable!("traits have no constructors"), }) .collect::>()?; + + let uniffi_foreign_handle = trait_interface.then(|| { + quote! { + fn uniffi_foreign_handle(&self) -> Option<::uniffi::Handle> { + let raw_clone = #internals_ident.invoke_callback::( + self.handle, uniffi::IDX_CALLBACK_CLONE, Default::default() + ); + let handle = ::uniffi::Handle::from_raw(raw_clone) + .unwrap_or_else(|| panic!("{} IDX_CALLBACK_CLONE returned null handle", #trait_name)); + Some(handle) + } + } + }); + Ok(quote! { #[doc(hidden)] static #internals_ident: ::uniffi::ForeignCallbackInternals = ::uniffi::ForeignCallbackInternals::new(); @@ -45,11 +60,11 @@ pub(super) fn trait_impl( #[doc(hidden)] #[derive(Debug)] struct #trait_impl_ident { - handle: u64, + handle: ::uniffi::Handle, } impl #trait_impl_ident { - fn new(handle: u64) -> Self { + fn new(handle: ::uniffi::Handle) -> Self { Self { handle } } } @@ -66,6 +81,7 @@ pub(super) fn trait_impl( impl #trait_ident for #trait_impl_ident { #trait_impl_methods + #uniffi_foreign_handle } }) } @@ -109,7 +125,11 @@ pub fn ffi_converter_callback_interface_impl( type FfiType = u64; fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result { - Ok(::std::boxed::Box::new(<#trait_impl_ident>::new(v))) + let handle = match ::uniffi::Handle::from_raw(v) { + Some(h) => h, + None => ::uniffi::deps::anyhow::bail!("{}::try_lift: null handle", #trait_name), + }; + Ok(::std::boxed::Box::new(<#trait_impl_ident>::new(handle))) } fn try_read(buf: &mut &[u8]) -> ::uniffi::deps::anyhow::Result { diff --git a/uniffi_macros/src/export/scaffolding.rs b/uniffi_macros/src/export/scaffolding.rs index d00d8403bd..cb333aac98 100644 --- a/uniffi_macros/src/export/scaffolding.rs +++ b/uniffi_macros/src/export/scaffolding.rs @@ -137,23 +137,8 @@ impl ScaffoldingBits { let params: Vec<_> = iter::once(quote! { uniffi_self_lowered: #lift_impl::FfiType }) .chain(sig.scaffolding_params()) .collect(); - let try_lift_self = if is_trait { - // For trait interfaces we need to special case this. Trait interfaces normally lift - // foreign trait impl pointers. However, for a method call, we want to lift a Rust - // pointer. - quote! { - { - let foreign_arc = ::std::boxed::Box::leak(unsafe { Box::from_raw(uniffi_self_lowered as *mut ::std::sync::Arc) }); - // Take a clone for our own use. - Ok(::std::sync::Arc::clone(foreign_arc)) - } - } - } else { - quote! { #lift_impl::try_lift(uniffi_self_lowered) } - }; - let lift_closure = sig.lift_closure(Some(quote! { - match #try_lift_self { + match #lift_impl::try_lift(uniffi_self_lowered) { Ok(v) => v, Err(e) => return Err(("self", e)) } @@ -264,7 +249,7 @@ pub(super) fn gen_ffi_function( quote! { #[doc(hidden)] #[no_mangle] - pub extern "C" fn #ffi_ident(#(#params,)*) -> ::uniffi::RustFutureHandle { + pub extern "C" fn #ffi_ident(#(#params,)*) -> ::uniffi::Handle { ::uniffi::deps::log::debug!(#name); let uniffi_lift_args = #lift_closure; match uniffi_lift_args() { diff --git a/uniffi_macros/src/export/trait_interface.rs b/uniffi_macros/src/export/trait_interface.rs index 53a7f78c17..bdbb6ca103 100644 --- a/uniffi_macros/src/export/trait_interface.rs +++ b/uniffi_macros/src/export/trait_interface.rs @@ -4,6 +4,7 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, quote_spanned}; +use syn::ItemTrait; use crate::{ export::{ @@ -11,9 +12,8 @@ use crate::{ item::ImplItem, }, object::interface_meta_static_var, - util::{ident_to_string, tagged_impl_header}, + util::{derive_ffi_traits, ident_to_string, tagged_impl_header}, }; -use uniffi_meta::free_fn_symbol_name; pub(super) fn gen_trait_scaffolding( mod_path: &str, @@ -26,24 +26,37 @@ pub(super) fn gen_trait_scaffolding( return Err(syn::Error::new_spanned(rt, "not supported for traits")); } let trait_name = ident_to_string(&self_ident); - let trait_impl = callback_interface::trait_impl(mod_path, &self_ident, &items) + let trait_impl = callback_interface::trait_impl(mod_path, &self_ident, &items, true) .unwrap_or_else(|e| e.into_compile_error()); - + let clone_fn_ident = Ident::new( + &uniffi_meta::clone_fn_symbol_name(mod_path, &trait_name), + Span::call_site(), + ); let free_fn_ident = Ident::new( - &free_fn_symbol_name(mod_path, &trait_name), + &uniffi_meta::free_fn_symbol_name(mod_path, &trait_name), Span::call_site(), ); - let free_tokens = quote! { + let helper_ffi_fn_tokens = quote! { + #[doc(hidden)] + #[no_mangle] + pub extern "C" fn #clone_fn_ident( + handle: ::uniffi::Handle, + call_status: &mut ::uniffi::RustCallStatus + ) -> ::uniffi::Handle { + uniffi::rust_call(call_status, || { + Ok(>::clone_handle(handle)) + }) + } + #[doc(hidden)] #[no_mangle] pub extern "C" fn #free_fn_ident( - ptr: *const ::std::ffi::c_void, + handle: ::uniffi::Handle, call_status: &mut ::uniffi::RustCallStatus ) { uniffi::rust_call(call_status, || { - assert!(!ptr.is_null()); - drop(unsafe { ::std::boxed::Box::from_raw(ptr as *mut std::sync::Arc) }); + >::consume_handle(handle); Ok(()) }); } @@ -73,7 +86,7 @@ pub(super) fn gen_trait_scaffolding( Ok(quote_spanned! { self_ident.span() => #meta_static_var - #free_tokens + #helper_ffi_fn_tokens #trait_impl #impl_tokens #ffi_converter_tokens @@ -83,6 +96,8 @@ pub(super) fn gen_trait_scaffolding( pub(crate) fn ffi_converter(mod_path: &str, trait_ident: &Ident, udl_mode: bool) -> TokenStream { let impl_spec = tagged_impl_header("FfiConverterArc", "e! { dyn #trait_ident }, udl_mode); let lift_ref_impl_spec = tagged_impl_header("LiftRef", "e! { dyn #trait_ident }, udl_mode); + let derive_ffi_traits = + derive_ffi_traits("e! { dyn #trait_ident }, udl_mode, &["HandleAlloc"]); let trait_name = ident_to_string(trait_ident); let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name); @@ -93,30 +108,44 @@ pub(crate) fn ffi_converter(mod_path: &str, trait_ident: &Ident, udl_mode: bool) // and thus help the user debug why the requirement isn't being met. uniffi::deps::static_assertions::assert_impl_all!(dyn #trait_ident: ::core::marker::Sync, ::core::marker::Send); + #derive_ffi_traits + unsafe #impl_spec { - type FfiType = *const ::std::os::raw::c_void; + type FfiType = ::uniffi::Handle; fn lower(obj: ::std::sync::Arc) -> Self::FfiType { - ::std::boxed::Box::into_raw(::std::boxed::Box::new(obj)) as *const ::std::os::raw::c_void + // If obj wraps a foreign implementation, then `uniffi_foreign_handle` will return + // the handle here and we can use that rather than wrapping it again with Rust. + let handle = match obj.uniffi_foreign_handle() { + Some(handle) => handle, + None => >::new_handle(obj), + }; + handle } - fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc> { - Ok(::std::sync::Arc::new(<#trait_impl_ident>::new(v as u64))) + fn try_lift(handle: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc> { + Ok(if handle.is_foreign() { + // For foreign handles, construct a struct that implements the trait by calling + // the handle + ::std::sync::Arc::new(<#trait_impl_ident>::new(handle)) + } else { + // For Rust handles, consume handle that the foreign side cloned. + >::consume_handle(handle) + }) } fn write(obj: ::std::sync::Arc, buf: &mut Vec) { - ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); ::uniffi::deps::bytes::BufMut::put_u64( buf, - >::lower(obj) as u64, + >::lower(obj).as_raw() ); } fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc> { - ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); ::uniffi::check_remaining(buf, 8)?; >::try_lift( - ::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType) + ::uniffi::Handle::from_raw_unchecked(::uniffi::deps::bytes::Buf::get_u64(buf)) + ) } const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_INTERFACE) @@ -130,3 +159,31 @@ pub(crate) fn ffi_converter(mod_path: &str, trait_ident: &Ident, udl_mode: bool) } } } + +pub fn alter_trait(item: &ItemTrait) -> TokenStream { + let ItemTrait { + attrs, + vis, + unsafety, + auto_token, + trait_token, + ident, + generics, + colon_token, + supertraits, + items, + .. + } = item; + + quote! { + #(#attrs)* + #vis #unsafety #auto_token #trait_token #ident #generics #colon_token #supertraits { + #(#items)* + + #[doc(hidden)] + fn uniffi_foreign_handle(&self) -> Option<::uniffi::Handle> { + None + } + } + } +} diff --git a/uniffi_macros/src/lib.rs b/uniffi_macros/src/lib.rs index b7ba86ddc1..50520186f4 100644 --- a/uniffi_macros/src/lib.rs +++ b/uniffi_macros/src/lib.rs @@ -104,20 +104,28 @@ pub fn export(attr_args: TokenStream, input: TokenStream) -> TokenStream { } fn do_export(attr_args: TokenStream, input: TokenStream, udl_mode: bool) -> TokenStream { - let copied_input = (!udl_mode).then(|| proc_macro2::TokenStream::from(input.clone())); - let gen_output = || { let args = syn::parse(attr_args)?; let item = syn::parse(input)?; - expand_export(item, args, udl_mode) + let altered_input = if udl_mode { + quote! {} + } else { + export::alter_input(&item) + }; + let output = expand_export(item, args, udl_mode)?; + Ok(quote! { + #altered_input + #output + }) }; - let output = gen_output().unwrap_or_else(syn::Error::into_compile_error); + gen_output() + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} - quote! { - #copied_input - #output - } - .into() +#[proc_macro_attribute] +pub fn trait_interface(_attr_args: TokenStream, input: TokenStream) -> TokenStream { + export::alter_trait(&parse_macro_input!(input)).into() } #[proc_macro_derive(Record, attributes(uniffi))] diff --git a/uniffi_macros/src/object.rs b/uniffi_macros/src/object.rs index 697641c829..59222d0283 100644 --- a/uniffi_macros/src/object.rs +++ b/uniffi_macros/src/object.rs @@ -1,7 +1,6 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::DeriveInput; -use uniffi_meta::free_fn_symbol_name; use crate::util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header}; @@ -9,7 +8,14 @@ pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result syn::Result ::uniffi::Handle { + uniffi::rust_call(call_status, || { + Ok(<#ident as ::uniffi::HandleAlloc>::clone_handle(handle)) + }) + } + #[doc(hidden)] #[no_mangle] pub extern "C" fn #free_fn_ident( - ptr: *const ::std::ffi::c_void, + handle: ::uniffi::Handle, call_status: &mut ::uniffi::RustCallStatus ) { uniffi::rust_call(call_status, || { - assert!(!ptr.is_null()); - let ptr = ptr.cast::<#ident>(); - unsafe { - ::std::sync::Arc::decrement_strong_count(ptr); - } + <#ident as ::uniffi::HandleAlloc>::consume_handle(handle); Ok(()) }); } @@ -58,61 +71,28 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { #[automatically_derived] /// Support for passing reference-counted shared objects via the FFI. /// - /// To avoid dealing with complex lifetime semantics over the FFI, any data passed - /// by reference must be encapsulated in an `Arc`, and must be safe to share - /// across threads. + /// Objects are `Arc<>` values in Rust and Handle values on the FFI. + /// The `HandleAlloc` trait doc string has usage guidelines for handles. unsafe #impl_spec { - // Don't use a pointer to as that requires a `pub ` - type FfiType = *const ::std::os::raw::c_void; + type FfiType = ::uniffi::Handle; - /// When lowering, we have an owned `Arc` and we transfer that ownership - /// to the foreign-language code, "leaking" it out of Rust's ownership system - /// as a raw pointer. This works safely because we have unique ownership of `self`. - /// The foreign-language code is responsible for freeing this by calling the - /// `ffi_object_free` FFI function provided by the corresponding UniFFI type. - /// - /// Safety: when freeing the resulting pointer, the foreign-language code must - /// call the destructor function specific to the type `T`. Calling the destructor - /// function for other types may lead to undefined behaviour. fn lower(obj: ::std::sync::Arc) -> Self::FfiType { - ::std::sync::Arc::into_raw(obj) as Self::FfiType + <#ident as ::uniffi::HandleAlloc>::new_handle(obj) } - /// When lifting, we receive a "borrow" of the `Arc` that is owned by - /// the foreign-language code, and make a clone of it for our own use. - /// - /// Safety: the provided value must be a pointer previously obtained by calling - /// the `lower()` or `write()` method of this impl. - fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc> { - let v = v as *const #ident; - // We musn't drop the `Arc` that is owned by the foreign-language code. - let foreign_arc = ::std::mem::ManuallyDrop::new(unsafe { ::std::sync::Arc::::from_raw(v) }); - // Take a clone for our own use. - Ok(::std::sync::Arc::clone(&*foreign_arc)) + fn try_lift(handle: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc> { + Ok(<#ident as ::uniffi::HandleAlloc>::consume_handle(handle)) } - /// When writing as a field of a complex structure, make a clone and transfer ownership - /// of it to the foreign-language code by writing its pointer into the buffer. - /// The foreign-language code is responsible for freeing this by calling the - /// `ffi_object_free` FFI function provided by the corresponding UniFFI type. - /// - /// Safety: when freeing the resulting pointer, the foreign-language code must - /// call the destructor function specific to the type `T`. Calling the destructor - /// function for other types may lead to undefined behaviour. fn write(obj: ::std::sync::Arc, buf: &mut Vec) { - ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); - ::uniffi::deps::bytes::BufMut::put_u64(buf, >::lower(obj) as u64); + ::uniffi::deps::bytes::BufMut::put_u64(buf, >::lower(obj).as_raw()) } - /// When reading as a field of a complex structure, we receive a "borrow" of the `Arc` - /// that is owned by the foreign-language code, and make a clone for our own use. - /// - /// Safety: the buffer must contain a pointer previously obtained by calling - /// the `lower()` or `write()` method of this impl. fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc> { - ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); ::uniffi::check_remaining(buf, 8)?; - >::try_lift(::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType) + >::try_lift( + ::uniffi::Handle::from_raw_unchecked(::uniffi::deps::bytes::Buf::get_u64(buf)) + ) } const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_INTERFACE) diff --git a/uniffi_macros/src/setup_scaffolding.rs b/uniffi_macros/src/setup_scaffolding.rs index a21016e9ff..fbce21a1e9 100644 --- a/uniffi_macros/src/setup_scaffolding.rs +++ b/uniffi_macros/src/setup_scaffolding.rs @@ -130,7 +130,7 @@ pub fn setup_scaffolding(namespace: String) -> Result { #[doc(hidden)] pub trait UniffiCustomTypeConverter { type Builtin; - fn into_custom(val: Self::Builtin) -> uniffi::Result where Self: Sized; + fn into_custom(val: Self::Builtin) -> ::uniffi::Result where Self: Sized; fn from_custom(obj: Self) -> Self::Builtin; } }) @@ -138,12 +138,12 @@ pub fn setup_scaffolding(namespace: String) -> Result { /// Generates the rust_future_* functions /// -/// The foreign side uses a type-erased `RustFutureHandle` to interact with futures, which presents +/// The foreign side uses a type-erased `Handle` to interact with futures, which presents /// a problem when creating scaffolding functions. What is the `ReturnType` parameter of `RustFutureFfi`? /// /// Handle this by using some brute-force monomorphization. For each possible ffi type, we /// generate a set of scaffolding functions. The bindings code is responsible for calling the one -/// corresponds the scaffolding function that created the `RustFutureHandle`. +/// corresponds the scaffolding function that created the `Handle`. /// /// This introduces safety issues, but we do get some type checking. If the bindings code calls /// the wrong rust_future_complete function, they should get an unexpected return type, which @@ -160,7 +160,7 @@ fn rust_future_scaffolding_fns(module_path: &str) -> TokenStream { (quote! { i64 }, "i64"), (quote! { f32 }, "f32"), (quote! { f64 }, "f64"), - (quote! { *const ::std::ffi::c_void }, "pointer"), + (quote! { ::uniffi::Handle }, "handle"), (quote! { ::uniffi::RustBuffer }, "rust_buffer"), (quote! { () }, "void"), ]; @@ -175,32 +175,32 @@ fn rust_future_scaffolding_fns(module_path: &str) -> TokenStream { #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::RustFutureHandle, callback: ::uniffi::RustFutureContinuationCallback, data: *const ()) { - ::uniffi::ffi::rust_future_poll::<#return_type>(handle, callback, data); + pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::Handle, callback: ::uniffi::RustFutureContinuationCallback, data: ::uniffi::Handle) { + ::uniffi::ffi::rust_future_poll::<#return_type, crate::UniFfiTag>(handle, callback, data); } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::RustFutureHandle) { - ::uniffi::ffi::rust_future_cancel::<#return_type>(handle) + pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::Handle) { + ::uniffi::ffi::rust_future_cancel::<#return_type, crate::UniFfiTag>(handle) } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] pub unsafe extern "C" fn #ffi_rust_future_complete( - handle: ::uniffi::RustFutureHandle, + handle: ::uniffi::Handle, out_status: &mut ::uniffi::RustCallStatus ) -> #return_type { - ::uniffi::ffi::rust_future_complete::<#return_type>(handle, out_status) + ::uniffi::ffi::rust_future_complete::<#return_type, crate::UniFfiTag>(handle, out_status) } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::RustFutureHandle) { - ::uniffi::ffi::rust_future_free::<#return_type>(handle) + pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::Handle) { + ::uniffi::ffi::rust_future_free::<#return_type, crate::UniFfiTag>(handle) } } }) diff --git a/uniffi_macros/src/util.rs b/uniffi_macros/src/util.rs index 9f213ea1d7..9cfd554491 100644 --- a/uniffi_macros/src/util.rs +++ b/uniffi_macros/src/util.rs @@ -216,7 +216,7 @@ pub(crate) fn tagged_impl_header( } } -pub(crate) fn derive_all_ffi_traits(ty: &Ident, udl_mode: bool) -> TokenStream { +pub(crate) fn derive_all_ffi_traits(ty: impl ToTokens, udl_mode: bool) -> TokenStream { if udl_mode { quote! { ::uniffi::derive_ffi_traits!(local #ty); } } else { @@ -224,7 +224,11 @@ pub(crate) fn derive_all_ffi_traits(ty: &Ident, udl_mode: bool) -> TokenStream { } } -pub(crate) fn derive_ffi_traits(ty: &Ident, udl_mode: bool, trait_names: &[&str]) -> TokenStream { +pub(crate) fn derive_ffi_traits( + ty: impl ToTokens, + udl_mode: bool, + trait_names: &[&str], +) -> TokenStream { let trait_idents = trait_names .iter() .map(|name| Ident::new(name, Span::call_site())); diff --git a/uniffi_meta/Cargo.toml b/uniffi_meta/Cargo.toml index d2a4dbe0ac..fe4b514cb1 100644 --- a/uniffi_meta/Cargo.toml +++ b/uniffi_meta/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_meta" -version = "0.25.0" +version = "0.25.2" edition = "2021" description = "uniffi_meta" homepage = "https://mozilla.github.io/uniffi-rs" @@ -12,4 +12,4 @@ keywords = ["ffi", "bindgen"] anyhow = "1" bytes = "1.3" siphasher = "0.3" -uniffi_checksum_derive = { version = "0.25.0", path = "../uniffi_checksum_derive" } +uniffi_checksum_derive = { version = "0.25.2", path = "../uniffi_checksum_derive" } diff --git a/uniffi_meta/src/ffi_names.rs b/uniffi_meta/src/ffi_names.rs index 44a5bc3e63..ebc125410b 100644 --- a/uniffi_meta/src/ffi_names.rs +++ b/uniffi_meta/src/ffi_names.rs @@ -39,6 +39,12 @@ pub fn free_fn_symbol_name(namespace: &str, object_name: &str) -> String { format!("uniffi_{namespace}_fn_free_{object_name}") } +/// FFI symbol name for the `clone` function for an object. +pub fn clone_fn_symbol_name(namespace: &str, object_name: &str) -> String { + let object_name = object_name.to_ascii_lowercase(); + format!("uniffi_{namespace}_fn_clone_{object_name}") +} + /// FFI symbol name for the `init_callback` function for a callback interface pub fn init_callback_fn_symbol_name(namespace: &str, callback_interface_name: &str) -> String { let callback_interface_name = callback_interface_name.to_ascii_lowercase(); diff --git a/uniffi_meta/src/group.rs b/uniffi_meta/src/group.rs index f0be2e5a98..ff011911fc 100644 --- a/uniffi_meta/src/group.rs +++ b/uniffi_meta/src/group.rs @@ -18,6 +18,7 @@ pub fn create_metadata_groups(items: &[Metadata]) -> MetadataGroupMap { Metadata::Namespace(namespace) => { let group = MetadataGroup { namespace: namespace.clone(), + namespace_docstring: None, items: BTreeSet::new(), }; Some((namespace.crate_name.clone(), group)) @@ -29,6 +30,7 @@ pub fn create_metadata_groups(items: &[Metadata]) -> MetadataGroupMap { }; let group = MetadataGroup { namespace, + namespace_docstring: None, items: BTreeSet::new(), }; Some((udl.module_path.clone(), group)) @@ -63,6 +65,7 @@ pub fn group_metadata(group_map: &mut MetadataGroupMap, items: Vec) -> #[derive(Debug)] pub struct MetadataGroup { pub namespace: NamespaceMetadata, + pub namespace_docstring: Option, pub items: BTreeSet, } diff --git a/uniffi_meta/src/lib.rs b/uniffi_meta/src/lib.rs index 1c8a66801c..05a759fe59 100644 --- a/uniffi_meta/src/lib.rs +++ b/uniffi_meta/src/lib.rs @@ -143,6 +143,7 @@ pub struct FnMetadata { pub return_type: Option, pub throws: Option, pub checksum: Option, + pub docstring: Option, } impl FnMetadata { @@ -163,6 +164,7 @@ pub struct ConstructorMetadata { pub inputs: Vec, pub throws: Option, pub checksum: Option, + pub docstring: Option, } impl ConstructorMetadata { @@ -190,6 +192,7 @@ pub struct MethodMetadata { pub throws: Option, pub takes_self_by_arc: bool, // unused except by rust udl bindgen. pub checksum: Option, + pub docstring: Option, } impl MethodMetadata { @@ -216,6 +219,7 @@ pub struct TraitMethodMetadata { pub throws: Option, pub takes_self_by_arc: bool, // unused except by rust udl bindgen. pub checksum: Option, + pub docstring: Option, } impl TraitMethodMetadata { @@ -283,6 +287,7 @@ pub struct RecordMetadata { pub module_path: String, pub name: String, pub fields: Vec, + pub docstring: Option, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -290,6 +295,7 @@ pub struct FieldMetadata { pub name: String, pub ty: Type, pub default: Option, + pub docstring: Option, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -297,12 +303,14 @@ pub struct EnumMetadata { pub module_path: String, pub name: String, pub variants: Vec, + pub docstring: Option, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct VariantMetadata { pub name: String, pub fields: Vec, + pub docstring: Option, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -310,15 +318,25 @@ pub struct ObjectMetadata { pub module_path: String, pub name: String, pub imp: types::ObjectImpl, + pub docstring: Option, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct CallbackInterfaceMetadata { pub module_path: String, pub name: String, + pub docstring: Option, } impl ObjectMetadata { + /// FFI symbol name for the `clone` function for this object. + /// + /// This function is used to increment the reference count before lowering an object to pass + /// back to Rust. + pub fn clone_ffi_symbol_name(&self) -> String { + clone_fn_symbol_name(&self.module_path, &self.name) + } + /// FFI symbol name for the `free` function for this object. /// /// This function is used to free the memory used by this object. diff --git a/uniffi_meta/src/reader.rs b/uniffi_meta/src/reader.rs index bf6525f2b5..fc00af11be 100644 --- a/uniffi_meta/src/reader.rs +++ b/uniffi_meta/src/reader.rs @@ -205,6 +205,7 @@ impl<'a> MetadataReader<'a> { inputs, return_type, throws, + docstring: None, checksum: self.calc_checksum(), }) } @@ -232,6 +233,7 @@ impl<'a> MetadataReader<'a> { inputs, throws, checksum: self.calc_checksum(), + docstring: None, }) } @@ -252,6 +254,7 @@ impl<'a> MetadataReader<'a> { throws, takes_self_by_arc: false, // not emitted by macros checksum: self.calc_checksum(), + docstring: None, }) } @@ -260,6 +263,7 @@ impl<'a> MetadataReader<'a> { module_path: self.read_string()?, name: self.read_string()?, fields: self.read_fields()?, + docstring: None, }) } @@ -276,6 +280,7 @@ impl<'a> MetadataReader<'a> { module_path, name, variants, + docstring: None, }) } @@ -290,6 +295,7 @@ impl<'a> MetadataReader<'a> { module_path: self.read_string()?, name: self.read_string()?, imp: ObjectImpl::from_is_trait(self.read_bool()?), + docstring: None, }) } @@ -322,6 +328,7 @@ impl<'a> MetadataReader<'a> { Ok(CallbackInterfaceMetadata { module_path: self.read_string()?, name: self.read_string()?, + docstring: None, }) } @@ -344,6 +351,7 @@ impl<'a> MetadataReader<'a> { throws, takes_self_by_arc: false, // not emitted by macros checksum: self.calc_checksum(), + docstring: None, }) } @@ -354,7 +362,12 @@ impl<'a> MetadataReader<'a> { let name = self.read_string()?; let ty = self.read_type()?; let default = self.read_default(&name, &ty)?; - Ok(FieldMetadata { name, ty, default }) + Ok(FieldMetadata { + name, + ty, + default, + docstring: None, + }) }) .collect() } @@ -366,6 +379,7 @@ impl<'a> MetadataReader<'a> { Ok(VariantMetadata { name: self.read_string()?, fields: self.read_fields()?, + docstring: None, }) }) .collect() @@ -378,6 +392,7 @@ impl<'a> MetadataReader<'a> { Ok(VariantMetadata { name: self.read_string()?, fields: vec![], + docstring: None, }) }) .collect() diff --git a/uniffi_testing/Cargo.toml b/uniffi_testing/Cargo.toml index cfec745d2a..f235c38579 100644 --- a/uniffi_testing/Cargo.toml +++ b/uniffi_testing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_testing" -version = "0.25.0" +version = "0.25.2" authors = ["Firefox Sync Team "] description = "a multi-language bindings generator for rust (testing helpers)" documentation = "https://mozilla.github.io/uniffi-rs" diff --git a/uniffi_udl/Cargo.toml b/uniffi_udl/Cargo.toml index e3062a864b..2631ed82b2 100644 --- a/uniffi_udl/Cargo.toml +++ b/uniffi_udl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_udl" -version = "0.25.0" +version = "0.25.2" description = "udl parsing for the uniffi project" documentation = "https://mozilla.github.io/uniffi-rs" homepage = "https://mozilla.github.io/uniffi-rs" @@ -12,5 +12,6 @@ keywords = ["ffi", "bindgen"] [dependencies] anyhow = "1" weedle2 = { version = "4.0.0", path = "../weedle2" } -uniffi_meta = { path = "../uniffi_meta", version = "=0.25.0" } -uniffi_testing = { path = "../uniffi_testing", version = "=0.25.0" } +textwrap = "0.16" +uniffi_meta = { path = "../uniffi_meta", version = "=0.25.2" } +uniffi_testing = { path = "../uniffi_testing", version = "=0.25.2" } diff --git a/uniffi_udl/src/attributes.rs b/uniffi_udl/src/attributes.rs index 1d6b1d64cb..f341e4f007 100644 --- a/uniffi_udl/src/attributes.rs +++ b/uniffi_udl/src/attributes.rs @@ -37,6 +37,9 @@ pub(super) enum Attribute { kind: ExternalKind, export: bool, }, + Rust { + kind: RustKind, + }, // Custom type on the scaffolding side Custom, // The interface described is implemented as a trait. @@ -44,6 +47,17 @@ pub(super) enum Attribute { Async, } +// A type defined in Rust via procmacros but which should be available +// in UDL. +#[derive(Debug, Copy, Clone, Checksum)] +pub(super) enum RustKind { + Object, + Trait, + Record, + Enum, + CallbackInterface, +} + impl Attribute { pub fn is_error(&self) -> bool { matches!(self, Attribute::Error) @@ -97,6 +111,9 @@ impl TryFrom<&weedle::attribute::ExtendedAttribute<'_>> for Attribute { kind: ExternalKind::Interface, export: true, }), + "Rust" => Ok(Attribute::Rust { + kind: rust_kind_from_id_or_string(&identity.rhs)?, + }), _ => anyhow::bail!( "Attribute identity Identifier not supported: {:?}", identity.lhs_identifier.0 @@ -132,6 +149,25 @@ fn name_from_id_or_string(nm: &weedle::attribute::IdentifierOrString<'_>) -> Str } } +fn rust_kind_from_id_or_string(nm: &weedle::attribute::IdentifierOrString<'_>) -> Result { + Ok(match nm { + weedle::attribute::IdentifierOrString::String(str_lit) => match str_lit.0 { + // support names which match either procmacro or udl + "interface" => RustKind::Object, + "object" => RustKind::Object, + "record" => RustKind::Record, + "dictionary" => RustKind::Record, + "enum" => RustKind::Enum, + "trait" => RustKind::Trait, + "callback" => RustKind::CallbackInterface, + _ => anyhow::bail!("Unknown `[Rust=]` kind {:?}", str_lit.0), + }, + weedle::attribute::IdentifierOrString::Identifier(_) => { + anyhow::bail!("Expected string attribute value, got identifer") + } + }) +} + /// Parse a weedle `ExtendedAttributeList` into a list of `Attribute`s, /// erroring out on duplicates. fn parse_attributes( @@ -508,10 +544,18 @@ impl TypedefAttributes { }) } + pub(super) fn rust_kind(&self) -> Option { + self.0.iter().find_map(|attr| match attr { + Attribute::Rust { kind, .. } => Some(*kind), + _ => None, + }) + } + pub(super) fn external_tagged(&self) -> Option { // If it was "exported" via a proc-macro the FfiConverter was not tagged. self.0.iter().find_map(|attr| match attr { Attribute::External { export, .. } => Some(!*export), + Attribute::Rust { .. } => Some(false), _ => None, }) } @@ -523,7 +567,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for TypedefAttribute weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, ) -> Result { let attrs = parse_attributes(weedle_attributes, |attr| match attr { - Attribute::External { .. } | Attribute::Custom => Ok(()), + Attribute::External { .. } | Attribute::Custom | Attribute::Rust { .. } => Ok(()), _ => bail!(format!("{attr:?} not supported for typedefs")), })?; Ok(Self(attrs)) diff --git a/uniffi_udl/src/collectors.rs b/uniffi_udl/src/collectors.rs index 6a91ab4a93..1cb7630aea 100644 --- a/uniffi_udl/src/collectors.rs +++ b/uniffi_udl/src/collectors.rs @@ -5,7 +5,7 @@ //! # Collects metadata from UDL. use crate::attributes; -use crate::converters::APIConverter; +use crate::converters::{convert_docstring, APIConverter}; use crate::finder; use crate::resolver::TypeResolver; use anyhow::{bail, Result}; @@ -138,6 +138,7 @@ impl From for uniffi_meta::MetadataGroup { crate_name: value.types.module_path(), name: value.types.namespace, }, + namespace_docstring: value.types.namespace_docstring.clone(), items: value.items, } } @@ -218,6 +219,7 @@ impl APIBuilder for weedle::NamespaceDefinition<'_> { if self.identifier.0 != ci.types.namespace { bail!("duplicate namespace definition"); } + ci.types.namespace_docstring = self.docstring.as_ref().map(|v| convert_docstring(&v.0)); for func in self.members.body.convert(ci)? { ci.add_definition(func.into())?; } @@ -229,6 +231,7 @@ impl APIBuilder for weedle::NamespaceDefinition<'_> { pub(crate) struct TypeCollector { /// The unique prefix that we'll use for namespacing when exposing this component's API. pub namespace: String, + pub namespace_docstring: Option, pub crate_name: String, diff --git a/uniffi_udl/src/converters/callables.rs b/uniffi_udl/src/converters/callables.rs index f8c95b8cc4..27959901eb 100644 --- a/uniffi_udl/src/converters/callables.rs +++ b/uniffi_udl/src/converters/callables.rs @@ -5,6 +5,7 @@ use super::APIConverter; use crate::attributes::ArgumentAttributes; use crate::attributes::{ConstructorAttributes, FunctionAttributes, MethodAttributes}; +use crate::converters::convert_docstring; use crate::literal::convert_default_value; use crate::InterfaceCollector; use anyhow::{bail, Result}; @@ -41,6 +42,7 @@ impl APIConverter for weedle::argument::SingleArgument<'_> { name: self.identifier.0.to_string(), ty: type_, default: None, + docstring: None, }) } } @@ -104,6 +106,7 @@ impl APIConverter for weedle::namespace::OperationNamespaceMember<'_ return_type, inputs: self.args.body.list.convert(ci)?, throws, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), checksum: None, }) } @@ -127,6 +130,7 @@ impl APIConverter for weedle::interface::ConstructorInterfa inputs: self.args.body.list.convert(ci)?, throws, checksum: None, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } @@ -172,6 +176,7 @@ impl APIConverter for weedle::interface::OperationInterfaceMembe throws, takes_self_by_arc, checksum: None, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } @@ -217,6 +222,7 @@ impl APIConverter for weedle::interface::OperationInterface throws, takes_self_by_arc, checksum: None, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } diff --git a/uniffi_udl/src/converters/enum_.rs b/uniffi_udl/src/converters/enum_.rs index a3e68fd23e..10179b1d54 100644 --- a/uniffi_udl/src/converters/enum_.rs +++ b/uniffi_udl/src/converters/enum_.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::APIConverter; -use crate::InterfaceCollector; +use crate::{converters::convert_docstring, InterfaceCollector}; use anyhow::{bail, Result}; use uniffi_meta::{EnumMetadata, ErrorMetadata, VariantMetadata}; @@ -23,11 +23,13 @@ impl APIConverter for weedle::EnumDefinition<'_> { .iter() .map::, _>(|v| { Ok(VariantMetadata { - name: v.0.to_string(), + name: v.value.0.to_string(), fields: vec![], + docstring: v.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) }) .collect::>>()?, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } @@ -45,11 +47,13 @@ impl APIConverter for weedle::EnumDefinition<'_> { .iter() .map::, _>(|v| { Ok(VariantMetadata { - name: v.0.to_string(), + name: v.value.0.to_string(), fields: vec![], + docstring: v.docstring.as_ref().map(|v| v.0.clone()), }) }) .collect::>>()?, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }, is_flat: true, }) @@ -78,6 +82,7 @@ impl APIConverter for weedle::InterfaceDefinition<'_> { ), }) .collect::>>()?, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), // Enums declared using the `[Enum] interface` syntax might have variants with fields. //flat: false, }) @@ -107,6 +112,7 @@ impl APIConverter for weedle::InterfaceDefinition<'_> { ), }) .collect::>>()?, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }, is_flat: false, }) diff --git a/uniffi_udl/src/converters/interface.rs b/uniffi_udl/src/converters/interface.rs index 58e6a9c8a0..d0d7493428 100644 --- a/uniffi_udl/src/converters/interface.rs +++ b/uniffi_udl/src/converters/interface.rs @@ -4,7 +4,7 @@ use super::APIConverter; use crate::attributes::InterfaceAttributes; -use crate::InterfaceCollector; +use crate::{converters::convert_docstring, InterfaceCollector}; use anyhow::{bail, Result}; use std::collections::HashSet; use uniffi_meta::{ @@ -70,6 +70,7 @@ impl APIConverter for weedle::InterfaceDefinition<'_> { throws: None, takes_self_by_arc: false, checksum: None, + docstring: None, }) }; // Trait methods are in the Metadata. @@ -130,6 +131,7 @@ impl APIConverter for weedle::InterfaceDefinition<'_> { module_path: ci.module_path(), name: object_name.to_string(), 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 7a2d22ac42..4e38fd624d 100644 --- a/uniffi_udl/src/converters/mod.rs +++ b/uniffi_udl/src/converters/mod.rs @@ -29,6 +29,11 @@ pub(crate) trait APIConverter { fn convert(&self, ci: &mut InterfaceCollector) -> Result; } +// Convert UDL docstring into metadata docstring +pub(crate) fn convert_docstring(docstring: &str) -> String { + textwrap::dedent(docstring) +} + /// Convert a list of weedle items into a list of `InterfaceCollector` items, /// by doing a direct item-by-item mapping. impl> APIConverter> for Vec { @@ -79,6 +84,7 @@ impl APIConverter for weedle::interface::OperationInterfaceMemb .iter() .map(|arg| arg.convert(ci)) .collect::>>()?, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } @@ -95,6 +101,7 @@ impl APIConverter for weedle::DictionaryDefinition<'_> { module_path: ci.module_path(), name: self.identifier.0.to_string(), fields: self.members.body.convert(ci)?, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } @@ -113,6 +120,7 @@ impl APIConverter for weedle::dictionary::DictionaryMember<'_> { name: self.identifier.0.to_string(), ty: type_, default, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } @@ -150,6 +158,7 @@ impl APIConverter for weedle::CallbackInterfaceDefini Ok(CallbackInterfaceMetadata { module_path: ci.module_path(), name: object_name.to_string(), + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } diff --git a/uniffi_udl/src/finder.rs b/uniffi_udl/src/finder.rs index 0c4c187dc0..fd369f7ca6 100644 --- a/uniffi_udl/src/finder.rs +++ b/uniffi_udl/src/finder.rs @@ -22,8 +22,8 @@ use std::convert::TryFrom; use anyhow::{bail, Result}; use super::TypeCollector; -use crate::attributes::{InterfaceAttributes, TypedefAttributes}; -use uniffi_meta::Type; +use crate::attributes::{InterfaceAttributes, RustKind, TypedefAttributes}; +use uniffi_meta::{ObjectImpl, Type}; /// Trait to help with an early "type discovery" phase when processing the UDL. /// @@ -111,7 +111,6 @@ impl TypeFinder for weedle::EnumDefinition<'_> { impl TypeFinder for weedle::TypedefDefinition<'_> { fn add_type_definitions_to(&self, types: &mut TypeCollector) -> Result<()> { - let name = self.identifier.0; let attrs = TypedefAttributes::try_from(self.attributes.as_ref())?; // If we wanted simple `typedef`s, it would be as easy as: // > let t = types.resolve_type_expression(&self.type_)?; @@ -122,29 +121,47 @@ impl TypeFinder for weedle::TypedefDefinition<'_> { // `FfiConverter` implementation. let builtin = types.resolve_type_expression(&self.type_)?; types.add_type_definition( - name, + self.identifier.0, Type::Custom { module_path: types.module_path(), - name: name.to_string(), + name: self.identifier.0.to_string(), builtin: builtin.into(), }, ) } else { - let kind = attrs.external_kind().expect("External missing"); - let tagged = attrs.external_tagged().expect("External missing"); + let module_path = types.module_path(); + let name = self.identifier.0.to_string(); + let ty = match attrs.rust_kind() { + Some(RustKind::Object) => Type::Object { + module_path, + name, + imp: ObjectImpl::Struct, + }, + Some(RustKind::Trait) => Type::Object { + module_path, + name, + imp: ObjectImpl::Trait, + }, + Some(RustKind::Record) => Type::Record { module_path, name }, + Some(RustKind::Enum) => Type::Enum { module_path, name }, + Some(RustKind::CallbackInterface) => Type::CallbackInterface { module_path, name }, + // must be external + None => { + let kind = attrs.external_kind().expect("External missing kind"); + let tagged = attrs.external_tagged().expect("External missing tagged"); + Type::External { + name, + namespace: "".to_string(), // we don't know this yet + module_path: attrs.get_crate_name(), + kind, + tagged, + } + } + }; // A crate which can supply an `FfiConverter`. // We don't reference `self._type`, so ideally we could insist on it being // the literal 'extern' but that's tricky - types.add_type_definition( - name, - Type::External { - name: name.to_string(), - namespace: "".to_string(), // we don't know this yet - module_path: attrs.get_crate_name(), - kind, - tagged, - }, - ) + types.add_type_definition(self.identifier.0, ty) } } } @@ -152,7 +169,7 @@ impl TypeFinder for weedle::TypedefDefinition<'_> { impl TypeFinder for weedle::CallbackInterfaceDefinition<'_> { fn add_type_definitions_to(&self, types: &mut TypeCollector) -> Result<()> { if self.attributes.is_some() { - bail!("no typedef attributes are currently supported"); + bail!("no callback interface attributes are currently supported"); } let name = self.identifier.0.to_string(); types.add_type_definition( diff --git a/weedle2/src/common.rs b/weedle2/src/common.rs index fadf89ba8b..d36f6e5438 100644 --- a/weedle2/src/common.rs +++ b/weedle2/src/common.rs @@ -33,6 +33,18 @@ impl<'a, T: Parse<'a>, U: Parse<'a>, V: Parse<'a>> Parse<'a> for (T, U, V) { parser!(nom::sequence::tuple((T::parse, U::parse, V::parse))); } +pub(crate) fn docstring(input: &str) -> IResult<&str, String> { + nom::multi::many1(nom::sequence::preceded( + nom::character::complete::multispace0, + nom::sequence::delimited( + nom::bytes::complete::tag("///"), + nom::bytes::complete::take_until("\n"), + nom::bytes::complete::tag("\n"), + ), + ))(input) + .map(|io| (io.0, io.1.join("\n"))) +} + ast_types! { /// Parses `( body )` #[derive(Copy, Default)] @@ -103,6 +115,11 @@ ast_types! { assign: term!(=), value: DefaultValue<'a>, } + + /// Represents consecutive comment lines starting with `///`, joined by `\n`. + struct Docstring( + String = docstring, + ) } #[cfg(test)] @@ -211,4 +228,32 @@ mod test { Identifier; 0 == "hello"; }); + + test!(should_parse_docstring { "///hello world\n" => + ""; + Docstring; + 0 == "hello world"; + }); + + test!(should_parse_multiline_docstring { "///hello\n///world\n" => + ""; + Docstring; + 0 == "hello\nworld"; + }); + + test!(should_parse_multiline_indented_docstring { "///hello\n ///world\n" => + ""; + Docstring; + 0 == "hello\nworld"; + }); + + test!(should_not_parse_docstring_with_comments { "///hello\n//comment1\n///world\n" => + "//comment1\n///world\n"; + Docstring; + 0 == "hello"; + }); + + test!(err should_not_parse_not_docstring { "" => + Docstring + }); } diff --git a/weedle2/src/dictionary.rs b/weedle2/src/dictionary.rs index 3c9b23cac5..b775d526dc 100644 --- a/weedle2/src/dictionary.rs +++ b/weedle2/src/dictionary.rs @@ -1,5 +1,5 @@ use crate::attribute::ExtendedAttributeList; -use crate::common::{Default, Identifier}; +use crate::common::{Default, Docstring, Identifier}; use crate::types::Type; /// Parses dictionary members @@ -8,6 +8,7 @@ pub type DictionaryMembers<'a> = Vec>; ast_types! { /// Parses dictionary member `[attributes]? required? type identifier ( = default )?;` struct DictionaryMember<'a> { + docstring: Option, attributes: Option>, required: Option, type_: Type<'a>, diff --git a/weedle2/src/interface.rs b/weedle2/src/interface.rs index 5e30909c38..ab3c10d3c3 100644 --- a/weedle2/src/interface.rs +++ b/weedle2/src/interface.rs @@ -1,6 +1,6 @@ use crate::argument::ArgumentList; use crate::attribute::ExtendedAttributeList; -use crate::common::{Generics, Identifier, Parenthesized}; +use crate::common::{Docstring, Generics, Identifier, Parenthesized}; use crate::literal::ConstValue; use crate::types::{AttributedType, ConstType, ReturnType}; @@ -41,6 +41,7 @@ ast_types! { /// /// (( )) means ( ) chars Constructor(struct ConstructorInterfaceMember<'a> { + docstring: Option, attributes: Option>, constructor: term!(constructor), args: Parenthesized>, @@ -50,6 +51,7 @@ ast_types! { /// /// (( )) means ( ) chars Operation(struct OperationInterfaceMember<'a> { + docstring: Option, attributes: Option>, modifier: Option, special: Option, diff --git a/weedle2/src/lib.rs b/weedle2/src/lib.rs index 610a34fa14..71ab7c33b5 100644 --- a/weedle2/src/lib.rs +++ b/weedle2/src/lib.rs @@ -23,7 +23,7 @@ use self::argument::ArgumentList; use self::attribute::ExtendedAttributeList; -use self::common::{Braced, Identifier, Parenthesized, PunctuatedNonEmpty}; +use self::common::{Braced, Docstring, Identifier, Parenthesized, PunctuatedNonEmpty}; use self::dictionary::DictionaryMembers; use self::interface::{Inheritance, InterfaceMembers}; use self::literal::StringLit; @@ -109,6 +109,7 @@ ast_types! { }), /// Parses `[attributes]? callback interface identifier ( : inheritance )? { members };` CallbackInterface(struct CallbackInterfaceDefinition<'a> { + docstring: Option, attributes: Option>, callback: term!(callback), interface: term!(interface), @@ -119,6 +120,7 @@ ast_types! { }), /// Parses `[attributes]? interface identifier ( : inheritance )? { members };` Interface(struct InterfaceDefinition<'a> { + docstring: Option, attributes: Option>, interface: term!(interface), identifier: Identifier<'a>, @@ -137,6 +139,7 @@ ast_types! { }), /// Parses `[attributes]? namespace identifier { members };` Namespace(struct NamespaceDefinition<'a> { + docstring: Option, attributes: Option>, namespace: term!(namespace), identifier: Identifier<'a>, @@ -145,6 +148,7 @@ ast_types! { }), /// Parses `[attributes]? dictionary identifier ( : inheritance )? { members };` Dictionary(struct DictionaryDefinition<'a> { + docstring: Option, attributes: Option>, dictionary: term!(dictionary), identifier: Identifier<'a>, @@ -191,6 +195,7 @@ ast_types! { }), /// Parses `[attributes]? enum identifier { values };` Enum(struct EnumDefinition<'a> { + docstring: Option, attributes: Option>, enum_: term!(enum), identifier: Identifier<'a>, @@ -224,8 +229,15 @@ ast_types! { } } +ast_types! { + struct EnumVariant<'a> { + docstring: Option, + value: StringLit<'a>, + } +} + /// Parses a non-empty enum value list -pub type EnumValueList<'a> = PunctuatedNonEmpty, term!(,)>; +pub type EnumValueList<'a> = PunctuatedNonEmpty, term!(,)>; #[cfg(test)] mod test { diff --git a/weedle2/src/namespace.rs b/weedle2/src/namespace.rs index ed28573218..60673cdcec 100644 --- a/weedle2/src/namespace.rs +++ b/weedle2/src/namespace.rs @@ -1,6 +1,6 @@ use crate::argument::ArgumentList; use crate::attribute::ExtendedAttributeList; -use crate::common::{Identifier, Parenthesized}; +use crate::common::{Docstring, Identifier, Parenthesized}; use crate::types::{AttributedType, ReturnType}; /// Parses namespace members declaration @@ -13,6 +13,7 @@ ast_types! { /// /// (( )) means ( ) chars Operation(struct OperationNamespaceMember<'a> { + docstring: Option, attributes: Option>, return_type: ReturnType<'a>, identifier: Option>, @@ -21,6 +22,7 @@ ast_types! { }), /// Parses `[attribute]? readonly attributetype type identifier;` Attribute(struct AttributeNamespaceMember<'a> { + docstring: Option, attributes: Option>, readonly: term!(readonly), attribute: term!(attribute), diff --git a/weedle2/src/whitespace.rs b/weedle2/src/whitespace.rs index 336e4784e1..4be3ca43e8 100644 --- a/weedle2/src/whitespace.rs +++ b/weedle2/src/whitespace.rs @@ -7,6 +7,7 @@ pub(crate) fn sp(input: &str) -> IResult<&str, &str> { (), nom::sequence::tuple(( nom::bytes::complete::tag("//"), + nom::combinator::not(nom::bytes::complete::tag("/")), nom::bytes::complete::take_until("\n"), nom::bytes::complete::tag("\n"), )),