diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a1b47eed4..432d8242d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,28 @@ ## [[UnreleasedUniFFIVersion]] (backend crates: [[UnreleasedBackendVersion]]) - (_[[ReleaseDate]]_) -[All changes in [[UnreleasedUniFFIVersion]]](https://github.com/mozilla/uniffi-rs/compare/v0.28.0...HEAD). +[All changes in [[UnreleasedUniFFIVersion]]](https://github.com/mozilla/uniffi-rs/compare/v0.28.1...HEAD). -## v0.28.0 (backend crates: v0.28.0) - (_2024-06-11_) +## v0.28.1 (backend crates: v0.28.1) - (_2024-08-09_) ### What's new? +- Lift errors will not cause an abort when `panic=abort` is set. +- Added the `cargo_metadata` feature, which is on by default. In some cases, this can be disabled + for better compatibility with projects that don't use cargo. +- A new bindgen command line option `--metadata-no-deps` is available to avoid processing + cargo_metadata for all dependencies. +- In UDL it's now possible (and preferred) to remove the `[Rust=]` attribute and use a plain-old typedef. + See [the manual page for this](https://mozilla.github.io/uniffi-rs/next/udl/ext_types.html#types-from-procmacros-in-this-crate). + +### What's changed? +- Kotlin will use the more efficient Enum.entries property instead of Enum.values() when possible + +[All changes in v0.28.1](https://github.com/mozilla/uniffi-rs/compare/v0.28.0...v0.28.1). + +## v0.28.0 (backend crates: v0.28.0) - (_2024-06-11_) + +### What's new? - Objects error types can now be as `Result<>` error type without wrapping them in `Arc<>`. - Swift errors now provide `localizedDescription` ([#2116](https://github.com/mozilla/uniffi-rs/pull/2116)) diff --git a/Cargo.lock b/Cargo.lock index ec5562171a..18c01bc4a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1471,10 +1471,11 @@ dependencies = [ [[package]] name = "uniffi" -version = "0.28.0" +version = "0.28.1" dependencies = [ "anyhow", "camino", + "cargo_metadata", "clap", "trybuild", "uniffi_bindgen", @@ -1626,6 +1627,7 @@ dependencies = [ name = "uniffi-fixture-docstring-proc-macro" version = "0.22.0" dependencies = [ + "cargo_metadata", "glob", "thiserror", "uniffi", @@ -1784,6 +1786,13 @@ dependencies = [ "uniffi", ] +[[package]] +name = "uniffi-fixture-proc-macro-no-implicit-prelude" +version = "0.22.0" +dependencies = [ + "uniffi", +] + [[package]] name = "uniffi-fixture-regression-callbacks-omit-labels" version = "0.22.0" @@ -1916,7 +1925,7 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.28.0" +version = "0.28.1" dependencies = [ "anyhow", "askama", @@ -1938,7 +1947,7 @@ dependencies = [ [[package]] name = "uniffi_build" -version = "0.28.0" +version = "0.28.1" dependencies = [ "anyhow", "camino", @@ -1947,7 +1956,7 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.28.0" +version = "0.28.1" dependencies = [ "quote", "syn", @@ -1955,7 +1964,7 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.28.0" +version = "0.28.1" dependencies = [ "anyhow", "async-compat", @@ -1969,7 +1978,7 @@ dependencies = [ [[package]] name = "uniffi_macros" -version = "0.28.0" +version = "0.28.1" dependencies = [ "bincode", "camino", @@ -1986,7 +1995,7 @@ dependencies = [ [[package]] name = "uniffi_meta" -version = "0.28.0" +version = "0.28.1" dependencies = [ "anyhow", "bytes", @@ -1996,7 +2005,7 @@ dependencies = [ [[package]] name = "uniffi_testing" -version = "0.28.0" +version = "0.28.1" dependencies = [ "anyhow", "camino", @@ -2007,7 +2016,7 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.28.0" +version = "0.28.1" dependencies = [ "anyhow", "textwrap", diff --git a/Cargo.toml b/Cargo.toml index b2fb6d899a..1160898623 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ members = [ "fixtures/keywords/swift", "fixtures/metadata", "fixtures/proc-macro", + "fixtures/proc-macro-no-implicit-prelude", "fixtures/regressions/enum-without-i32-helpers", "fixtures/regressions/fully-qualified-types", "fixtures/regressions/kotlin-experimental-unsigned-types", diff --git a/docs/adr/0009-remote-types-interfaces-and-proc-macros.md b/docs/adr/0009-remote-types-interfaces-and-proc-macros.md new file mode 100644 index 0000000000..a46157f458 --- /dev/null +++ b/docs/adr/0009-remote-types-interfaces-and-proc-macros.md @@ -0,0 +1,275 @@ +# Remote types: proc-macros and interfaces + +* Status: proposed +* Deciders: Ben Dean-Kawamura, Mark Hammond, Jonas Platte, Alexander Cyon +* Date: 2024-05-24 + +Discussion and approval: [PR 2130](https://github.com/mozilla/uniffi-rs/pull/2130) + +## Context and Problem Statement + +We want to expose APIs which are described naturally in our Rust implementation. For example, + +```rust +fn set_level(level: log::Level) -> Result<(), anyhow::Error> { ... } +``` + +(or maybe `serde::Value` etc) - things naturally expressed by our implementation. + +These `Error/Level/Value`s are "remote types" -- types defined in 3rd-party crates -- and so require special handling from UniFFI. + +One reason, discussed in ADR-0006, is the Rust orphan rule, but the more fundamental reason +is that UniFFI needs to know about the enough about the type to generate the FFI. +As we will discuss, UDL helps with metadata collection, but that still leaves proc-macros. + +This ADR will explore: + - Adding support for collecting this metadata for proc-macro-based generation + - Adding interface type support to both UDL and proc-macros. + +## The current state + +UniFFI currently supports re-declaring remote records/enums in UDL files using the normal syntax. +For example, users can use `Log::Level` in their interface by creating a type alias `type LogLevel = log::Level`, then adding this definition to the UDL: + + +```idl +enum LogLevel { + "Error", + "Warn", + "Info", + "Debug", + "Trace", +} +``` + +UniFFI exposed functions/structs/etc could then use `log::Level` as a param/struct member/etc directly in the API. + +Proc-macros obviously can't arrange for a `#[derive(uniffi::Enum)]]` around `log::Level`, +or a `#[uniffi::export]` around `anyhow::Error`, but we want some way of making that work. + +## Considered Options + +### [Option 1] expose remote types directly + +We could continue to expose remote types directly, similar to how it currently works in UDL. +One issue here is that proc-macro generation is based attributes that wrap an item, however there's no way for a user to add an attribute to a remote type. +However, macros can work around this issue. + +```rust +type LogLevel = log::Level; + +#[uniffi::remote] +pub enum LogLevel { + Error = 1, + Warn = 2, + Info = 3, + Debug = 4, + Trace = 5, +} +``` + +The `remote` macro would generate all scaffolding code needed to handle `LogLevel`. +The `enum LogLevel` item would not end up in the expanded code. + +This could also work for interfaces: + +```rust +type AnyhowError = anyhow::Error; + +#[uniffi::remote] +impl AnyhowError { + // Expose the `to_string` method (technically, `to_string` comes from the `Display` trait, but that + // doesn't matter for foreign consumers. Since the item definition is not used for the + // scaffolding code and will not be present in the expanded code, it can be left empty. + pub fn to_string(&self) -> String; +} +``` + +One issue with this approach is that most methods defined by the source crate will not be compatible with UniFFI. +To get around this, UniFFI will allow defining extra methods for the FFI: + +```rust +type AnyhowError = anyhow::Error; + +#[uniffi::remote] +impl AnyhowError { + // [anyhow::Error::is] is a generic method, which can't be exported by UniFFI. + // Instead, define extra FFI methods which forward to the original method. + // This code will result in extra scaffolding methods being generated that can be called from foreign languages. + // These methods will not be callable from Rust. + + fn is_foo_error(&self) -> bool { + self.is::() + } + + fn is_bar_error(&self) -> bool { + self.is::() + } +} +``` + +### [Option 1a] use a function-style macro + +The same idea could also be spelled out using a function-style macro rather than an attribute macro: + +```rust +type LogLevel = log::Level; + +uniffi::remote!( + pub enum LogLevel { + Error = 1, + Warn = 2, + Info = 3, + Debug = 4, + Trace = 5, + } +); + +type AnyhowError = anyhow::Error; + +uniffi::remote!( + impl AnyhowError { + pub fn to_string(&self) -> String; + + fn is_foo_error(&self) -> bool { + self.is::() + } + + fn is_bar_error(&self) -> bool { + self.is::() + } + } +); +``` + +### [Option 2] use custom-type conversion to expose the type + +An alternate strategy would be to use a custom-type conversion from that type into a local type that does implement the UniFFI traits. +These examples will use the custom type syntax from #2087, since I think it looks nicer than the current `UniffiCustomTypeConverter` based code. + +```rust +/// Define a type that mirrors `Log::Level` +#[derive(uniffi::Enum)] +pub enum LogLevel { + Error = 1, + Warn = 2, + Info = 3, + Debug = 4, + Trace = 5, +} + +/// Define a custom type conversion from `log::Level` to the above type. +uniffi::custom_type!(log::Level, LogLevel, { + from_custom: |l| match l { + log::Level::Error => LogLevel::Error, + log::Level::Warn => LogLevel::Warn, + log::Level::Info => LogLevel::Info, + log::Level::Debug => LogLevel::Debug, + log::Level::Trace => LogLevel::Trace, + }, + try_into_custom: |l| Ok(match l ({ + LogLevel::Error => log::Level::Error, + LogLevel::Warn => log::Level::Warn, + LogLevel::Info => log::Level::Info, + LogLevel::Debug => log::Level::Debug, + LogLevel::Trace => log::Level::Trace, + }) +}) + +/// Interfaces can use the newtype pattern +#[derive(uniffi::Object)] +pub struct AnyhowError(anyhow::Error); + +uniffi::custom_newtype!(anyhow::Error, AnyhowError). + +// We can define methods directly with this approach, no need for extension traits. +#[uniffi::export] +impl AnyhowError { + fn is_foo_error(&self) -> bool { + self.0.is::() + } + + fn is_bar_error(&self) -> bool { + self.0.is::() + } + + fn message(&self) -> String { + self.0.to_string() + } +} +``` + +#### Two types + +One drawback of this approach is that we have to equivalent, but different types. +Rust code would need to use `anyhow::Error` in their signatures, while foreign code would use `AnyhowError`. +Since the types are almost exactly the same, but named slightly different and with slightly different methods, it can be awkward to document this distinction -- both by UniFFI for library authors and by library authors for their consumers. + +### [Option 3] hybrid approach + +We could try to combine the best of both worlds by using option 2 for records/structs and option 1 for interfaces. + +## Pros and Cons of the Options + +### [Option 1] expose remote types directly + +* Good, because both the foreign code and Rust code can use the same type names. +* Good, because it has a low amount of boilerplate code (assuming we provide the `remote_extend!` macro). +* Bad, because we need to define extension traits for remote interfaces types. +* Bad, because it can be confusing to see a type declaration that the `uniffi::remote!` macro will eventually throw away. + +### [Option 1a] use an function-like macro + +(compared to option 1) + +* Bad, because the item declaration looks less natural. +* Good, since it makes it a bit more obvious that the item declaration will be thrown away. + +### [Option 2] use custom-type conversion to expose the type + +* Good, because adding methods to remote interface types is natural. +* Bad, because having two equivalent but different types could cause confusion. +* Bad, because users have to write out the trivial struct/enum conversions. + +### [Option 3] hybrid approach + +* Good, because adding methods to remote interface types is natural. +* Good, because both the foreign code and Rust code can use the same type names for struct/record types. +* Bad, because there will be two types for interface types. +* Good, because it has a low amount of boilerplate code. +* Bad, because mixing the two systems increases the overall complexity and risk of confusion. + +## Decision Outcome + +Option 1 + +## Discussion: how to extend remote interfaces + +Option 1 proposes extending remote interfaces by adding FFI methods. +An alternative would be to use extension traits, for example: + +```rust +pub trait AnyhowErrorExt { + fn is_foo_error(&self) -> bool; + fn is_bar_error(&self) -> bool; +} + +impl AnyhowErrorExt for anyhow::Error { + fn is_foo_error(&self) -> bool { + self.is::() + } + + fn is_bar_error(&self) -> bool { + self.is::() + } +} +``` + +This extension trait code could be generated by UniFFI using the same macro syntax. The main +difference between the two approaches is that the extension trait methods could be called from Rust +(though users might have to jump through some hoops to import the trait), while FFI methods could +only be called from the foreign language. + +We decided to propose FFI method solution, since in most cases there's no reason to call these +methods from Rust. Users can always define extension traits by hand, which is the recommended +solution for traits that are intended to be used by other Rust code. diff --git a/docs/manual/src/Motivation.md b/docs/manual/src/Motivation.md index 1cea93d61c..d10edb0a49 100644 --- a/docs/manual/src/Motivation.md +++ b/docs/manual/src/Motivation.md @@ -28,11 +28,11 @@ continues to evolve. Using UniFFI, you can: * Implement your software component as a `cdylib` crate in Rust; let's say the code is in `./src/lib.rs`. -* Specify the desired component API using an *Interface Definition Language* (specifically, a variant of WebIDL) in a separate file like `./src/lib.udl`. -* Run `uniffi-bindgen scaffolding ./src/lib.udl` to generate a bunch of boilerplate rust code that exposes this API as a C-compatible FFI layer, - and include it as part of your crate. +* Optionally, describe parts of your component API using proc-macros directly in `lib.rs`. +* Optionally, describe parts of your component API using an *Interface Definition Language* in a separate file like `./src/lib.udl`. UniFFI will generate a bunch of boilerplate Rust code that exposes this API as a C-compatible FFI layer, and include it as part of your crate. * `cargo build` your crate as normal to produce a shared library. -* Run `uniffi-bindgen generate ./src/lib.udl -l kotlin` to generate a Kotlin library that can load your shared library +* Run `uniffi-bindgen generate ... -l kotlin` (see [the bindgen docs](./tutorial/foreign_language_bindings.md) for omitted arg details) + to generate a Kotlin library that can load your shared library and expose it to Kotlin code using your nice high-level component API! * Or `-l swift` or `-l python` to produce bindings for other languages. diff --git a/docs/manual/src/futures.md b/docs/manual/src/futures.md index 81d88b2a18..b2a5163d57 100644 --- a/docs/manual/src/futures.md +++ b/docs/manual/src/futures.md @@ -94,3 +94,13 @@ Use `uniffi_set_event_loop()` to handle this case. It should be called before the Rust code makes the async call and passed an eventloop to use. Note that `uniffi_set_event_loop` cannot be glob-imported because it's not part of the library's `__all__`. + +## Cancelling async code. + +We don't directly support cancellation in UniFFI even when the underlying platforms do. +You should build your cancellation in a separate, library specific channel; for example, exposing a `cancel()` method that sets a flag that the library checks periodically. + +Cancellation can then be exposed in the API and be mapped to one of the error variants, or None/empty-vec/whatever makes sense. +There's no builtin way to cancel a future, nor to cause/raise a platform native async cancellation error (eg, a swift `CancellationError`). + +See also https://github.com/mozilla/uniffi-rs/pull/1768. diff --git a/docs/manual/src/index.md b/docs/manual/src/index.md index e6cc886b5c..c0fa836645 100644 --- a/docs/manual/src/index.md +++ b/docs/manual/src/index.md @@ -3,12 +3,14 @@ UniFFI is a tool that automatically generates foreign-language bindings targeting Rust libraries. The repository can be found on [github](https://github.com/mozilla/uniffi-rs/). It fits in the practice of consolidating business logic in a single Rust library while targeting multiple platforms, making it simpler to develop and maintain a cross-platform codebase. -Note that this tool will not help you ship a Rust library to these platforms, but simply not have to write bindings code by hand. [Related](https://i.kym-cdn.com/photos/images/newsfeed/000/572/078/d6d.jpg). +Note that this tool will not help you ship a Rust library to these platforms, but it will help you avoid writing bindings code by hand. +[Related](https://i.kym-cdn.com/photos/images/newsfeed/000/572/078/d6d.jpg). ## Design -UniFFI requires to write an Interface Definition Language (based on [WebIDL](https://heycam.github.io/webidl/)) file describing the methods and data structures available to the targeted languages. -This .udl (UniFFI Definition Language) file, whose definitions must match with the exposed Rust code, is then used to generate Rust *scaffolding* code and foreign-languages *bindings*. This process can take place either during the build process or be manually initiated by the developer. +UniFFI requires you to describe your interface via either proc-macros or in an Interface Definition Language (based on [WebIDL](https://webidl.spec.whatwg.org/)) file. +These definitions describe the methods and data structures available to the targeted languages, and are used to generate Rust *scaffolding* code and foreign-language *bindings*. +This process can take place either during the build process or be manually initiated by the developer. ![uniffi diagram](./uniffi_diagram.png) diff --git a/docs/manual/src/kotlin/configuration.md b/docs/manual/src/kotlin/configuration.md index 19ae2a745b..3473873516 100644 --- a/docs/manual/src/kotlin/configuration.md +++ b/docs/manual/src/kotlin/configuration.md @@ -4,15 +4,16 @@ The generated Kotlin modules can be configured using a `uniffi.toml` configurati ## Available options -| Configuration name | Default | Description | -| ------------------ | ------- |------------ | -| `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) -| `android` | `false` | Used to toggle on Android specific optimizations -| `android_cleaner` | `android` | Use the [`android.system.SystemCleaner`](https://developer.android.com/reference/android/system/SystemCleaner) instead of [`java.lang.ref.Cleaner`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ref/Cleaner.html). Fallback in both instances is the one shipped with JNA. +| Configuration name | Default | Description | +|------------------------------|--------------------------|------------ | +| `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) +| `android` | `false` | Used to toggle on Android specific optimizations +| `android_cleaner` | `android` | Use the [`android.system.SystemCleaner`](https://developer.android.com/reference/android/system/SystemCleaner) instead of [`java.lang.ref.Cleaner`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ref/Cleaner.html). Fallback in both instances is the one shipped with JNA. +| `kotlin_target_version` | `"x.y.z"` | When provided, it will enable features in the bindings supported for this version. The build process will fail if an invalid format is used. ## Example diff --git a/docs/manual/src/proc_macro/index.md b/docs/manual/src/proc_macro/index.md index caa9ceeafc..264a2921f8 100644 --- a/docs/manual/src/proc_macro/index.md +++ b/docs/manual/src/proc_macro/index.md @@ -107,7 +107,7 @@ Arguments and receivers can also be references to these types, for example: ```rust // Input data types as references #[uniffi::export] -fn process_data(a: &MyRecord, b: &MyEnum, c: Option<&MyRecord>) { +fn process_data(a: &MyRecord, b: &MyEnum, c: &Option) { ... } diff --git a/docs/manual/src/tutorial/foreign_language_bindings.md b/docs/manual/src/tutorial/foreign_language_bindings.md index b2ab9d3b5a..53a9f47f53 100644 --- a/docs/manual/src/tutorial/foreign_language_bindings.md +++ b/docs/manual/src/tutorial/foreign_language_bindings.md @@ -8,9 +8,9 @@ The next step is to have UniFFI generate source code for your foreign language. First, make sure you have installed all the [prerequisites](./Prerequisites.md). -Ideally you would then run the `uniffi-bindgen` binary from the `uniffi` crate to generate your bindings. However, this -is only available with [Cargo nightly](https://doc.rust-lang.org/cargo/reference/unstable.html#artifact-dependencies). -To work around this, you need to create a binary in your project that does the same thing. +Ideally you would then run the `uniffi-bindgen` binary from the `uniffi` crate to generate your bindings, +but if not on [Cargo nightly](https://doc.rust-lang.org/cargo/reference/unstable.html#artifact-dependencies), +you need to create a binary in your project that does the same thing. Add the following to your `Cargo.toml`: @@ -32,11 +32,10 @@ You can now run `uniffi-bindgen` from your project using `cargo run --features=u ### Multi-crate workspaces -If your project consists of multiple crates in a Cargo workspace, then the process outlined above would require you -creating a binary for each crate that uses UniFFI. You can avoid this by creating a separate crate for running `uniffi-bindgen`: - - Name the crate `uniffi-bindgen` +In a multiple crates workspace, you can create a separate crate for running `uniffi-bindgen`: + - Name the crate `uniffi-bindgen`, add it to your workspace. - Add this dependency to `Cargo.toml`: `uniffi = {version = "0.XX.0", features = ["cli"] }` - - Follow the steps from the previous section to add the `uniffi-bindgen` binary target + - As above, add the `uniffi-bindgen` binary target Then your can run `uniffi-bindgen` from any crate in your project using `cargo run -p uniffi-bindgen [args]` diff --git a/docs/manual/src/udl/errors.md b/docs/manual/src/udl/errors.md index 5ee67cc660..d96cc36948 100644 --- a/docs/manual/src/udl/errors.md +++ b/docs/manual/src/udl/errors.md @@ -71,7 +71,7 @@ pub struct MyError { } impl MyError { - fn message(&self) -> String> { self.to_string() } + fn message(&self) -> String { self.to_string() } } impl From for MyError { diff --git a/docs/manual/src/udl/ext_types.md b/docs/manual/src/udl/ext_types.md index 4ad4139c5e..65bfb66937 100644 --- a/docs/manual/src/udl/ext_types.md +++ b/docs/manual/src/udl/ext_types.md @@ -14,8 +14,7 @@ giving more detail. ## 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: +to the UDL file in your own crate via a `typedef` describing the concrete type. ```rust #[derive(uniffi::Record)] @@ -26,8 +25,7 @@ pub struct One { you can use it in your UDL: ```idl -[Rust="record"] -typedef extern One; +typedef record One; namespace app { // use the procmacro type. @@ -37,6 +35,16 @@ namespace app { ``` Supported values: -* "enum", "trait", "callback", "trait_with_foreign" -* For records, either "record" or "dictionary" -* For objects, either "object" or "interface" +* Enums: `enum` +* Records: `record`, `dictionary` or `struct` +* Objects: `object`, `impl` or `interface` +* Traits: `trait`, `callback` or `trait_with_foreign` + +eg: +``` +typedef enum MyEnum; +typedef interface MyObject; +``` + +Note that in 0.28 and prior, we also supported this capability with a `[Rust=]` attribute. +This attribute is deprecated and may be removed in a later version. diff --git a/fixtures/benchmarks/Cargo.toml b/fixtures/benchmarks/Cargo.toml index 74bbcc961d..f7fc07dde1 100644 --- a/fixtures/benchmarks/Cargo.toml +++ b/fixtures/benchmarks/Cargo.toml @@ -20,7 +20,7 @@ criterion = "0.5.1" uniffi = { workspace = true, features = ["build"] } [dev-dependencies] -uniffi_bindgen = {path = "../../uniffi_bindgen"} +uniffi_bindgen = {path = "../../uniffi_bindgen", features = ["bindgen-tests"]} [[bench]] name = "benchmarks" diff --git a/fixtures/benchmarks/benches/benchmarks.rs b/fixtures/benchmarks/benches/benchmarks.rs index 891d9821e4..627e42e69b 100644 --- a/fixtures/benchmarks/benches/benchmarks.rs +++ b/fixtures/benchmarks/benches/benchmarks.rs @@ -6,7 +6,8 @@ use clap::Parser; use std::env; use uniffi_benchmarks::Args; use uniffi_bindgen::bindings::{ - kotlin_run_script, python_run_script, swift_run_script, RunScriptOptions, + kotlin_test::run_script as kotlin_run_script, python_test::run_script as python_run_script, + swift_test::run_script as swift_run_script, RunScriptOptions, }; fn main() { diff --git a/fixtures/docstring-proc-macro/Cargo.toml b/fixtures/docstring-proc-macro/Cargo.toml index f67dece8db..45142fd21b 100644 --- a/fixtures/docstring-proc-macro/Cargo.toml +++ b/fixtures/docstring-proc-macro/Cargo.toml @@ -11,6 +11,7 @@ name = "uniffi_fixture_docstring_proc_macro" crate-type = ["lib", "cdylib"] [dependencies] +cargo_metadata = { version = "0.15" } thiserror = "1.0" uniffi = { path = "../../uniffi" } diff --git a/fixtures/docstring-proc-macro/tests/test_generated_bindings.rs b/fixtures/docstring-proc-macro/tests/test_generated_bindings.rs index 61c3d6e89d..e06bfdc6b3 100644 --- a/fixtures/docstring-proc-macro/tests/test_generated_bindings.rs +++ b/fixtures/docstring-proc-macro/tests/test_generated_bindings.rs @@ -50,10 +50,19 @@ mod tests { let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir).unwrap(); + let config_supplier = { + use uniffi_bindgen::cargo_metadata::CrateConfigSupplier; + let metadata = cargo_metadata::MetadataCommand::new() + .exec() + .expect("error running cargo metadata"); + CrateConfigSupplier::from(metadata) + }; + uniffi_bindgen::library_mode::generate_bindings( &cdylib_path, None, &gen, + &config_supplier, None, &out_dir, false, diff --git a/fixtures/ext-types/README.md b/fixtures/ext-types/README.md index 91f9ba421a..cb11c8ab61 100644 --- a/fixtures/ext-types/README.md +++ b/fixtures/ext-types/README.md @@ -1,6 +1,13 @@ -This directory contains the tests for external types -- types defined in one crate and used in a -different one. +This directory contains the tests for external types - cross-crate depedencies and libraries +to tie them together. -- `guid` and `uniffi-one` are dependent crates that define types exported by UniFFI -- `lib` is a library crate that depends on `guid` and `uniffi-one` -- `proc-macro-lib` is another library crate, but this one uses proc-macros rather than UDL files +- `lib` is a library crate that depends on various other crates. +- `proc-macro-lib` is another library crate, but this one uses proc-macros rather than UDL files. + +The various other crates all inter-relate and are ultimately consumed by the above libs: +- `custom-types` is all about wrapping types (eg, Guid, Handle) in a native type (eg, String, u64) +- `uniffi-one` is just a normal other crate also using uniffi. +- `sub-lib` itself consumes and exposes the other types. +- `external-crate` doesn't depend on uniffi but has types we expose. + +etc. diff --git a/fixtures/ext-types/sub-lib/README.md b/fixtures/ext-types/sub-lib/README.md index 41c153fde4..48fd1db921 100644 --- a/fixtures/ext-types/sub-lib/README.md +++ b/fixtures/ext-types/sub-lib/README.md @@ -1,3 +1,3 @@ -This is a "sub library" - it is itself a "library" which consumes types from +This is a "sub library" - ie, a crate 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/proc-macro-no-implicit-prelude/Cargo.toml b/fixtures/proc-macro-no-implicit-prelude/Cargo.toml new file mode 100644 index 0000000000..e56fd77196 --- /dev/null +++ b/fixtures/proc-macro-no-implicit-prelude/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "uniffi-fixture-proc-macro-no-implicit-prelude" +version = "0.22.0" +authors = ["Firefox Sync Team "] +edition = "2018" +license = "MPL-2.0" +publish = false + +[lib] +name = "uniffi_proc_macro_nip" +crate-type = ["lib", "cdylib"] + +[features] +default = ["myfeature"] +myfeature = [] + +[dependencies] +# Add the "scaffolding-ffi-buffer-fns" feature to make sure things can build correctly +uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns"] } + +[build-dependencies] +uniffi = { workspace = true, features = ["build", "scaffolding-ffi-buffer-fns"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } diff --git a/fixtures/proc-macro-no-implicit-prelude/build.rs b/fixtures/proc-macro-no-implicit-prelude/build.rs new file mode 100644 index 0000000000..a652fcd339 --- /dev/null +++ b/fixtures/proc-macro-no-implicit-prelude/build.rs @@ -0,0 +1,9 @@ +/* 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() { + // generate_scaffolding would work here, but we use the _for_crate version for + // test coverage. + uniffi::generate_scaffolding_for_crate("src/proc-macro.udl", "uniffi_proc_macro").unwrap(); +} diff --git a/fixtures/proc-macro-no-implicit-prelude/src/callback_interface.rs b/fixtures/proc-macro-no-implicit-prelude/src/callback_interface.rs new file mode 100644 index 0000000000..ca88186511 --- /dev/null +++ b/fixtures/proc-macro-no-implicit-prelude/src/callback_interface.rs @@ -0,0 +1,24 @@ +/* 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::marker::Sized; + +use crate::{BasicError, Object, RecordWithBytes}; + +#[::uniffi::export(callback_interface)] +pub trait TestCallbackInterface { + fn do_nothing(&self); + fn add(&self, a: u32, b: u32) -> u32; + fn optional(&self, a: ::std::option::Option) -> u32; + fn with_bytes(&self, rwb: RecordWithBytes) -> ::std::vec::Vec; + fn try_parse_int(&self, value: ::std::string::String) + -> ::std::result::Result; + fn callback_handler(&self, h: ::std::sync::Arc) -> u32; + fn get_other_callback_interface(&self) -> ::std::boxed::Box; +} + +#[::uniffi::export(callback_interface)] +pub trait OtherCallbackInterface { + fn multiply(&self, a: u32, b: u32) -> u32; +} diff --git a/fixtures/proc-macro-no-implicit-prelude/src/lib.rs b/fixtures/proc-macro-no-implicit-prelude/src/lib.rs new file mode 100644 index 0000000000..1823929cb8 --- /dev/null +++ b/fixtures/proc-macro-no-implicit-prelude/src/lib.rs @@ -0,0 +1,413 @@ +/* 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/. */ +#![no_implicit_prelude] + +// Let's not have the macros care about derive being shadowed in the macro namespace for now.. +use ::std::prelude::rust_2021::derive; +// Required for now because `static-assertions` (used internally) macros assume it to be in scope. +use ::std::marker::Sized; + +mod callback_interface; + +use callback_interface::TestCallbackInterface; + +#[derive(::uniffi::Record)] +pub struct One { + inner: i32, +} + +#[::uniffi::export] +pub fn one_inner_by_ref(one: &One) -> i32 { + one.inner +} + +#[derive(::uniffi::Record)] +pub struct Two { + a: ::std::string::String, +} + +#[derive(::uniffi::Record)] +pub struct NestedRecord { + // This used to result in an error in bindings generation + user_type_in_builtin_generic: ::std::option::Option, +} + +#[derive(::uniffi::Record)] +pub struct Three { + obj: ::std::sync::Arc, +} + +#[derive(::uniffi::Record, Debug, PartialEq)] +pub struct RecordWithBytes { + some_bytes: ::std::vec::Vec, +} + +// An object that's not used anywhere (ie, in records, function signatures, etc) +// should not break things. +#[derive(::uniffi::Object)] +pub struct Unused; + +#[::uniffi::export] +impl Unused { + #[::uniffi::constructor] + fn new() -> ::std::sync::Arc { + ::std::sync::Arc::new(Self) + } +} + +#[::uniffi::export] +pub trait Trait: ::std::marker::Send + ::std::marker::Sync { + // Test the absence of `with_foreign` by inputting reference arguments, which is + // incompatible with callback interfaces + #[allow(clippy::ptr_arg)] + fn concat_strings(&self, a: &str, b: &str) -> ::std::string::String; +} + +struct TraitImpl {} + +impl Trait for TraitImpl { + fn concat_strings(&self, a: &str, b: &str) -> ::std::string::String { + ::std::format!("{a}{b}") + } +} + +#[::uniffi::export(with_foreign)] +pub trait TraitWithForeign: ::std::marker::Send + ::std::marker::Sync { + fn name(&self) -> ::std::string::String; +} + +struct RustTraitImpl {} + +impl TraitWithForeign for RustTraitImpl { + fn name(&self) -> ::std::string::String { + use ::std::string::ToString; + "RustTraitImpl".to_string() + } +} + +#[derive(::uniffi::Object)] +pub struct Object; + +#[cfg_attr(feature = "myfeature", ::uniffi::export)] +impl Object { + #[cfg_attr(feature = "myfeature", ::uniffi::constructor)] + fn new() -> ::std::sync::Arc { + ::std::sync::Arc::new(Self) + } + + #[::uniffi::constructor] + fn named_ctor(arg: u32) -> Self { + _ = arg; + // This constructor returns Self directly. UniFFI ensures that it's wrapped in an Arc + // before sending it across the FFI. + Self + } + + fn is_heavy(&self) -> MaybeBool { + MaybeBool::Uncertain + } + + fn is_other_heavy(&self, other: &Self) -> MaybeBool { + other.is_heavy() + } + + fn get_trait( + &self, + inc: ::std::option::Option<::std::sync::Arc>, + ) -> ::std::sync::Arc { + inc.unwrap_or_else(|| ::std::sync::Arc::new(TraitImpl {})) + } + + fn get_trait_with_foreign( + &self, + inc: ::std::option::Option<::std::sync::Arc>, + ) -> ::std::sync::Arc { + inc.unwrap_or_else(|| ::std::sync::Arc::new(RustTraitImpl {})) + } + + fn take_error(&self, e: BasicError) -> u32 { + ::std::assert!(::std::matches!(e, BasicError::InvalidInput)); + 42 + } +} + +#[::uniffi::export] +fn concat_strings_by_ref(t: &dyn Trait, a: &str, b: &str) -> ::std::string::String { + t.concat_strings(a, b) +} + +#[::uniffi::export] +fn make_one(inner: i32) -> One { + One { inner } +} + +#[::uniffi::export] +fn take_two(two: Two) -> ::std::string::String { + two.a +} + +#[::uniffi::export] +fn make_hashmap(k: i8, v: u64) -> ::std::collections::HashMap { + ::std::convert::From::from([(k, v)]) +} + +#[::uniffi::export] +fn return_hashmap(h: ::std::collections::HashMap) -> ::std::collections::HashMap { + h +} + +#[::uniffi::export] +fn take_record_with_bytes(rwb: RecordWithBytes) -> ::std::vec::Vec { + rwb.some_bytes +} + +#[::uniffi::export] +fn call_callback_interface(cb: ::std::boxed::Box) { + use ::std::{assert_eq, matches, option::Option::*, result::Result::*, string::ToString, vec}; + + cb.do_nothing(); + assert_eq!(cb.add(1, 1), 2); + assert_eq!(cb.optional(Some(1)), 1); + assert_eq!(cb.optional(None), 0); + assert_eq!( + cb.with_bytes(RecordWithBytes { + some_bytes: vec![9, 8, 7], + }), + vec![9, 8, 7] + ); + assert_eq!(Ok(10), cb.try_parse_int("10".to_string())); + assert_eq!( + Err(BasicError::InvalidInput), + cb.try_parse_int("ten".to_string()) + ); + assert!(matches!( + cb.try_parse_int("force-unexpected-error".to_string()), + Err(BasicError::UnexpectedError { .. }), + )); + assert_eq!(42, cb.callback_handler(Object::new())); + + assert_eq!(6, cb.get_other_callback_interface().multiply(2, 3)); +} + +// Type that's defined in the UDL and not wrapped with #[::uniffi::export] +pub struct Zero { + inner: ::std::string::String, +} + +#[::uniffi::export] +fn make_zero() -> Zero { + use ::std::borrow::ToOwned; + Zero { + inner: "ZERO".to_owned(), + } +} + +#[::uniffi::export] +fn make_record_with_bytes() -> RecordWithBytes { + RecordWithBytes { + some_bytes: ::std::vec![0, 1, 2, 3, 4], + } +} + +#[derive(::uniffi::Enum)] +pub enum MaybeBool { + True, + False, + Uncertain, +} + +#[derive(::uniffi::Enum)] +pub enum MixedEnum { + None, + String(::std::string::String), + Int(i64), + Both(::std::string::String, i64), + All { s: ::std::string::String, i: i64 }, +} + +#[::uniffi::export] +fn get_mixed_enum(v: ::std::option::Option) -> MixedEnum { + v.unwrap_or(MixedEnum::Int(1)) +} + +#[repr(u8)] +#[derive(::uniffi::Enum)] +pub enum ReprU8 { + One = 1, + Three = 0x3, +} + +#[::uniffi::export] +fn enum_identity(value: MaybeBool) -> MaybeBool { + value +} + +#[derive(::uniffi::Error, Debug, PartialEq, Eq)] +pub enum BasicError { + InvalidInput, + OsError, + UnexpectedError { reason: ::std::string::String }, +} + +impl ::std::fmt::Display for BasicError { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + f.write_str(match self { + Self::InvalidInput => "InvalidInput", + Self::OsError => "OsError", + Self::UnexpectedError { .. } => "UnexpectedError", + }) + } +} + +impl ::std::error::Error for BasicError {} + +impl ::std::convert::From<::uniffi::UnexpectedUniFFICallbackError> for BasicError { + fn from(e: ::uniffi::UnexpectedUniFFICallbackError) -> Self { + Self::UnexpectedError { reason: e.reason } + } +} + +#[::uniffi::export] +fn always_fails() -> ::std::result::Result<(), BasicError> { + ::std::result::Result::Err(BasicError::OsError) +} + +#[derive(Debug, ::uniffi::Error)] +#[uniffi(flat_error)] +#[non_exhaustive] +pub enum FlatError { + InvalidInput, + + // Inner types that aren't FFI-convertible, as well as unnamed fields, + // are allowed for flat errors + OsError(::std::io::Error), +} + +impl ::std::fmt::Display for FlatError { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match self { + Self::InvalidInput => f.write_str("Invalid input"), + Self::OsError(e) => ::std::write!(f, "OS error: {e}"), + } + } +} + +#[::uniffi::export] +impl Object { + fn do_stuff(&self, times: u32) -> ::std::result::Result<(), FlatError> { + match times { + 0 => ::std::result::Result::Err(FlatError::InvalidInput), + _ => { + // do stuff + ::std::result::Result::Ok(()) + } + } + } +} + +// defined in UDL. +fn get_one(one: ::std::option::Option) -> One { + one.unwrap_or(One { inner: 0 }) +} + +fn get_bool(b: ::std::option::Option) -> MaybeBool { + b.unwrap_or(MaybeBool::Uncertain) +} + +fn get_object(o: ::std::option::Option<::std::sync::Arc>) -> ::std::sync::Arc { + o.unwrap_or_else(Object::new) +} + +fn get_trait(o: ::std::option::Option<::std::sync::Arc>) -> ::std::sync::Arc { + o.unwrap_or_else(|| ::std::sync::Arc::new(TraitImpl {})) +} + +fn get_trait_with_foreign( + o: ::std::option::Option<::std::sync::Arc>, +) -> ::std::sync::Arc { + o.unwrap_or_else(|| ::std::sync::Arc::new(RustTraitImpl {})) +} + +#[derive(Default)] +struct Externals { + one: ::std::option::Option, + bool: ::std::option::Option, +} + +fn get_externals(e: ::std::option::Option) -> Externals { + e.unwrap_or_default() +} + +#[::uniffi::export] +pub fn join(parts: &[::std::string::String], sep: &str) -> ::std::string::String { + parts.join(sep) +} + +// Custom names +#[derive(::uniffi::Object)] +pub struct Renamed; + +// `renamed_new` becomes the default constructor because it's named `new` +#[::uniffi::export] +impl Renamed { + #[::uniffi::constructor(name = "new")] + fn renamed_new() -> ::std::sync::Arc { + ::std::sync::Arc::new(Self) + } + + #[::uniffi::method(name = "func")] + fn renamed_func(&self) -> bool { + true + } +} + +#[::uniffi::export(name = "rename_test")] +fn renamed_rename_test() -> bool { + true +} + +/// Test defaults on Records +#[derive(::uniffi::Record)] +pub struct RecordWithDefaults { + no_default_string: ::std::string::String, + #[uniffi(default = true)] + boolean: bool, + #[uniffi(default = 42)] + integer: i32, + #[uniffi(default = 4.2)] + float_var: f64, + #[uniffi(default=[])] + vec: ::std::vec::Vec, + #[uniffi(default=None)] + opt_vec: ::std::option::Option<::std::vec::Vec>, + #[uniffi(default = Some(42))] + opt_integer: ::std::option::Option, +} + +/// Test defaults on top-level functions +#[::uniffi::export(default(num = 21))] +fn double_with_default(num: i32) -> i32 { + num + num +} + +/// Test defaults on constructors / methods +#[derive(::uniffi::Object)] +pub struct ObjectWithDefaults { + num: i32, +} + +#[::uniffi::export] +impl ObjectWithDefaults { + #[::uniffi::constructor(default(num = 30))] + fn new(num: i32) -> Self { + Self { num } + } + + #[::uniffi::method(default(other = 12))] + fn add_to_num(&self, other: i32) -> i32 { + self.num + other + } +} + +::uniffi::include_scaffolding!("proc-macro"); diff --git a/fixtures/proc-macro-no-implicit-prelude/src/proc-macro.udl b/fixtures/proc-macro-no-implicit-prelude/src/proc-macro.udl new file mode 100644 index 0000000000..8eeafe13cd --- /dev/null +++ b/fixtures/proc-macro-no-implicit-prelude/src/proc-macro.udl @@ -0,0 +1,37 @@ +// Like our proc-macro fixture, but tests everything works without Rust `std::` type preludes. +dictionary Zero { + string inner; +}; + +// NOTE: `[Rust=..]` is deprecated and this test hasn't migrated. +// This helps testing the attribute, so don't remove them unless you are removing support entirely! +[Rust="record"] +typedef extern One; + +[Rust="enum"] +typedef extern MaybeBool; + +[Rust="interface"] +typedef extern Object; + +[Rust="trait"] +typedef extern Trait; + +[Rust="trait_with_foreign"] +typedef extern TraitWithForeign; + +// 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); + TraitWithForeign get_trait_with_foreign(TraitWithForeign? t); + Externals get_externals(Externals? e); +}; diff --git a/fixtures/proc-macro-no-implicit-prelude/uniffi.toml b/fixtures/proc-macro-no-implicit-prelude/uniffi.toml new file mode 100644 index 0000000000..09899a8b65 --- /dev/null +++ b/fixtures/proc-macro-no-implicit-prelude/uniffi.toml @@ -0,0 +1,2 @@ +[bindings.kotlin] +package_name = "uniffi.fixture.proc_macro" diff --git a/fixtures/proc-macro/src/proc-macro.udl b/fixtures/proc-macro/src/proc-macro.udl index 9896f2e633..84f096ca66 100644 --- a/fixtures/proc-macro/src/proc-macro.udl +++ b/fixtures/proc-macro/src/proc-macro.udl @@ -4,20 +4,11 @@ dictionary Zero { }; // 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; - -[Rust="trait_with_foreign"] -typedef extern TraitWithForeign; +typedef record One; +typedef enum MaybeBool; +typedef interface Object; +typedef trait Trait; +typedef trait_with_foreign TraitWithForeign; // Then stuff defined here but referencing the imported types. dictionary Externals { diff --git a/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr b/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr index f32a1e4a25..19eaba8de0 100644 --- a/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr +++ b/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr @@ -16,7 +16,7 @@ note: required by a bound in `_::{closure#0}::assert_impl_all` | | #[::uniffi::udl_derive(Object)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl_all` - = note: this error originates in the macro `uniffi::deps::static_assertions::assert_impl_all` which comes from the expansion of the attribute macro `::uniffi::udl_derive` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::uniffi::deps::static_assertions::assert_impl_all` which comes from the expansion of the attribute macro `::uniffi::udl_derive` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `Cell` cannot be shared between threads safely --> $OUT_DIR[uniffi_uitests]/counter.uniffi.rs @@ -55,7 +55,7 @@ note: required by a bound in `_::{closure#0}::assert_impl_all` | 26 | #[derive(uniffi::Object)] | ^^^^^^^^^^^^^^ required by this bound in `assert_impl_all` - = note: this error originates in the macro `uniffi::deps::static_assertions::assert_impl_all` which comes from the expansion of the derive macro `uniffi::Object` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::uniffi::deps::static_assertions::assert_impl_all` which comes from the expansion of the derive macro `uniffi::Object` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `Cell` cannot be shared between threads safely --> tests/ui/interface_not_sync_and_send.rs:27:12 diff --git a/uniffi/Cargo.toml b/uniffi/Cargo.toml index 6396757233..9a4eb1c370 100644 --- a/uniffi/Cargo.toml +++ b/uniffi/Cargo.toml @@ -7,7 +7,7 @@ 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.28.0" +version = "0.28.1" authors = ["Firefox Sync Team "] license = "MPL-2.0" edition = "2021" @@ -15,31 +15,34 @@ keywords = ["ffi", "bindgen"] readme = "../README.md" [dependencies] -uniffi_bindgen = { path = "../uniffi_bindgen", version = "=0.28.0", optional = true } -uniffi_build = { path = "../uniffi_build", version = "=0.28.0", optional = true } -uniffi_core = { path = "../uniffi_core", version = "=0.28.0" } -uniffi_macros = { path = "../uniffi_macros", version = "=0.28.0" } +uniffi_bindgen = { path = "../uniffi_bindgen", version = "=0.28.1", optional = true } +uniffi_build = { path = "../uniffi_build", version = "=0.28.1", optional = true } +uniffi_core = { path = "../uniffi_core", version = "=0.28.1" } +uniffi_macros = { path = "../uniffi_macros", version = "=0.28.1" } anyhow = "1" camino = { version = "1.0.8", optional = true } +cargo_metadata = { version = "0.15", optional = true } clap = { version = "4", features = ["cargo", "std", "derive"], optional = true } [dev-dependencies] trybuild = "1" [features] -default = [] +default = ["cargo-metadata"] # Support for features needed by the `build.rs` script. Enable this in your # `build-dependencies`. build = [ "dep:uniffi_build" ] # Support for `uniffi_bindgen::{generate_bindings, generate_component_scaffolding}`. # Enable this feature for your `uniffi-bindgen` binaries if you don't need the full CLI. bindgen = ["dep:uniffi_bindgen"] +cargo-metadata = ["dep:cargo_metadata", "uniffi_bindgen?/cargo-metadata"] + # Support for `uniffi_bindgen_main()`. Enable this feature for your # `uniffi-bindgen` binaries. cli = [ "bindgen", "dep:clap", "dep:camino" ] # Support for running example/fixture tests for `uniffi-bindgen`. You probably # don't need to enable this. -bindgen-tests = [ "dep:uniffi_bindgen" ] +bindgen-tests = [ "dep:uniffi_bindgen", "uniffi_bindgen/bindgen-tests" ] # Enable support for Tokio's futures. # This must still be opted into on a per-function basis using `#[uniffi::export(async_runtime = "tokio")]`. tokio = ["uniffi_core/tokio"] diff --git a/uniffi/src/cli.rs b/uniffi/src/cli.rs index 09f408d628..e3d5cf41e9 100644 --- a/uniffi/src/cli.rs +++ b/uniffi/src/cli.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 anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; use camino::Utf8PathBuf; use clap::{Parser, Subcommand}; use std::fmt; @@ -108,6 +108,14 @@ enum Commands { /// Path to the UDL file, or cdylib if `library-mode` is specified source: Utf8PathBuf, + + /// Whether we should exclude dependencies when running "cargo metadata". + /// This will mean external types may not be resolved if they are implemented in crates + /// outside of this workspace. + /// This can be used in environments when all types are in the namespace and fetching + /// all sub-dependencies causes obscure platform specific problems. + #[clap(long)] + metadata_no_deps: bool, }, /// Generate Rust scaffolding code @@ -138,8 +146,23 @@ fn gen_library_mode( cfo: Option<&camino::Utf8Path>, out_dir: &camino::Utf8Path, fmt: bool, + metadata_no_deps: bool, ) -> anyhow::Result<()> { use uniffi_bindgen::library_mode::generate_bindings; + + #[cfg(feature = "cargo-metadata")] + let config_supplier = { + use uniffi_bindgen::cargo_metadata::CrateConfigSupplier; + let mut cmd = cargo_metadata::MetadataCommand::new(); + if metadata_no_deps { + cmd.no_deps(); + } + let metadata = cmd.exec().context("error running cargo metadata")?; + CrateConfigSupplier::from(metadata) + }; + #[cfg(not(feature = "cargo-metadata"))] + let config_supplier = uniffi_bindgen::EmptyCrateConfigSupplier; + for language in languages { // to help avoid mistakes we check the library is actually a cdylib, except // for swift where static libs are often used to extract the metadata. @@ -155,6 +178,7 @@ fn gen_library_mode( library_path, crate_name.clone(), &KotlinBindingGenerator, + &config_supplier, cfo, out_dir, fmt, @@ -164,6 +188,7 @@ fn gen_library_mode( library_path, crate_name.clone(), &PythonBindingGenerator, + &config_supplier, cfo, out_dir, fmt, @@ -173,6 +198,7 @@ fn gen_library_mode( library_path, crate_name.clone(), &RubyBindingGenerator, + &config_supplier, cfo, out_dir, fmt, @@ -182,6 +208,7 @@ fn gen_library_mode( library_path, crate_name.clone(), &SwiftBindingGenerator, + &config_supplier, cfo, out_dir, fmt, @@ -257,6 +284,7 @@ pub fn run_main() -> anyhow::Result<()> { source, crate_name, library_mode, + metadata_no_deps, } => { if library_mode { if lib_file.is_some() { @@ -273,8 +301,12 @@ pub fn run_main() -> anyhow::Result<()> { config.as_deref(), &out_dir, !no_format, + metadata_no_deps, )?; } else { + if metadata_no_deps { + panic!("--metadata-no-deps makes no sense when not in library mode") + } gen_bindings( &source, config.as_deref(), diff --git a/uniffi/src/lib.rs b/uniffi/src/lib.rs index 55d2acd9ae..d49e89e584 100644 --- a/uniffi/src/lib.rs +++ b/uniffi/src/lib.rs @@ -8,13 +8,8 @@ pub use uniffi_macros::*; #[cfg(feature = "cli")] mod cli; #[cfg(feature = "bindgen-tests")] -pub use uniffi_bindgen::bindings::kotlin_run_test; -#[cfg(feature = "bindgen-tests")] -pub use uniffi_bindgen::bindings::python_run_test; -#[cfg(feature = "bindgen-tests")] -pub use uniffi_bindgen::bindings::ruby_run_test; -#[cfg(feature = "bindgen-tests")] -pub use uniffi_bindgen::bindings::swift_run_test; +pub use uniffi_bindgen::bindings::{kotlin_test, python_test, ruby_test, swift_test}; + #[cfg(feature = "bindgen")] pub use uniffi_bindgen::{ bindings::{ diff --git a/uniffi_bindgen/Cargo.toml b/uniffi_bindgen/Cargo.toml index 75c0a4c47f..2e253e5f80 100644 --- a/uniffi_bindgen/Cargo.toml +++ b/uniffi_bindgen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_bindgen" -version = "0.28.0" +version = "0.28.1" authors = ["Firefox Sync Team "] description = "a multi-language bindings generator for rust (codegen and cli tooling)" documentation = "https://mozilla.github.io/uniffi-rs" @@ -11,11 +11,16 @@ edition = "2021" keywords = ["ffi", "bindgen"] readme = "../README.md" +[features] +default = ["cargo-metadata"] +cargo-metadata = ["dep:cargo_metadata"] +bindgen-tests = ["cargo-metadata", "dep:uniffi_testing"] + [dependencies] anyhow = "1" askama = { version = "0.12", default-features = false, features = ["config"] } camino = "1.0.8" -cargo_metadata = "0.15" +cargo_metadata = { version = "0.15", optional = true } fs-err = "2.7.0" glob = "0.3" goblin = "0.8" @@ -24,9 +29,9 @@ once_cell = "1.12" paste = "1.0" serde = { version = "1", features = ["derive"] } toml = "0.5" -uniffi_meta = { path = "../uniffi_meta", version = "=0.28.0" } -uniffi_testing = { path = "../uniffi_testing", version = "=0.28.0" } -uniffi_udl = { path = "../uniffi_udl", version = "=0.28.0" } +uniffi_meta = { path = "../uniffi_meta", version = "=0.28.1" } +uniffi_testing = { path = "../uniffi_testing", version = "=0.28.1", optional = true } +uniffi_udl = { path = "../uniffi_udl", version = "=0.28.1" } # Don't include the `unicode-linebreak` or `unicode-width` since that functionality isn't needed for # docstrings. textwrap = { version = "0.16", features=["smawk"], default-features = false } diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index e6faf8ddfa..92f2f39c33 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -7,9 +7,8 @@ use std::cell::RefCell; use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Debug; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use askama::Template; - use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; @@ -82,12 +81,60 @@ pub struct Config { android: bool, #[serde(default)] android_cleaner: Option, + #[serde(default)] + kotlin_target_version: Option, } impl Config { pub(crate) fn android_cleaner(&self) -> bool { self.android_cleaner.unwrap_or(self.android) } + + pub(crate) fn use_enum_entries(&self) -> bool { + self.get_kotlin_version() >= KotlinVersion::new(1, 9, 0) + } + + /// Returns a `Version` with the contents of `kotlin_target_version`. + /// If `kotlin_target_version` is not defined, version `0.0.0` will be used as a fallback. + /// If it's not valid, this function will panic. + fn get_kotlin_version(&self) -> KotlinVersion { + self.kotlin_target_version + .clone() + .map(|v| { + KotlinVersion::parse(&v).unwrap_or_else(|_| { + panic!("Provided Kotlin target version is not valid: {}", v) + }) + }) + .unwrap_or(KotlinVersion::new(0, 0, 0)) + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +struct KotlinVersion((u16, u16, u16)); + +impl KotlinVersion { + fn new(major: u16, minor: u16, patch: u16) -> Self { + Self((major, minor, patch)) + } + + fn parse(version: &str) -> Result { + let components = version + .split('.') + .map(|n| { + n.parse::() + .map_err(|_| anyhow!("Invalid version string ({n} is not an integer)")) + }) + .collect::>>()?; + + match components.as_slice() { + [major, minor, patch] => Ok(Self((*major, *minor, *patch))), + [major, minor] => Ok(Self((*major, *minor, 0))), + [major] => Ok(Self((*major, 0, 0))), + _ => Err(anyhow!( + "Invalid version string (expected 1-3 components): {version}" + )), + } + } } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -384,9 +431,10 @@ impl KotlinCodeOracle { FfiType::Float64 => "Double".to_string(), FfiType::Handle => "Long".to_string(), FfiType::RustArcPtr(_) => "Pointer".to_string(), - FfiType::RustBuffer(maybe_suffix) => { - format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default()) - } + FfiType::RustBuffer(maybe_external) => match maybe_external { + Some(external_meta) => format!("RustBuffer{}", external_meta.name), + None => "RustBuffer".to_string(), + }, FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue".to_string(), FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(), FfiType::Callback(name) => self.ffi_callback_name(name), @@ -674,3 +722,30 @@ mod filters { Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_kotlin_version() { + assert_eq!( + KotlinVersion::parse("1.2.3").unwrap(), + KotlinVersion::new(1, 2, 3) + ); + assert_eq!( + KotlinVersion::parse("2.3").unwrap(), + KotlinVersion::new(2, 3, 0), + ); + assert_eq!( + KotlinVersion::parse("2").unwrap(), + KotlinVersion::new(2, 0, 0), + ); + assert!(KotlinVersion::parse("2.").is_err()); + assert!(KotlinVersion::parse("").is_err()); + assert!(KotlinVersion::parse("A.B.C").is_err()); + assert!(KotlinVersion::new(1, 2, 3) > KotlinVersion::new(0, 1, 2)); + assert!(KotlinVersion::new(1, 2, 3) > KotlinVersion::new(0, 100, 0)); + assert!(KotlinVersion::new(10, 0, 0) > KotlinVersion::new(1, 10, 0)); + } +} diff --git a/uniffi_bindgen/src/bindings/kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/mod.rs index 4a447d13d9..501f449da0 100644 --- a/uniffi_bindgen/src/bindings/kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/mod.rs @@ -11,8 +11,8 @@ use std::process::Command; mod gen_kotlin; use gen_kotlin::{generate_bindings, Config}; -mod test; -pub use test::{run_script, run_test}; +#[cfg(feature = "bindgen-tests")] +pub mod test; pub struct KotlinBindingGenerator; impl BindingGenerator for KotlinBindingGenerator { diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt index 8d1c2235ec..1c17799633 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt @@ -29,7 +29,11 @@ enum class {{ type_name }}(val value: {{ variant_discr_type|type_name(ci) }}) { public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { override fun read(buf: ByteBuffer) = try { + {% if config.use_enum_entries() %} + {{ type_name }}.entries[buf.getInt() - 1] + {% else -%} {{ type_name }}.values()[buf.getInt() - 1] + {%- endif %} } catch (e: IndexOutOfBoundsException) { throw RuntimeException("invalid enum value, something is very wrong!!", e) } diff --git a/uniffi_bindgen/src/bindings/kotlin/test.rs b/uniffi_bindgen/src/bindings/kotlin/test.rs index 48c3d06cf1..5c7f0c9943 100644 --- a/uniffi_bindgen/src/bindings/kotlin/test.rs +++ b/uniffi_bindgen/src/bindings/kotlin/test.rs @@ -3,6 +3,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::bindings::RunScriptOptions; +use crate::cargo_metadata::CrateConfigSupplier; use crate::library_mode::generate_bindings; use anyhow::{bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; @@ -35,10 +36,12 @@ pub fn run_script( let test_helper = UniFFITestHelper::new(crate_name)?; let out_dir = test_helper.create_out_dir(tmp_dir, script_path)?; let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; + generate_bindings( &cdylib_path, None, &super::KotlinBindingGenerator, + &CrateConfigSupplier::from(test_helper.cargo_metadata()), None, &out_dir, false, diff --git a/uniffi_bindgen/src/bindings/mod.rs b/uniffi_bindgen/src/bindings/mod.rs index c984d07aff..2d6cdc07fb 100644 --- a/uniffi_bindgen/src/bindings/mod.rs +++ b/uniffi_bindgen/src/bindings/mod.rs @@ -8,26 +8,28 @@ //! along with some helpers for executing foreign language scripts or tests. mod kotlin; -pub use kotlin::{ - run_script as kotlin_run_script, run_test as kotlin_run_test, KotlinBindingGenerator, -}; +pub use kotlin::KotlinBindingGenerator; mod python; -pub use python::{ - run_script as python_run_script, run_test as python_run_test, PythonBindingGenerator, -}; +pub use python::PythonBindingGenerator; mod ruby; -pub use ruby::{run_test as ruby_run_test, RubyBindingGenerator}; +pub use ruby::RubyBindingGenerator; mod swift; -pub use swift::{ - run_script as swift_run_script, run_test as swift_run_test, SwiftBindingGenerator, +pub use swift::SwiftBindingGenerator; + +#[cfg(feature = "bindgen-tests")] +pub use self::{ + kotlin::test as kotlin_test, python::test as python_test, ruby::test as ruby_test, + swift::test as swift_test, }; +#[cfg(feature = "bindgen-tests")] /// Mode for the `run_script` function defined for each language #[derive(Clone, Debug)] pub struct RunScriptOptions { pub show_compiler_messages: bool, } +#[cfg(feature = "bindgen-tests")] impl Default for RunScriptOptions { fn default() -> Self { Self { diff --git a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index 61ab04166e..b831274963 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -16,6 +16,7 @@ use std::fmt::Debug; use crate::backend::TemplateExpression; use crate::interface::*; +use crate::VisitMut; mod callback_interface; mod compounds; @@ -148,7 +149,7 @@ impl Config { } // Generate python bindings for the given ComponentInterface, as a string. -pub fn generate_python_bindings(config: &Config, ci: &ComponentInterface) -> Result { +pub fn generate_python_bindings(config: &Config, ci: &mut ComponentInterface) -> Result { PythonWrapper::new(config.clone(), ci) .render() .context("failed to render python bindings") @@ -298,10 +299,13 @@ pub struct PythonWrapper<'a> { type_imports: BTreeSet, } impl<'a> PythonWrapper<'a> { - pub fn new(config: Config, ci: &'a ComponentInterface) -> Self { + pub fn new(config: Config, ci: &'a mut ComponentInterface) -> Self { + ci.visit_mut(&PythonCodeOracle); + let type_renderer = TypeRenderer::new(&config, ci); let type_helper_code = type_renderer.render().unwrap(); let type_imports = type_renderer.imports.into_inner(); + Self { config, ci, @@ -377,8 +381,8 @@ impl PythonCodeOracle { FfiType::Float64 => "ctypes.c_double".to_string(), FfiType::Handle => "ctypes.c_uint64".to_string(), FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(), - FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { - Some(suffix) => format!("_UniffiRustBuffer{suffix}"), + FfiType::RustBuffer(maybe_external) => match maybe_external { + Some(external_meta) => format!("_UniffiRustBuffer{}", external_meta.name), None => "_UniffiRustBuffer".to_string(), }, FfiType::RustCallStatus => "_UniffiRustCallStatus".to_string(), @@ -407,8 +411,10 @@ impl PythonCodeOracle { | FfiType::Int64 => "0".to_owned(), FfiType::Float32 | FfiType::Float64 => "0.0".to_owned(), FfiType::RustArcPtr(_) => "ctypes.c_void_p()".to_owned(), - FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { - Some(suffix) => format!("_UniffiRustBuffer{suffix}.default()"), + FfiType::RustBuffer(maybe_external) => match maybe_external { + Some(external_meta) => { + format!("_UniffiRustBuffer{}.default()", external_meta.name) + } None => "_UniffiRustBuffer.default()".to_owned(), }, _ => unimplemented!("FFI return type: {t:?}"), @@ -437,6 +443,84 @@ impl PythonCodeOracle { } } +impl VisitMut for PythonCodeOracle { + fn visit_record(&self, record: &mut Record) { + record.rename(self.class_name(record.name())); + } + + fn visit_object(&self, object: &mut Object) { + object.rename(self.class_name(object.name())); + } + + fn visit_field(&self, field: &mut Field) { + field.rename(self.var_name(field.name())); + } + + fn visit_ffi_field(&self, ffi_field: &mut FfiField) { + ffi_field.rename(self.var_name(ffi_field.name())); + } + + fn visit_ffi_argument(&self, ffi_argument: &mut FfiArgument) { + ffi_argument.rename(self.class_name(ffi_argument.name())); + } + + fn visit_enum(&self, is_error: bool, enum_: &mut Enum) { + if is_error { + enum_.rename(self.class_name(enum_.name())); + } else { + enum_.rename(self.enum_variant_name(enum_.name())); + } + } + + fn visit_enum_key(&self, key: &mut String) -> String { + self.class_name(key) + } + + fn visit_variant(&self, is_error: bool, variant: &mut Variant) { + //TODO: If we want to remove the last var_name filter + // in the template, this is it. We need an additional + // attribute for the `Variant` so we can + // display Python is_NAME functions + // variant.set_is_name(self.var_name(variant.name())); + + if is_error { + variant.rename(self.class_name(variant.name())); + } else { + variant.rename(self.enum_variant_name(variant.name())); + } + } + + fn visit_type(&self, type_: &mut Type) { + // Renaming Types is a special case. We have simple types with names like + // an Object, but we also have types which have inner_types and builtin types. + // Which in turn have a different name. Therefore we pass the patterns as a + // function down to the renaming operation of the type itself, which can apply it + // to all its nested names if needed. + let name_transformer = |name: &str| self.class_name(name); + type_.rename_recursive(&name_transformer); + } + + fn visit_method(&self, method: &mut Method) { + method.rename(self.fn_name(method.name())); + } + + fn visit_argument(&self, argument: &mut Argument) { + argument.rename(self.var_name(argument.name())); + } + + fn visit_constructor(&self, constructor: &mut Constructor) { + if !constructor.is_primary_constructor() { + constructor.rename(self.fn_name(constructor.name())); + } + } + + fn visit_function(&self, function: &mut Function) { + // Conversions for wrapper.py + //TODO: Renaming the function name in wrapper.py is not currently tested + function.rename(self.fn_name(function.name())); + } +} + trait AsCodeType { fn as_codetype(&self) -> Box; } @@ -498,6 +582,20 @@ pub mod filters { Ok(as_ct.as_codetype().type_label()) } + //TODO: Remove. Currently just being used by EnumTemplate.py to + // display is_NAME_OF_ENUM. + /// Get the idiomatic Python rendering of a variable name. + pub fn var_name(nm: &str) -> Result { + Ok(PythonCodeOracle.var_name(nm)) + } + + //TODO: Remove. Currently just being used by wrapper.py to display the + // callback_interface function names. + /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc). + pub fn class_name(nm: &str) -> Result { + Ok(PythonCodeOracle.class_name(nm)) + } + pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result { Ok(String::from("_Uniffi") + &as_ct.as_codetype().ffi_converter_name()[3..]) } @@ -547,26 +645,6 @@ pub mod filters { Ok(PythonCodeOracle.ffi_default_value(return_type.as_ref())) } - /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc). - pub fn class_name(nm: &str) -> Result { - Ok(PythonCodeOracle.class_name(nm)) - } - - /// Get the idiomatic Python rendering of a function name. - pub fn fn_name(nm: &str) -> Result { - Ok(PythonCodeOracle.fn_name(nm)) - } - - /// Get the idiomatic Python rendering of a variable name. - pub fn var_name(nm: &str) -> Result { - Ok(PythonCodeOracle.var_name(nm)) - } - - /// Get the idiomatic Python rendering of an individual enum variant. - pub fn enum_variant_py(nm: &str) -> Result { - Ok(PythonCodeOracle.enum_variant_name(nm)) - } - /// Get the idiomatic Python rendering of an FFI callback function name pub fn ffi_callback_name(nm: &str) -> Result { Ok(PythonCodeOracle.ffi_callback_name(nm)) diff --git a/uniffi_bindgen/src/bindings/python/mod.rs b/uniffi_bindgen/src/bindings/python/mod.rs index 04c8f7f76c..ca598baf56 100644 --- a/uniffi_bindgen/src/bindings/python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/mod.rs @@ -8,14 +8,15 @@ use anyhow::Result; use fs_err as fs; mod gen_python; -mod test; -use crate::{Component, GenerationSettings}; +#[cfg(feature = "bindgen-tests")] +pub mod test; +use crate::{BindingGenerator, Component, GenerationSettings}; + use gen_python::{generate_python_bindings, Config}; -pub use test::{run_script, run_test}; pub struct PythonBindingGenerator; -impl crate::BindingGenerator for PythonBindingGenerator { +impl BindingGenerator for PythonBindingGenerator { type Config = Config; fn new_config(&self, root_toml: &toml::Value) -> Result { @@ -50,7 +51,7 @@ impl crate::BindingGenerator for PythonBindingGenerator { ) -> Result<()> { for Component { ci, config, .. } in components { let py_file = settings.out_dir.join(format!("{}.py", ci.namespace())); - fs::write(&py_file, generate_python_bindings(config, ci)?)?; + fs::write(&py_file, generate_python_bindings(config, &mut ci.clone())?)?; if settings.try_format_code { if let Err(e) = Command::new("yapf").arg(&py_file).output() { diff --git a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py index d3d67a1a97..1009505d63 100644 --- a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py +++ b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py @@ -7,9 +7,9 @@ class {{ trait_impl }}: {%- for (ffi_callback, meth) in vtable_methods.iter() %} @{{ ffi_callback.name()|ffi_callback_name }} - def {{ meth.name()|fn_name }}( + def {{ meth.name() }}( {%- for arg in ffi_callback.arguments() %} - {{ arg.name()|var_name }}, + {{ arg.name() }}, {%- endfor -%} {%- if ffi_callback.has_rust_call_status_arg() %} uniffi_call_status_ptr, @@ -17,8 +17,8 @@ def {{ meth.name()|fn_name }}( ): uniffi_obj = {{ ffi_converter_name }}._handle_map.get(uniffi_handle) def make_call(): - args = ({% for arg in meth.arguments() %}{{ arg|lift_fn }}({{ arg.name()|var_name }}), {% endfor %}) - method = uniffi_obj.{{ meth.name()|fn_name }} + args = ({% for arg in meth.arguments() %}{{ arg|lift_fn }}({{ arg.name() }}), {% endfor %}) + method = uniffi_obj.{{ meth.name() }} return method(*args) {% if !meth.is_async() %} @@ -89,7 +89,7 @@ def _uniffi_free(uniffi_handle): # Generate the FFI VTable. This has a field for each callback interface method. _uniffi_vtable = {{ vtable|ffi_type_name }}( {%- for (_, meth) in vtable_methods.iter() %} - {{ meth.name()|fn_name }}, + {{ meth.name() }}, {%- endfor %} _uniffi_free ) diff --git a/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py b/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py index c308edea39..cfbf972603 100644 --- a/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py @@ -9,7 +9,7 @@ class {{ type_name }}(enum.Enum): {%- call py::docstring(e, 4) %} {%- for variant in e.variants() %} - {{ variant.name()|enum_variant_py }} = {{ e|variant_discr_literal(loop.index0) }} + {{ variant.name() }} = {{ e|variant_discr_literal(loop.index0) }} {%- call py::docstring(variant, 4) %} {% endfor %} {% else %} @@ -21,7 +21,7 @@ def __init__(self): # Each enum variant is a nested class of the enum itself. {% for variant in e.variants() -%} - class {{ variant.name()|enum_variant_py }}: + class {{ variant.name() }}: {%- call py::docstring(variant, 8) %} {%- if variant.has_nameless_fields() %} @@ -38,7 +38,7 @@ def __getitem__(self, index): return self._values[index] def __str__(self): - return f"{{ type_name }}.{{ variant.name()|enum_variant_py }}{self._values!r}" + return f"{{ type_name }}.{{ variant.name() }}{self._values!r}" def __eq__(self, other): if not other.is_{{ variant.name()|var_name }}(): @@ -47,27 +47,27 @@ def __eq__(self, other): {%- else -%} {%- for field in variant.fields() %} - {{ field.name()|var_name }}: "{{ field|type_name }}" + {{ field.name() }}: "{{ field|type_name }}" {%- call py::docstring(field, 8) %} {%- endfor %} - def __init__(self,{% for field in variant.fields() %}{{ field.name()|var_name }}: "{{- field|type_name }}"{% if loop.last %}{% else %}, {% endif %}{% endfor %}): + def __init__(self,{% for field in variant.fields() %}{{ field.name() }}: "{{- field|type_name }}"{% if loop.last %}{% else %}, {% endif %}{% endfor %}): {%- if variant.has_fields() %} {%- for field in variant.fields() %} - self.{{ field.name()|var_name }} = {{ field.name()|var_name }} + self.{{ field.name() }} = {{ field.name() }} {%- endfor %} {%- else %} pass {%- endif %} def __str__(self): - return "{{ type_name }}.{{ variant.name()|enum_variant_py }}({% for field in variant.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) + return "{{ type_name }}.{{ variant.name() }}({% for field in variant.fields() %}{{ field.name() }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name() }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) def __eq__(self, other): if not other.is_{{ variant.name()|var_name }}(): return False {%- for field in variant.fields() %} - if self.{{ field.name()|var_name }} != other.{{ field.name()|var_name }}: + if self.{{ field.name() }} != other.{{ field.name() }}: return False {%- endfor %} return True @@ -78,14 +78,14 @@ def __eq__(self, other): # whether an instance is that variant. {% for variant in e.variants() -%} def is_{{ variant.name()|var_name }}(self) -> bool: - return isinstance(self, {{ type_name }}.{{ variant.name()|enum_variant_py }}) + return isinstance(self, {{ type_name }}.{{ variant.name() }}) {% endfor %} # Now, a little trick - we make each nested variant class be a subclass of the main # enum class, so that method calls and instance checks etc will work intuitively. # We might be able to do this a little more neatly with a metaclass, but this'll do. {% for variant in e.variants() -%} -{{ type_name }}.{{ variant.name()|enum_variant_py }} = type("{{ type_name }}.{{ variant.name()|enum_variant_py }}", ({{ type_name }}.{{variant.name()|enum_variant_py}}, {{ type_name }},), {}) # type: ignore +{{ type_name }}.{{ variant.name() }} = type("{{ type_name }}.{{ variant.name() }}", ({{ type_name }}.{{variant.name()}}, {{ type_name }},), {}) # type: ignore {% endfor %} {% endif %} @@ -98,9 +98,9 @@ def read(buf): {%- for variant in e.variants() %} if variant == {{ loop.index }}: {%- if e.is_flat() %} - return {{ type_name }}.{{variant.name()|enum_variant_py}} + return {{ type_name }}.{{variant.name()}} {%- else %} - return {{ type_name }}.{{variant.name()|enum_variant_py}}( + return {{ type_name }}.{{variant.name()}}( {%- for field in variant.fields() %} {{ field|read_fn }}(buf), {%- endfor %} @@ -116,7 +116,7 @@ def check_lower(value): {%- else %} {%- for variant in e.variants() %} {%- if e.is_flat() %} - if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}: + if value == {{ type_name }}.{{ variant.name() }}: {%- else %} if value.is_{{ variant.name()|var_name }}(): {%- endif %} @@ -124,7 +124,7 @@ def check_lower(value): {%- if variant.has_nameless_fields() %} {{ field|check_lower_fn }}(value._values[{{ loop.index0 }}]) {%- else %} - {{ field|check_lower_fn }}(value.{{ field.name()|var_name }}) + {{ field|check_lower_fn }}(value.{{ field.name() }}) {%- endif %} {%- endfor %} return @@ -136,7 +136,7 @@ def check_lower(value): def write(value, buf): {%- for variant in e.variants() %} {%- if e.is_flat() %} - if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}: + if value == {{ type_name }}.{{ variant.name() }}: buf.write_i32({{ loop.index }}) {%- else %} if value.is_{{ variant.name()|var_name }}(): @@ -145,7 +145,7 @@ def write(value, buf): {%- if variant.has_nameless_fields() %} {{ field|write_fn }}(value._values[{{ loop.index0 }}], buf) {%- else %} - {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {{ field|write_fn }}(value.{{ field.name() }}, buf) {%- endif %} {%- endfor %} {%- endif %} diff --git a/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py b/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py index 53818b1806..9d3b30831d 100644 --- a/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py @@ -13,7 +13,7 @@ class {{ type_name }}(Exception): class {{ type_name }}: # type: ignore {%- call py::docstring(e, 4) %} {%- for variant in e.variants() -%} - {%- let variant_type_name = variant.name()|class_name -%} + {%- let variant_type_name = variant.name() -%} {%- if e.is_flat() %} class {{ variant_type_name }}(_UniffiTemp{{ type_name }}): {%- call py::docstring(variant, 8) %} @@ -39,15 +39,15 @@ def __getitem__(self, index): return self._values[index] {%- else %} - def __init__(self{% for field in variant.fields() %}, {{ field.name()|var_name }}{% endfor %}): + def __init__(self{% for field in variant.fields() %}, {{ field.name() }}{% endfor %}): {%- if variant.has_fields() %} super().__init__(", ".join([ {%- for field in variant.fields() %} - "{{ field.name()|var_name }}={!r}".format({{ field.name()|var_name }}), + "{{ field.name() }}={!r}".format({{ field.name() }}), {%- endfor %} ])) {%- for field in variant.fields() %} - self.{{ field.name()|var_name }} = {{ field.name()|var_name }} + self.{{ field.name() }} = {{ field.name() }} {%- endfor %} {%- else %} pass @@ -70,7 +70,7 @@ def read(buf): variant = buf.read_i32() {%- for variant in e.variants() %} if variant == {{ loop.index }}: - return {{ type_name }}.{{ variant.name()|class_name }}( + return {{ type_name }}.{{ variant.name() }}( {%- if e.is_flat() %} {{ Type::String.borrow()|read_fn }}(buf), {%- else %} @@ -88,12 +88,12 @@ def check_lower(value): pass {%- else %} {%- for variant in e.variants() %} - if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}): + if isinstance(value, {{ type_name }}.{{ variant.name() }}): {%- for field in variant.fields() %} {%- if variant.has_nameless_fields() %} {{ field|check_lower_fn }}(value._values[{{ loop.index0 }}]) {%- else %} - {{ field|check_lower_fn }}(value.{{ field.name()|var_name }}) + {{ field|check_lower_fn }}(value.{{ field.name() }}) {%- endif %} {%- endfor %} return @@ -103,13 +103,13 @@ def check_lower(value): @staticmethod def write(value, buf): {%- for variant in e.variants() %} - if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}): + if isinstance(value, {{ type_name }}.{{ variant.name() }}): buf.write_i32({{ loop.index }}) {%- for field in variant.fields() %} {%- if variant.has_nameless_fields() %} {{ field|write_fn }}(value._values[{{ loop.index0 }}], buf) {%- else %} - {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {{ field|write_fn }}(value.{{ field.name() }}, buf) {%- endif %} {%- endfor %} {%- endfor %} diff --git a/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py b/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py index 6c0cee85ef..26e4639e19 100644 --- a/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py @@ -3,7 +3,7 @@ # External type {{name}} is in namespace "{{namespace}}", crate {{module_path}} {%- let ffi_converter_name = "_UniffiConverterType{}"|format(name) %} {{ self.add_import_of(module, ffi_converter_name) }} -{{ self.add_import_of(module, name|class_name) }} {#- import the type alias itself -#} +{{ self.add_import_of(module, name) }} {#- import the type alias itself -#} {%- let rustbuffer_local_name = "_UniffiRustBuffer{}"|format(name) %} {{ self.add_import_of_as(module, "_UniffiRustBuffer", rustbuffer_local_name) }} diff --git a/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py index 1929f9aad6..e5d0eba423 100644 --- a/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py @@ -76,7 +76,7 @@ def _uniffi_check_api_checksums(lib): class {{ ffi_struct.name()|ffi_struct_name }}(ctypes.Structure): _fields_ = [ {%- for field in ffi_struct.fields() %} - ("{{ field.name()|var_name }}", {{ field.type_().borrow()|ffi_type_name }}), + ("{{ field.name() }}", {{ field.type_().borrow()|ffi_type_name }}), {%- endfor %} ] {%- when FfiDefinition::Function(func) %} @@ -89,4 +89,4 @@ class {{ ffi_struct.name()|ffi_struct_name }}(ctypes.Structure): {# Ensure to call the contract verification only after we defined all functions. -#} _uniffi_check_contract_api_version(_UniffiLib) -_uniffi_check_api_checksums(_UniffiLib) +# _uniffi_check_api_checksums(_UniffiLib) diff --git a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py index 33a914195e..86029beea6 100644 --- a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py @@ -49,10 +49,9 @@ def _make_instance_(cls, pointer): return inst {%- for cons in obj.alternate_constructors() %} - @classmethod {%- if cons.is_async() %} - async def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): + async def {{ cons.name() }}(cls, {% call py::arg_list_decl(cons) %}): {%- call py::docstring(cons, 8) %} {%- call py::setup_args_extra_indent(cons) %} @@ -65,7 +64,7 @@ async def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): {% call py::error_ffi_converter(cons) %} ) {%- else %} - def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): + def {{ cons.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. @@ -75,7 +74,7 @@ def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): {% endfor %} {%- for meth in obj.methods() -%} - {%- call py::method_decl(meth.name()|fn_name, meth) %} + {%- call py::method_decl(meth.name(), meth) %} {%- endfor %} {%- for tm in obj.uniffi_traits() -%} {%- match tm %} diff --git a/uniffi_bindgen/src/bindings/python/templates/Protocol.py b/uniffi_bindgen/src/bindings/python/templates/Protocol.py index 3b7e93596a..8e5012ddd4 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Protocol.py +++ b/uniffi_bindgen/src/bindings/python/templates/Protocol.py @@ -1,7 +1,7 @@ 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) %}): + def {{ meth.name() }}(self, {% call py::arg_list_decl(meth) %}): {%- call py::docstring(meth, 8) %} raise NotImplementedError {%- else %} diff --git a/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py b/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py index cf0d0653d7..d778b90ebd 100644 --- a/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py @@ -2,18 +2,18 @@ class {{ type_name }}: {%- call py::docstring(rec, 4) %} {%- for field in rec.fields() %} - {{ field.name()|var_name }}: "{{ field|type_name }}" + {{ field.name() }}: "{{ field|type_name }}" {%- call py::docstring(field, 4) %} {%- endfor %} {%- if rec.has_fields() %} def __init__(self, *, {% for field in rec.fields() %} - {{- field.name()|var_name }}: "{{- field|type_name }}" + {{- field.name() }}: "{{- field|type_name }}" {%- if field.default_value().is_some() %} = _DEFAULT{% endif %} {%- if !loop.last %}, {% endif %} {%- endfor %}): {%- for field in rec.fields() %} - {%- let field_name = field.name()|var_name %} + {%- let field_name = field.name() %} {%- match field.default_value() %} {%- when None %} self.{{ field_name }} = {{ field_name }} @@ -27,11 +27,11 @@ def __init__(self, *, {% for field in rec.fields() %} {%- endif %} def __str__(self): - return "{{ type_name }}({% for field in rec.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in rec.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) + return "{{ type_name }}({% for field in rec.fields() %}{{ field.name() }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in rec.fields() %}self.{{ field.name() }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) def __eq__(self, other): {%- for field in rec.fields() %} - if self.{{ field.name()|var_name }} != other.{{ field.name()|var_name }}: + if self.{{ field.name() }} != other.{{ field.name() }}: return False {%- endfor %} return True @@ -41,7 +41,7 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): def read(buf): return {{ type_name }}( {%- for field in rec.fields() %} - {{ field.name()|var_name }}={{ field|read_fn }}(buf), + {{ field.name() }}={{ field|read_fn }}(buf), {%- endfor %} ) @@ -51,7 +51,7 @@ def check_lower(value): pass {%- else %} {%- for field in rec.fields() %} - {{ field|check_lower_fn }}(value.{{ field.name()|var_name }}) + {{ field|check_lower_fn }}(value.{{ field.name() }}) {%- endfor %} {%- endif %} @@ -59,7 +59,7 @@ def check_lower(value): def write(value, buf): {%- if rec.has_fields() %} {%- for field in rec.fields() %} - {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {{ field|write_fn }}(value.{{ field.name() }}, buf) {%- endfor %} {%- else %} pass diff --git a/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py b/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py index 230b9e853f..7a6cb6d7c2 100644 --- a/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py @@ -2,9 +2,9 @@ {%- match func.return_type() -%} {%- when Some with (return_type) %} -async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}": +async def {{ func.name() }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}": {% when None %} -async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> None: +async def {{ func.name() }}({%- call py::arg_list_decl(func) -%}) -> None: {% endmatch %} {%- call py::docstring(func, 4) %} @@ -28,13 +28,13 @@ async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> Non {%- match func.return_type() -%} {%- when Some with (return_type) %} -def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}": +def {{ func.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) -%}) -> None: +def {{ func.name() }}({%- call py::arg_list_decl(func) -%}) -> None: {%- call py::docstring(func, 4) %} {%- call py::setup_args(func) %} {% call py::to_ffi_call(func) %} diff --git a/uniffi_bindgen/src/bindings/python/templates/macros.py b/uniffi_bindgen/src/bindings/python/templates/macros.py index 7fdf66e02b..1a47226bd0 100644 --- a/uniffi_bindgen/src/bindings/python/templates/macros.py +++ b/uniffi_bindgen/src/bindings/python/templates/macros.py @@ -34,7 +34,7 @@ {%- macro arg_list_lowered(func) %} {%- for arg in func.arguments() %} - {{ arg|lower_fn }}({{ arg.name()|var_name }}) + {{ arg|lower_fn }}({{ arg.name() }}) {%- if !loop.last %},{% endif %} {%- endfor %} {%- endmacro -%} @@ -54,12 +54,12 @@ {#- // Arglist as used in Python declarations of methods, functions and constructors. -// Note the var_name and type_name filters. +// Note the type_name filters. -#} {% macro arg_list_decl(func) %} {%- for arg in func.arguments() -%} - {{ arg.name()|var_name }} + {{ arg.name() }} {%- match arg.default_value() %} {%- when Some with(literal) %}: "typing.Union[object, {{ arg|type_name -}}]" = _DEFAULT {%- else %}: "{{ arg|type_name -}}" @@ -88,10 +88,10 @@ {%- match arg.default_value() %} {%- when None %} {%- when Some with(literal) %} - if {{ arg.name()|var_name }} is _DEFAULT: - {{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }} + if {{ arg.name() }} is _DEFAULT: + {{ arg.name() }} = {{ literal|literal_py(arg.as_type().borrow()) }} {%- endmatch %} - {{ arg|check_lower_fn }}({{ arg.name()|var_name }}) + {{ arg|check_lower_fn }}({{ arg.name() }}) {% endfor -%} {%- endmacro -%} @@ -104,10 +104,10 @@ {%- match arg.default_value() %} {%- when None %} {%- when Some with(literal) %} - if {{ arg.name()|var_name }} is _DEFAULT: - {{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }} + if {{ arg.name() }} is _DEFAULT: + {{ arg.name() }} = {{ literal|literal_py(arg.as_type().borrow()) }} {%- endmatch %} - {{ arg|check_lower_fn }}({{ arg.name()|var_name }}) + {{ arg|check_lower_fn }}({{ arg.name() }}) {% endfor -%} {%- endmacro -%} diff --git a/uniffi_bindgen/src/bindings/python/templates/wrapper.py b/uniffi_bindgen/src/bindings/python/templates/wrapper.py index 8a932406de..4380041ad8 100644 --- a/uniffi_bindgen/src/bindings/python/templates/wrapper.py +++ b/uniffi_bindgen/src/bindings/python/templates/wrapper.py @@ -67,7 +67,7 @@ "{{ record|type_name }}", {%- endfor %} {%- for func in ci.function_definitions() %} - "{{ func.name()|fn_name }}", + "{{ func.name() }}", {%- endfor %} {%- for obj in ci.object_definitions() %} "{{ obj|type_name }}", diff --git a/uniffi_bindgen/src/bindings/python/test.rs b/uniffi_bindgen/src/bindings/python/test.rs index 0392c324d4..39dbc9c136 100644 --- a/uniffi_bindgen/src/bindings/python/test.rs +++ b/uniffi_bindgen/src/bindings/python/test.rs @@ -3,6 +3,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::bindings::RunScriptOptions; +use crate::cargo_metadata::CrateConfigSupplier; use crate::library_mode::generate_bindings; use anyhow::{Context, Result}; use camino::Utf8Path; @@ -40,6 +41,7 @@ pub fn run_script( &cdylib_path, None, &super::PythonBindingGenerator, + &CrateConfigSupplier::from(test_helper.cargo_metadata()), None, &out_dir, false, diff --git a/uniffi_bindgen/src/bindings/ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/mod.rs index f970b50fc1..e8f3c5f77c 100644 --- a/uniffi_bindgen/src/bindings/ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/mod.rs @@ -9,9 +9,9 @@ use anyhow::{Context, Result}; use fs_err as fs; mod gen_ruby; -mod test; +#[cfg(feature = "bindgen-tests")] +pub mod test; use gen_ruby::{Config, RubyWrapper}; -pub use test::run_test; pub struct RubyBindingGenerator; impl BindingGenerator for RubyBindingGenerator { diff --git a/uniffi_bindgen/src/bindings/ruby/test.rs b/uniffi_bindgen/src/bindings/ruby/test.rs index 86965fe2ff..47a3b68401 100644 --- a/uniffi_bindgen/src/bindings/ruby/test.rs +++ b/uniffi_bindgen/src/bindings/ruby/test.rs @@ -2,6 +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::cargo_metadata::CrateConfigSupplier; use crate::library_mode::generate_bindings; use anyhow::{bail, Context, Result}; use camino::Utf8Path; @@ -37,6 +38,7 @@ pub fn test_script_command( &cdylib_path, None, &super::RubyBindingGenerator, + &CrateConfigSupplier::from(test_helper.cargo_metadata()), None, &out_dir, false, diff --git a/uniffi_bindgen/src/bindings/swift/mod.rs b/uniffi_bindgen/src/bindings/swift/mod.rs index 8421b93f9e..27d2bcf0a3 100644 --- a/uniffi_bindgen/src/bindings/swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/mod.rs @@ -36,8 +36,9 @@ use std::process::Command; mod gen_swift; use gen_swift::{generate_bindings, Config}; -mod test; -pub use test::{run_script, run_test}; + +#[cfg(feature = "bindgen-tests")] +pub mod test; /// The Swift bindings generated from a [`crate::ComponentInterface`]. /// diff --git a/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift index e36feb0010..cf8eec4019 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift @@ -37,9 +37,11 @@ fileprivate extension ForeignBytes { fileprivate extension Data { init(rustBuffer: RustBuffer) { - // TODO: This copies the buffer. Can we read directly from a - // Rust buffer? - self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len)) + self.init( + bytesNoCopy: rustBuffer.data!, + count: Int(rustBuffer.len), + deallocator: .none + ) } } diff --git a/uniffi_bindgen/src/bindings/swift/test.rs b/uniffi_bindgen/src/bindings/swift/test.rs index 7417775295..9a2a5a7de9 100644 --- a/uniffi_bindgen/src/bindings/swift/test.rs +++ b/uniffi_bindgen/src/bindings/swift/test.rs @@ -3,10 +3,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::bindings::RunScriptOptions; +use crate::cargo_metadata::CrateConfigSupplier; use crate::library_mode::generate_bindings; use anyhow::{bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; +use cargo_metadata::Metadata; use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; use std::ffi::OsStr; use std::fs::{read_to_string, File}; @@ -15,10 +17,10 @@ use std::process::{Command, Stdio}; use uniffi_testing::UniFFITestHelper; /// Run Swift tests for a UniFFI test fixture -pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { +pub fn run_test(tmp_dir: &str, package_name: &str, script_file: &str) -> Result<()> { run_script( tmp_dir, - fixture_name, + package_name, script_file, vec![], &RunScriptOptions::default(), @@ -30,16 +32,21 @@ pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result< /// This function will set things up so that the script can import the UniFFI bindings for a crate pub fn run_script( tmp_dir: &str, - crate_name: &str, + package_name: &str, script_file: &str, args: Vec, options: &RunScriptOptions, ) -> Result<()> { let script_path = Utf8Path::new(script_file).canonicalize_utf8()?; - let test_helper = UniFFITestHelper::new(crate_name)?; + let test_helper = UniFFITestHelper::new(package_name)?; let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; - let generated_sources = GeneratedSources::new(crate_name, &cdylib_path, &out_dir)?; + let generated_sources = GeneratedSources::new( + test_helper.crate_name(), + &cdylib_path, + test_helper.cargo_metadata(), + &out_dir, + )?; // Compile the generated sources together to create a single swift module compile_swift_module( @@ -124,18 +131,24 @@ struct GeneratedSources { } impl GeneratedSources { - fn new(package_name: &str, cdylib_path: &Utf8Path, out_dir: &Utf8Path) -> Result { + fn new( + crate_name: &str, + cdylib_path: &Utf8Path, + cargo_metadata: Metadata, + out_dir: &Utf8Path, + ) -> Result { let sources = generate_bindings( cdylib_path, None, &super::SwiftBindingGenerator, + &CrateConfigSupplier::from(cargo_metadata), None, out_dir, false, )?; let main_source = sources .iter() - .find(|s| s.package_name.as_deref() == Some(package_name)) + .find(|s| s.ci.crate_name() == crate_name) .unwrap(); let main_module = main_source.config.module_name(); let modulemap_glob = glob(&out_dir.join("*.modulemap"))?; diff --git a/uniffi_bindgen/src/cargo_metadata.rs b/uniffi_bindgen/src/cargo_metadata.rs new file mode 100644 index 0000000000..e304025f8f --- /dev/null +++ b/uniffi_bindgen/src/cargo_metadata.rs @@ -0,0 +1,56 @@ +/* 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/. */ + +//! Helpers for data returned by cargo_metadata. Note that this doesn't +//! execute cargo_metadata, just parses its output. + +use anyhow::{bail, Context}; +use camino::Utf8PathBuf; +use cargo_metadata::Metadata; +use std::{collections::HashMap, fs}; + +use crate::BindgenCrateConfigSupplier; + +#[derive(Debug, Clone, Default)] +pub struct CrateConfigSupplier { + paths: HashMap, +} + +impl BindgenCrateConfigSupplier for CrateConfigSupplier { + fn get_toml(&self, crate_name: &str) -> anyhow::Result> { + let toml = self.paths.get(crate_name).map(|p| p.join("uniffi.toml")); + crate::load_toml_file(toml.as_deref()) + } + + fn get_udl(&self, crate_name: &str, udl_name: &str) -> anyhow::Result { + let path = self + .paths + .get(crate_name) + .context(format!("No path known to UDL files for '{crate_name}'"))? + .join("src") + .join(format!("{udl_name}.udl")); + if path.exists() { + Ok(fs::read_to_string(path)?) + } else { + bail!(format!("No UDL file found at '{path}'")); + } + } +} + +impl From for CrateConfigSupplier { + fn from(metadata: Metadata) -> Self { + let paths: HashMap = metadata + .packages + .iter() + .flat_map(|p| { + p.targets.iter().filter(|t| t.is_lib()).filter_map(|t| { + p.manifest_path + .parent() + .map(|p| (t.name.replace('-', "_"), p.to_owned())) + }) + }) + .collect(); + Self { paths } + } +} diff --git a/uniffi_bindgen/src/interface/callbacks.rs b/uniffi_bindgen/src/interface/callbacks.rs index f176a7a684..b0e978e8b4 100644 --- a/uniffi_bindgen/src/interface/callbacks.rs +++ b/uniffi_bindgen/src/interface/callbacks.rs @@ -93,11 +93,11 @@ impl CallbackInterface { } /// Vec of (ffi_callback, method) pairs - pub fn vtable_methods(&self) -> Vec<(FfiCallbackFunction, &Method)> { + pub fn vtable_methods(&self) -> Vec<(FfiCallbackFunction, Method)> { self.methods .iter() .enumerate() - .map(|(i, method)| (method_ffi_callback(&self.name, method, i), method)) + .map(|(i, method)| (method_ffi_callback(&self.name, method, i), method.clone())) .collect() } diff --git a/uniffi_bindgen/src/interface/enum_.rs b/uniffi_bindgen/src/interface/enum_.rs index 2418978b22..0d66ce2985 100644 --- a/uniffi_bindgen/src/interface/enum_.rs +++ b/uniffi_bindgen/src/interface/enum_.rs @@ -187,6 +187,10 @@ impl Enum { &self.name } + pub fn rename(&mut self, name: String) { + self.name = name; + } + pub fn variants(&self) -> &[Variant] { &self.variants } @@ -296,6 +300,10 @@ impl Variant { &self.name } + pub fn rename(&mut self, new_name: String) { + self.name = new_name; + } + pub fn fields(&self) -> &[Field] { &self.fields } diff --git a/uniffi_bindgen/src/interface/ffi.rs b/uniffi_bindgen/src/interface/ffi.rs index a1dc29713a..c0c1d33603 100644 --- a/uniffi_bindgen/src/interface/ffi.rs +++ b/uniffi_bindgen/src/interface/ffi.rs @@ -43,7 +43,7 @@ pub enum FfiType { /// or pass it to someone that will. /// If the inner option is Some, it is the name of the external type. The bindings may need /// to use this name to import the correct RustBuffer for that type. - RustBuffer(Option), + RustBuffer(Option), /// A borrowed reference to some raw bytes owned by foreign language code. /// The provider of this reference must keep it alive for the duration of the receiving call. ForeignBytes, @@ -144,13 +144,26 @@ impl From<&Type> for FfiType { Type::External { name, kind: ExternalKind::DataClass, + module_path, + namespace, .. - } => FfiType::RustBuffer(Some(name.clone())), + } => FfiType::RustBuffer(Some(ExternalFfiMetadata { + name: name.clone(), + module_path: module_path.clone(), + namespace: namespace.clone(), + })), Type::Custom { builtin, .. } => FfiType::from(builtin.as_ref()), } } } +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct ExternalFfiMetadata { + pub name: String, + pub module_path: String, + pub namespace: String, +} + // Needed for rust scaffolding askama template impl From for FfiType { fn from(ty: Type) -> Self { @@ -218,6 +231,10 @@ impl FfiFunction { &self.name } + pub fn rename(&mut self, new_name: String) { + self.name = new_name; + } + /// Name of the FFI buffer version of this function that's generated when the /// `scaffolding-ffi-buffer-fns` feature is enabled. pub fn ffi_buffer_fn_name(&self) -> String { @@ -293,6 +310,10 @@ impl FfiArgument { &self.name } + pub fn rename(&mut self, new_name: String) { + self.name = new_name; + } + pub fn type_(&self) -> FfiType { self.type_.clone() } @@ -315,6 +336,10 @@ impl FfiCallbackFunction { &self.name } + pub fn rename(&mut self, new_name: String) { + self.name = new_name; + } + pub fn arguments(&self) -> Vec<&FfiArgument> { self.arguments.iter().collect() } @@ -341,6 +366,10 @@ impl FfiStruct { &self.name } + pub fn rename(&mut self, new_name: String) { + self.name = new_name; + } + /// Get the fields for this struct pub fn fields(&self) -> &[FfiField] { &self.fields @@ -369,6 +398,10 @@ impl FfiField { pub fn type_(&self) -> FfiType { self.type_.clone() } + + pub fn rename(&mut self, name: String) { + self.name = name; + } } impl From for FfiDefinition { diff --git a/uniffi_bindgen/src/interface/function.rs b/uniffi_bindgen/src/interface/function.rs index 8effc4c876..69f4f153dc 100644 --- a/uniffi_bindgen/src/interface/function.rs +++ b/uniffi_bindgen/src/interface/function.rs @@ -33,10 +33,10 @@ //! ``` use anyhow::Result; +use uniffi_meta::Checksum; use super::ffi::{FfiArgument, FfiFunction, FfiType}; use super::{AsType, ComponentInterface, Literal, ObjectImpl, Type, TypeIterator}; -use uniffi_meta::Checksum; /// Represents a standalone function. /// @@ -73,6 +73,12 @@ impl Function { &self.name } + // Note: Don't recalculate the checksum. In order to have consistent checksums, + // we use the Rust names as input. + pub fn rename(&mut self, new_name: String) { + self.name = new_name; + } + pub fn is_async(&self) -> bool { self.is_async } @@ -194,6 +200,10 @@ impl Argument { &self.name } + pub fn rename(&mut self, new_name: String) { + self.name = new_name; + } + pub fn by_ref(&self) -> bool { self.by_ref } diff --git a/uniffi_bindgen/src/interface/mod.rs b/uniffi_bindgen/src/interface/mod.rs index 19c2152672..ec61f20cd7 100644 --- a/uniffi_bindgen/src/interface/mod.rs +++ b/uniffi_bindgen/src/interface/mod.rs @@ -67,6 +67,7 @@ mod record; pub use record::{Field, Record}; pub mod ffi; +mod visit_mut; pub use ffi::{ FfiArgument, FfiCallbackFunction, FfiDefinition, FfiField, FfiFunction, FfiStruct, FfiType, }; @@ -79,7 +80,7 @@ pub type Literal = LiteralMetadata; /// The main public interface for this module, representing the complete details of an interface exposed /// by a rust component and the details of consuming it via an extern-C FFI layer. -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct ComponentInterface { /// All of the types used in the interface. // We can't checksum `self.types`, but its contents are implied by the other fields diff --git a/uniffi_bindgen/src/interface/object.rs b/uniffi_bindgen/src/interface/object.rs index 2b86e54a45..2a9ee0b907 100644 --- a/uniffi_bindgen/src/interface/object.rs +++ b/uniffi_bindgen/src/interface/object.rs @@ -116,6 +116,10 @@ impl Object { &self.name } + pub fn rename(&mut self, new_name: String) { + self.name = new_name; + } + /// Returns the fully qualified name that should be used by Rust code for this object. /// Includes `r#`, traits get a leading `dyn`. If we ever supported associated types, then /// this would also include them. @@ -266,14 +270,14 @@ impl Object { } /// Vec of (ffi_callback_name, method) pairs - pub fn vtable_methods(&self) -> Vec<(FfiCallbackFunction, &Method)> { + pub fn vtable_methods(&self) -> Vec<(FfiCallbackFunction, Method)> { self.methods .iter() .enumerate() .map(|(i, method)| { ( callbacks::method_ffi_callback(&self.name, method, i), - method, + method.clone(), ) }) .collect() @@ -379,6 +383,10 @@ impl Constructor { &self.name } + pub fn rename(&mut self, new_name: String) { + self.name = new_name; + } + pub fn arguments(&self) -> Vec<&Argument> { self.arguments.iter().collect() } @@ -494,6 +502,10 @@ impl Method { &self.name } + pub fn rename(&mut self, new_name: String) { + self.name = new_name; + } + pub fn is_async(&self) -> bool { self.is_async } diff --git a/uniffi_bindgen/src/interface/record.rs b/uniffi_bindgen/src/interface/record.rs index e9a6004189..3af6495c99 100644 --- a/uniffi_bindgen/src/interface/record.rs +++ b/uniffi_bindgen/src/interface/record.rs @@ -69,6 +69,10 @@ impl Record { &self.name } + pub fn rename(&mut self, name: String) { + self.name = name; + } + pub fn fields(&self) -> &[Field] { &self.fields } @@ -127,6 +131,10 @@ impl Field { &self.name } + pub fn rename(&mut self, name: String) { + self.name = name; + } + pub fn default_value(&self) -> Option<&Literal> { self.default.as_ref() } diff --git a/uniffi_bindgen/src/interface/universe.rs b/uniffi_bindgen/src/interface/universe.rs index 70bc61f8a9..9a78b3bf34 100644 --- a/uniffi_bindgen/src/interface/universe.rs +++ b/uniffi_bindgen/src/interface/universe.rs @@ -21,16 +21,16 @@ pub use uniffi_meta::{AsType, ExternalKind, NamespaceMetadata, ObjectImpl, Type, /// You could imagine this struct doing some clever interning of names and so-on in future, /// to reduce the overhead of passing around [Type] instances. For now we just do a whole /// lot of cloning. -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] 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, + pub(super) type_definitions: HashMap, // All the types in the universe, by canonical type name, in a well-defined order. - all_known_types: BTreeSet, + pub(super) all_known_types: BTreeSet, } impl TypeUniverse { diff --git a/uniffi_bindgen/src/interface/visit_mut.rs b/uniffi_bindgen/src/interface/visit_mut.rs new file mode 100644 index 0000000000..31d03ae0fc --- /dev/null +++ b/uniffi_bindgen/src/interface/visit_mut.rs @@ -0,0 +1,145 @@ +use crate::interface::{Enum, FfiDefinition, Record}; +use crate::{ComponentInterface, VisitMut}; +use std::collections::{BTreeMap, BTreeSet}; +use uniffi_meta::Type; + +impl ComponentInterface { + /// A generic interface for mutating items in the [`ComponentInterface`]. + /// + /// Walk down the [`ComponentInterface`] and adjust the names of each type + /// based on the naming conventions of the supported languages. + /// + /// Each suppoerted language implements the [`VisitMut`] Trait and is able + /// to alter the functions, enums etc. to its own naming conventions. + pub fn visit_mut(&mut self, visitor: &V) { + for type_ in self.types.type_definitions.values_mut() { + visitor.visit_type(type_); + } + + let mut all_known_types_altered: BTreeSet = BTreeSet::new(); + + for type_ in self.types.all_known_types.iter() { + let mut type_altered = type_.clone(); + visitor.visit_type(&mut type_altered); + all_known_types_altered.insert(type_altered); + } + + self.types.all_known_types = all_known_types_altered; + + let mut updated_enums: BTreeMap = BTreeMap::new(); + let errors_clone = self.errors.clone(); + for (enum_name, enum_item) in self.enums.iter_mut() { + let updated_key = visitor.visit_enum_key(&mut enum_name.clone()); + let is_error = errors_clone.contains(enum_item.name()); + + visitor.visit_enum(is_error, enum_item); + + for variant in enum_item.variants.iter_mut() { + visitor.visit_variant(is_error, variant); + + for field in variant.fields.iter_mut() { + visitor.visit_field(field); + visitor.visit_type(&mut field.type_); + } + } + updated_enums.insert(updated_key, enum_item.clone()); + } + self.enums = updated_enums; + + for record_item in self.records.values_mut() { + visitor.visit_record(record_item); + + for field in &mut record_item.fields { + visitor.visit_field(field); + visitor.visit_type(&mut field.type_); + } + } + self.fix_record_keys_after_rename(); + + for function in self.functions.iter_mut() { + visitor.visit_function(function); + + if function.clone().return_type.is_some() { + let mut return_type = function.clone().return_type.unwrap(); + visitor.visit_type(&mut return_type); + function.return_type = Some(return_type); + } + + for argument in function.arguments.iter_mut() { + visitor.visit_argument(argument); + visitor.visit_type(&mut argument.type_); + } + } + + for object in self.objects.iter_mut() { + visitor.visit_object(object); + + for method in object.methods.iter_mut() { + visitor.visit_method(method); + + for argument in method.arguments.iter_mut() { + visitor.visit_argument(argument); + visitor.visit_type(&mut argument.type_); + } + + if method.clone().return_type.is_some() { + let mut return_type = method.clone().return_type.unwrap(); + visitor.visit_type(&mut return_type); + method.return_type = Some(return_type); + } + } + + for constructor in object.constructors.iter_mut() { + visitor.visit_constructor(constructor); + + for argument in constructor.arguments.iter_mut() { + visitor.visit_argument(argument); + visitor.visit_type(&mut argument.type_); + } + } + } + + for callback_interface in self.callback_interfaces.iter_mut() { + for method in callback_interface.methods.iter_mut() { + visitor.visit_method(method); + + for argument in method.arguments.iter_mut() { + visitor.visit_argument(argument); + visitor.visit_type(&mut argument.type_); + } + } + + for ffi_arg in callback_interface.ffi_init_callback.arguments.iter_mut() { + visitor.visit_ffi_argument(ffi_arg); + } + } + + let mut throw_types_altered: BTreeSet = BTreeSet::new(); + + for throw_type in self.callback_interface_throws_types.iter() { + let mut type_ = throw_type.clone(); + visitor.visit_type(&mut type_); + throw_types_altered.insert(type_); + } + + self.callback_interface_throws_types = throw_types_altered; + + for ffi_definition in self.ffi_definitions() { + if let FfiDefinition::Struct(mut ffi_struct) = ffi_definition { + for field in ffi_struct.fields.iter_mut() { + visitor.visit_ffi_field(field); + } + } + } + } + + fn fix_record_keys_after_rename(&mut self) { + let mut new_records: BTreeMap = BTreeMap::new(); + + for record in self.records.values() { + new_records.insert(record.name().to_string(), record.clone()); + } + + self.records = new_records; + } +} diff --git a/uniffi_bindgen/src/lib.rs b/uniffi_bindgen/src/lib.rs index 18f20f3ad6..6d32bd4784 100644 --- a/uniffi_bindgen/src/lib.rs +++ b/uniffi_bindgen/src/lib.rs @@ -107,8 +107,17 @@ pub mod library_mode; pub mod macro_metadata; pub mod scaffolding; +#[cfg(feature = "cargo-metadata")] +pub mod cargo_metadata; + +use crate::interface::{ + Argument, Constructor, Enum, FfiArgument, FfiField, Field, Function, Method, Object, Record, + Variant, +}; pub use interface::ComponentInterface; +pub use library_mode::find_components; use scaffolding::RustScaffolding; +use uniffi_meta::Type; /// The options used when creating bindings. Named such /// it doesn't cause confusion that it's settings specific to @@ -157,14 +166,94 @@ pub trait BindingGenerator: Sized { ) -> Result<()>; } +/// A trait to alter language specific type representations. +/// +/// It is meant to be implemented by each language oracle. It takes a +/// ['ComponentInterface'] and uses its own specific language adjustment +/// functions to be able to generate language specific templates. +pub trait VisitMut { + /// Go through each `Record` of a [`ComponentInterface`] and + /// adjust it to language specific naming conventions. + fn visit_record(&self, record: &mut Record); + + /// Change the name of an `Object` of a [`ComponentInterface` + /// to language specific naming conventions. + fn visit_object(&self, object: &mut Object); + + /// Change the name of a `Field` of an `Enum` `Variant` + /// to language specific naming conventions. + fn visit_field(&self, field: &mut Field); + + /// Change the name of a `FfiField` inside a `FfiStruct` + /// to language specific naming conventions. + fn visit_ffi_field(&self, ffi_field: &mut FfiField); + + /// Change the `Arugment` of a `FfiFunction` in the [`ComponentInterface`] + /// to language specific naming conventions. + fn visit_ffi_argument(&self, ffi_argument: &mut FfiArgument); + + /// Go through each `Enum` of a [`ComponentInterface`] and + /// adjust it to language specific naming conventions. + fn visit_enum(&self, is_error: bool, enum_: &mut Enum); + + /// Change the naming of the key in the [`ComponentInterface`] + /// `BTreeMap` where all `Enum`s are stored to reflect the changed + /// name of an `Enum`. + fn visit_enum_key(&self, key: &mut String) -> String; + + /// Go through each `Variant` of an `Enum` and + /// adjust it to language specific naming conventions. + fn visit_variant(&self, is_error: bool, variant: &mut Variant); + + /// Go through each `Type` in the `TypeUniverse` of + /// a [`ComponentInterface`] and adjust it to language specific + /// naming conventions. + fn visit_type(&self, type_: &mut Type); + + /// Go through each `Method` of an `Object` and + /// adjust it to language specific naming conventions. + fn visit_method(&self, method: &mut Method); + + /// Go through each `Argument` of a `Function` and + /// adjust it to language specific naming conventions. + fn visit_argument(&self, argument: &mut Argument); + + /// Go through each `Constructor` of a [`ComponentInterface`] and + /// adjust it to language specific naming conventions. + fn visit_constructor(&self, constructor: &mut Constructor); + + /// Go through each `Function` of a [`ComponentInterface`] and + /// adjust it to language specific naming conventions. + fn visit_function(&self, function: &mut Function); +} + /// Everything needed to generate a ComponentInterface. #[derive(Debug)] pub struct Component { pub ci: ComponentInterface, pub config: Config, - pub package_name: Option, } +/// A trait used by the bindgen to obtain config information about a source crate +/// which was found in the metadata for the library. +/// +/// This is an abstraction around needing the source directory for a crate. +/// In most cases `cargo_metadata` can be used, but this should be able to work in +/// more environments. +pub trait BindgenCrateConfigSupplier { + /// Get the toml for the crate. Probably came from uniffi.toml in the root of the crate source. + fn get_toml(&self, _crate_name: &str) -> Result> { + Ok(None) + } + /// Obtains the contents of the named UDL file which was referenced by the type metadata. + fn get_udl(&self, crate_name: &str, udl_name: &str) -> Result { + bail!("Crate {crate_name} has no UDL {udl_name}") + } +} + +pub struct EmptyCrateConfigSupplier; +impl BindgenCrateConfigSupplier for EmptyCrateConfigSupplier {} + /// A convenience function for the CLI to help avoid using static libs /// in places cdylibs are required. pub fn is_cdylib(library_file: impl AsRef) -> bool { @@ -207,8 +296,11 @@ pub fn generate_external_bindings( let config_file_override = config_file_override.as_ref().map(|p| p.as_ref()); let config = { - let toml = load_initial_config(crate_root, config_file_override)?; - binding_generator.new_config(&toml)? + let crate_config = load_toml_file(Some(&crate_root.join("uniffi.toml"))) + .context("failed to load {crate_root}/uniffi.toml")?; + let toml_value = + overridden_config_value(crate_config.unwrap_or_default(), config_file_override)?; + binding_generator.new_config(&toml_value)? }; let settings = GenerationSettings { @@ -225,11 +317,7 @@ pub fn generate_external_bindings( try_format_code, }; - let mut components = vec![Component { - ci, - config, - package_name: None, - }]; + let mut components = vec![Component { ci, config }]; binding_generator.update_component_configs(&settings, &mut components)?; binding_generator.write_bindings(&settings, &components) } @@ -410,19 +498,14 @@ fn load_toml_file(source: Option<&Utf8Path>) -> Result, ) -> Result { - let mut config = load_toml_file(Some(crate_root.join("uniffi.toml").as_path())) - .context("default config")? - .unwrap_or(toml::value::Table::default()); - let override_config = load_toml_file(config_file_override).context("override config")?; if let Some(override_config) = override_config { merge_toml(&mut config, override_config); } - Ok(toml::Value::from(config)) } diff --git a/uniffi_bindgen/src/library_mode.rs b/uniffi_bindgen/src/library_mode.rs index 47dfc75504..3a6a3f0133 100644 --- a/uniffi_bindgen/src/library_mode.rs +++ b/uniffi_bindgen/src/library_mode.rs @@ -16,50 +16,39 @@ /// - UniFFI can figure out the package/module names for each crate, eliminating the external /// package maps. use crate::{ - load_initial_config, macro_metadata, BindingGenerator, Component, ComponentInterface, - GenerationSettings, Result, + macro_metadata, overridden_config_value, BindgenCrateConfigSupplier, BindingGenerator, + Component, ComponentInterface, GenerationSettings, Result, }; -use anyhow::{bail, Context}; +use anyhow::bail; use camino::Utf8Path; -use cargo_metadata::{MetadataCommand, Package}; use std::{collections::HashMap, fs}; +use toml::value::Table as TomlTable; use uniffi_meta::{ create_metadata_groups, fixup_external_type, group_metadata, Metadata, MetadataGroup, }; /// Generate foreign bindings /// +/// This replicates the current process used for generating the builtin bindings. +/// External bindings authors should consider using [find_components], which provides a simpler +/// interface and allows for more flexibility in how the external bindings are generated. +/// /// Returns the list of sources used to generate the bindings, in no particular order. -// XXX - we should consider killing this function and replace it with a function -// which just locates the `Components` and returns them, leaving the filtering -// and actual generation to the callers, which also would allow removing the potentially -// confusing crate_name param. pub fn generate_bindings( library_path: &Utf8Path, crate_name: Option, binding_generator: &T, + config_supplier: &dyn BindgenCrateConfigSupplier, config_file_override: Option<&Utf8Path>, out_dir: &Utf8Path, try_format_code: bool, ) -> Result>> { - let cargo_metadata = MetadataCommand::new() - .exec() - .context("error running cargo metadata")?; - - let mut components = find_components(&cargo_metadata, library_path)? + let mut components = find_components(library_path, config_supplier)? .into_iter() - .map(|(ci, package)| { - let crate_root = package - .manifest_path - .parent() - .context("manifest path has no parent")?; - let config = binding_generator - .new_config(&load_initial_config(crate_root, config_file_override)?)?; - Ok(Component { - ci, - config, - package_name: Some(package.name), - }) + .map(|Component { ci, config }| { + let toml_value = overridden_config_value(config, config_file_override)?; + let config = binding_generator.new_config(&toml_value)?; + Ok(Component { ci, config }) }) .collect::>>()?; @@ -101,10 +90,17 @@ pub fn calc_cdylib_name(library_path: &Utf8Path) -> Option<&str> { None } -fn find_components( - cargo_metadata: &cargo_metadata::Metadata, +/// Find UniFFI components from a shared library file +/// +/// This method inspects the library file and creates [ComponentInterface] instances for each +/// component used to build it. It parses the UDL files from `uniffi::include_scaffolding!` macro +/// calls. +/// +/// `config_supplier` is used to find UDL files on disk and load config data. +pub fn find_components( library_path: &Utf8Path, -) -> Result> { + config_supplier: &dyn BindgenCrateConfigSupplier, +) -> Result>> { let items = macro_metadata::extract_from_library(library_path)?; let mut metadata_groups = create_metadata_groups(&items); group_metadata(&mut metadata_groups, items)?; @@ -114,13 +110,8 @@ fn find_components( let mut udl_items: HashMap = HashMap::new(); for group in metadata_groups.values() { - let package = find_package_by_crate_name(cargo_metadata, &group.namespace.crate_name)?; - let crate_root = package - .manifest_path - .parent() - .context("manifest path has no parent")?; let crate_name = group.namespace.crate_name.clone(); - if let Some(mut metadata_group) = load_udl_metadata(group, crate_root, &crate_name)? { + if let Some(mut metadata_group) = load_udl_metadata(group, &crate_name, config_supplier)? { // fixup the items. metadata_group.items = metadata_group .items @@ -138,40 +129,24 @@ fn find_components( metadata_groups .into_values() .map(|group| { - let package = find_package_by_crate_name(cargo_metadata, &group.namespace.crate_name)?; - let mut ci = ComponentInterface::new(&group.namespace.crate_name); - if let Some(metadata) = udl_items.remove(&group.namespace.crate_name) { + let crate_name = &group.namespace.crate_name; + let mut ci = ComponentInterface::new(crate_name); + if let Some(metadata) = udl_items.remove(crate_name) { ci.add_metadata(metadata)?; }; ci.add_metadata(group)?; - Ok((ci, package)) + let config = config_supplier + .get_toml(ci.crate_name())? + .unwrap_or_default(); + Ok(Component { ci, config }) }) .collect() } -fn find_package_by_crate_name( - metadata: &cargo_metadata::Metadata, - crate_name: &str, -) -> Result { - let matching: Vec<&Package> = metadata - .packages - .iter() - .filter(|p| { - p.targets - .iter() - .any(|t| t.name.replace('-', "_") == crate_name) - }) - .collect(); - match matching.len() { - 1 => Ok(matching[0].clone()), - n => bail!("cargo metadata returned {n} packages for crate name {crate_name}"), - } -} - fn load_udl_metadata( group: &MetadataGroup, - crate_root: &Utf8Path, crate_name: &str, + config_supplier: &dyn BindgenCrateConfigSupplier, ) -> Result> { let udl_items = group .items @@ -181,10 +156,9 @@ fn load_udl_metadata( _ => None, }) .collect::>(); + // We only support 1 UDL file per crate, for no good reason! match udl_items.len() { - // No UDL files, load directly from the group 0 => Ok(None), - // Found a UDL file, use it to load the CI, then add the MetadataGroup 1 => { if udl_items[0].module_path != crate_name { bail!( @@ -193,17 +167,11 @@ fn load_udl_metadata( crate_name ); } - let ci_name = &udl_items[0].file_stub; - let ci_path = crate_root.join("src").join(format!("{ci_name}.udl")); - if ci_path.exists() { - let udl = fs::read_to_string(ci_path)?; - let udl_group = uniffi_udl::parse_udl(&udl, crate_name)?; - Ok(Some(udl_group)) - } else { - bail!("{ci_path} not found"); - } + let udl = config_supplier.get_udl(crate_name, &udl_items[0].file_stub)?; + let udl_group = uniffi_udl::parse_udl(&udl, crate_name)?; + Ok(Some(udl_group)) } - n => bail!("{n} UDL files found for {crate_root}"), + n => bail!("{n} UDL files found for {crate_name}"), } } diff --git a/uniffi_bindgen/src/scaffolding/mod.rs b/uniffi_bindgen/src/scaffolding/mod.rs index 7fd81831aa..9c410cc714 100644 --- a/uniffi_bindgen/src/scaffolding/mod.rs +++ b/uniffi_bindgen/src/scaffolding/mod.rs @@ -36,24 +36,24 @@ mod filters { Type::Float32 => "f32".into(), Type::Float64 => "f64".into(), Type::Boolean => "bool".into(), - Type::String => "String".into(), - Type::Bytes => "Vec".into(), - Type::Timestamp => "std::time::SystemTime".into(), - Type::Duration => "std::time::Duration".into(), + Type::String => "::std::string::String".into(), + Type::Bytes => "::std::vec::Vec".into(), + Type::Timestamp => "::std::time::SystemTime".into(), + Type::Duration => "::std::time::Duration".into(), Type::Enum { name, .. } | Type::Record { name, .. } => format!("r#{name}"), Type::Object { name, imp, .. } => { - format!("std::sync::Arc<{}>", imp.rust_name_for(name)) + format!("::std::sync::Arc<{}>", imp.rust_name_for(name)) } Type::CallbackInterface { name, .. } => format!("Box"), Type::Optional { inner_type } => { - format!("std::option::Option<{}>", type_rs(inner_type)?) + format!("::std::option::Option<{}>", type_rs(inner_type)?) } Type::Sequence { inner_type } => format!("std::vec::Vec<{}>", type_rs(inner_type)?), Type::Map { key_type, value_type, } => format!( - "std::collections::HashMap<{}, {}>", + "::std::collections::HashMap<{}, {}>", type_rs(key_type)?, type_rs(value_type)? ), diff --git a/uniffi_build/Cargo.toml b/uniffi_build/Cargo.toml index 799957928e..e03823837b 100644 --- a/uniffi_build/Cargo.toml +++ b/uniffi_build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_build" -version = "0.28.0" +version = "0.28.1" authors = ["Firefox Sync Team "] description = "a multi-language bindings generator for rust (build script helpers)" documentation = "https://mozilla.github.io/uniffi-rs" @@ -14,7 +14,7 @@ readme = "../README.md" [dependencies] anyhow = "1" camino = "1.0.8" -uniffi_bindgen = { path = "../uniffi_bindgen", default-features = false, version = "=0.28.0" } +uniffi_bindgen = { path = "../uniffi_bindgen", default-features = false, version = "=0.28.1" } [features] default = [] diff --git a/uniffi_checksum_derive/Cargo.toml b/uniffi_checksum_derive/Cargo.toml index f68affd79b..a2c93c3ade 100644 --- a/uniffi_checksum_derive/Cargo.toml +++ b/uniffi_checksum_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_checksum_derive" -version = "0.28.0" +version = "0.28.1" 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 0273575923..ce67b7f754 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.28.0" +version = "0.28.1" 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 e7a4faab64..33db6f0928 100644 --- a/uniffi_core/src/ffi/callbackinterface.rs +++ b/uniffi_core/src/ffi/callbackinterface.rs @@ -195,7 +195,7 @@ macro_rules! convert_unexpected_error { fn get_converter(&self) -> $crate::UnexpectedUniFFICallbackErrorConverterSpecialized; } - impl> GetConverterSpecialized for T { + impl> GetConverterSpecialized for T { fn get_converter(&self) -> $crate::UnexpectedUniFFICallbackErrorConverterSpecialized { $crate::UnexpectedUniFFICallbackErrorConverterSpecialized } diff --git a/uniffi_core/src/ffi/ffiserialize.rs b/uniffi_core/src/ffi/ffiserialize.rs index 2ef91a8270..edbeb51229 100644 --- a/uniffi_core/src/ffi/ffiserialize.rs +++ b/uniffi_core/src/ffi/ffiserialize.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::{Handle, RustBuffer, RustCallStatus, RustCallStatusCode}; -use std::{mem::MaybeUninit, ptr::NonNull}; +use std::{mem::ManuallyDrop, ptr::NonNull}; /// FFIBuffer element /// @@ -81,7 +81,7 @@ pub trait FfiSerialize: Sized { fn write(buf: &mut &mut [FfiBufferElement], value: Self) { Self::put(buf, value); // Lifetime dance taken from `bytes::BufMut` - let (_, new_buf) = core::mem::take(buf).split_at_mut(Self::SIZE); + let (_, new_buf) = ::core::mem::take(buf).split_at_mut(Self::SIZE); *buf = new_buf; } } @@ -228,15 +228,13 @@ impl FfiSerialize for RustCallStatus { let code = unsafe { buf[0].i8 }; Self { code: RustCallStatusCode::try_from(code).unwrap_or(RustCallStatusCode::UnexpectedError), - error_buf: MaybeUninit::new(RustBuffer::get(&buf[1..])), + error_buf: ManuallyDrop::new(RustBuffer::get(&buf[1..])), } } fn put(buf: &mut [FfiBufferElement], value: Self) { buf[0].i8 = value.code as i8; - // Safety: This is okay even if the error buf is not initialized. It just means we'll be - // copying the garbage data. - unsafe { RustBuffer::put(&mut buf[1..], value.error_buf.assume_init()) } + RustBuffer::put(&mut buf[1..], ManuallyDrop::into_inner(value.error_buf)) } } @@ -277,9 +275,9 @@ mod test { rust_buffer.len(), rust_buffer.capacity(), ); - let handle = Handle::from_raw(101).unwrap(); - let rust_call_status = RustCallStatus::new(); - let rust_call_status_error_buf = unsafe { rust_call_status.error_buf.assume_init_ref() }; + let handle = unsafe { Handle::from_raw(101).unwrap() }; + let rust_call_status = RustCallStatus::default(); + let rust_call_status_error_buf = &rust_call_status.error_buf; let orig_rust_call_status_buffer_data = ( rust_call_status_error_buf.data_pointer(), rust_call_status_error_buf.len(), @@ -334,7 +332,7 @@ mod test { let rust_call_status2 = ::read(&mut buf_reader); assert_eq!(rust_call_status2.code, RustCallStatusCode::Success); - let rust_call_status2_error_buf = unsafe { rust_call_status2.error_buf.assume_init() }; + let rust_call_status2_error_buf = ManuallyDrop::into_inner(rust_call_status2.error_buf); assert_eq!( ( rust_call_status2_error_buf.data_pointer(), diff --git a/uniffi_core/src/ffi/foreignfuture.rs b/uniffi_core/src/ffi/foreignfuture.rs index 87d8b9a4e7..804beaef62 100644 --- a/uniffi_core/src/ffi/foreignfuture.rs +++ b/uniffi_core/src/ffi/foreignfuture.rs @@ -147,7 +147,7 @@ mod test { *data, ForeignFutureResult { return_value: >::lower(value), - call_status: RustCallStatus::new(), + call_status: RustCallStatus::default(), }, ); } diff --git a/uniffi_core/src/ffi/handle.rs b/uniffi_core/src/ffi/handle.rs index 8ee2f46c35..8ca66a41de 100644 --- a/uniffi_core/src/ffi/handle.rs +++ b/uniffi_core/src/ffi/handle.rs @@ -12,7 +12,7 @@ /// 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). +/// leaked pointer will be aligned to `size_of::>()` == `size_of::<*const T>()`). /// /// Rust handles are mainly managed is through the [crate::HandleAlloc] trait. #[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] @@ -28,7 +28,9 @@ impl Handle { self.0 as *const T } - pub fn from_raw(raw: u64) -> Option { + /// # Safety + /// The raw value must be a valid handle as described above. + pub unsafe fn from_raw(raw: u64) -> Option { if raw == 0 { None } else { diff --git a/uniffi_core/src/ffi/rustbuffer.rs b/uniffi_core/src/ffi/rustbuffer.rs index 47eafbdb96..9c718aa611 100644 --- a/uniffi_core/src/ffi/rustbuffer.rs +++ b/uniffi_core/src/ffi/rustbuffer.rs @@ -78,13 +78,10 @@ impl RustBuffer { /// Creates a `RustBuffer` from its constituent fields. /// - /// This is intended mainly as an internal convenience function and should not - /// be used outside of this module. - /// /// # Safety /// /// You must ensure that the raw parts uphold the documented invariants of this class. - pub unsafe fn from_raw_parts(data: *mut u8, len: u64, capacity: u64) -> Self { + pub(crate) unsafe fn from_raw_parts(data: *mut u8, len: u64, capacity: u64) -> Self { Self { capacity, len, diff --git a/uniffi_core/src/ffi/rustcalls.rs b/uniffi_core/src/ffi/rustcalls.rs index 91d3fe2472..c4cd1ac03a 100644 --- a/uniffi_core/src/ffi/rustcalls.rs +++ b/uniffi_core/src/ffi/rustcalls.rs @@ -12,7 +12,7 @@ //! exception use crate::{FfiDefault, Lower, RustBuffer, UniFfiTag}; -use std::mem::MaybeUninit; +use std::mem::ManuallyDrop; use std::panic; /// Represents the success/error of a rust call @@ -42,52 +42,43 @@ use std::panic; #[repr(C)] pub struct RustCallStatus { pub code: RustCallStatusCode, - // code is signed because unsigned types are experimental in Kotlin - pub error_buf: MaybeUninit, - // error_buf is MaybeUninit to avoid dropping the value that the consumer code sends in: - // - Consumers should send in a zeroed out RustBuffer. In this case dropping is a no-op and - // avoiding the drop is a small optimization. - // - If consumers pass in invalid data, then we should avoid trying to drop it. In - // particular, we don't want to try to free any data the consumer has allocated. - // - // `MaybeUninit` requires unsafe code, since we are preventing rust from dropping the value. - // To use this safely we need to make sure that no code paths set this twice, since that will - // leak the first `RustBuffer`. + // error_buf is owned by the foreign side. + // - Whatever we are passed, we must never free. This however implies we must be passed + // an empty `RustBuffer` otherwise it would leak when we replace it with our own. + // - On error we will set it to a `RustBuffer` we expect the foreign side to free. + // We assume initialization, which means we can use `ManuallyDrop` instead of + // `MaybeUninit`, which avoids unsafe code and clarifies ownership. + // We must take care to not set this twice to avoid leaking the first `RustBuffer`. + pub error_buf: ManuallyDrop, } -impl RustCallStatus { - pub fn new() -> Self { +impl Default for RustCallStatus { + fn default() -> Self { Self { code: RustCallStatusCode::Success, - error_buf: MaybeUninit::new(RustBuffer::new()), + error_buf: Default::default(), } } +} +impl RustCallStatus { pub fn cancelled() -> Self { Self { code: RustCallStatusCode::Cancelled, - error_buf: MaybeUninit::new(RustBuffer::new()), + error_buf: Default::default(), } } pub fn error(message: impl Into) -> Self { Self { code: RustCallStatusCode::UnexpectedError, - error_buf: MaybeUninit::new(>::lower(message.into())), - } - } -} - -impl Default for RustCallStatus { - fn default() -> Self { - Self { - code: RustCallStatusCode::Success, - error_buf: MaybeUninit::uninit(), + error_buf: ManuallyDrop::new(>::lower(message.into())), } } } /// Result of a FFI call to a Rust function +/// Value is signed to avoid Kotlin's experimental unsigned types. #[repr(i8)] #[derive(Debug, PartialEq, Eq)] pub enum RustCallStatusCode { @@ -120,6 +111,29 @@ impl TryFrom for RustCallStatusCode { } } +/// Error type for Rust scaffolding calls +/// +/// This enum represents the fact that there are two ways for a scaffolding call to fail: +/// - Expected errors (the Rust function returned an `Err` value). +/// - Unexpected errors (there was a failure calling the Rust function, for example the failure to +/// lift the arguments). +pub enum RustCallError { + /// The Rust function returned an `Err` value. + /// + /// The associated value is the serialized `Err` value. + Error(RustBuffer), + /// There was a failure to call the Rust function, for example a failure to lift the arguments. + /// + /// The associated value is a message string for the error. + InternalError(String), +} + +/// Error when trying to lift arguments to pass to the scaffolding call +pub struct LiftArgsError { + pub arg_name: &'static str, + pub error: anyhow::Error, +} + /// Handle a scaffolding calls /// /// `callback` is responsible for making the actual Rust call and returning a special result type: @@ -141,7 +155,7 @@ impl TryFrom for RustCallStatusCode { /// - `FfiDefault::ffi_default()` is returned, although foreign code should ignore this value pub fn rust_call(out_status: &mut RustCallStatus, callback: F) -> R where - F: panic::UnwindSafe + FnOnce() -> Result, + F: panic::UnwindSafe + FnOnce() -> Result, R: FfiDefault, { rust_call_with_out_status(out_status, callback).unwrap_or_else(R::ffi_default) @@ -158,7 +172,7 @@ pub(crate) fn rust_call_with_out_status( callback: F, ) -> Option where - F: panic::UnwindSafe + FnOnce() -> Result, + F: panic::UnwindSafe + FnOnce() -> Result, { let result = panic::catch_unwind(|| { crate::panichook::ensure_setup(); @@ -169,13 +183,14 @@ where // initializes it to [RustCallStatusCode::Success] Ok(Ok(v)) => Some(v), // Callback returned an Err. - Ok(Err(buf)) => { + Ok(Err(RustCallError::Error(buf))) => { out_status.code = RustCallStatusCode::Error; - unsafe { - // Unsafe because we're setting the `MaybeUninit` value, see above for safety - // invariants. - out_status.error_buf.as_mut_ptr().write(buf); - } + *out_status.error_buf = buf; + None + } + Ok(Err(RustCallError::InternalError(msg))) => { + out_status.code = RustCallStatusCode::UnexpectedError; + *out_status.error_buf = >::lower(msg); None } // Callback panicked @@ -196,11 +211,9 @@ where >::lower(message) })); if let Ok(buf) = message_result { - unsafe { - // Unsafe because we're setting the `MaybeUninit` value, see above for safety - // invariants. - out_status.error_buf.as_mut_ptr().write(buf); - } + // If this was ever set twice we'd leak the old value - but because this is the only + // place where it is set, and this is only called once, no leaks should exist in practice. + *out_status.error_buf = buf; } // Ignore the error case. We've done all that we can at this point. In the bindings // code, we handle this by checking if `error_buf` still has an empty `RustBuffer` and @@ -214,53 +227,58 @@ where mod test { use super::*; use crate::{test_util::TestError, Lift, LowerReturn}; - - fn create_call_status() -> RustCallStatus { - RustCallStatus { - code: RustCallStatusCode::Success, - error_buf: MaybeUninit::new(RustBuffer::new()), - } - } - - fn test_callback(a: u8) -> Result { - match a { - 0 => Ok(100), - 1 => Err(TestError("Error".to_owned())), - x => panic!("Unexpected value: {x}"), - } - } + use anyhow::anyhow; #[test] fn test_rust_call() { - let mut status = create_call_status(); + // Successful call + let mut status = RustCallStatus::default(); let return_value = rust_call(&mut status, || { - as LowerReturn>::lower_return(test_callback(0)) + as LowerReturn>::lower_return(Ok(100)) }); assert_eq!(status.code, RustCallStatusCode::Success); assert_eq!(return_value, 100); + // Successful call that returns an Err value + let mut status = RustCallStatus::default(); rust_call(&mut status, || { - as LowerReturn>::lower_return(test_callback(1)) + as LowerReturn>::lower_return(Err(TestError( + "Error".into(), + ))) }); assert_eq!(status.code, RustCallStatusCode::Error); - unsafe { - assert_eq!( - >::try_lift(status.error_buf.assume_init()).unwrap(), - TestError("Error".to_owned()) - ); - } + assert_eq!( + >::try_lift(ManuallyDrop::into_inner(status.error_buf)) + .unwrap(), + TestError("Error".to_owned()) + ); - let mut status = create_call_status(); + // Internal error while trying to make the call + let mut status = RustCallStatus::default(); rust_call(&mut status, || { - as LowerReturn>::lower_return(test_callback(2)) + as LowerReturn>::handle_failed_lift(LiftArgsError { + arg_name: "foo", + error: anyhow!("invalid handle"), + }) }); assert_eq!(status.code, RustCallStatusCode::UnexpectedError); - unsafe { - assert_eq!( - >::try_lift(status.error_buf.assume_init()).unwrap(), - "Unexpected value: 2" - ); - } + assert_eq!( + >::try_lift(ManuallyDrop::into_inner(status.error_buf)) + .unwrap(), + "Failed to convert arg 'foo': invalid handle" + ); + + // Panic inside the call + let mut status = RustCallStatus::default(); + rust_call(&mut status, || -> Result { + panic!("I crashed") + }); + assert_eq!(status.code, RustCallStatusCode::UnexpectedError); + assert_eq!( + >::try_lift(ManuallyDrop::into_inner(status.error_buf)) + .unwrap(), + "I crashed" + ); } } diff --git a/uniffi_core/src/ffi/rustfuture/future.rs b/uniffi_core/src/ffi/rustfuture/future.rs index 93c34e7543..ee468ffb97 100644 --- a/uniffi_core/src/ffi/rustfuture/future.rs +++ b/uniffi_core/src/ffi/rustfuture/future.rs @@ -86,13 +86,13 @@ use std::{ }; use super::{RustFutureContinuationCallback, RustFuturePoll, Scheduler}; -use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus}; +use crate::{rust_call_with_out_status, FfiDefault, LiftArgsError, LowerReturn, RustCallStatus}; /// 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, + F: Future> + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, { @@ -106,7 +106,7 @@ where impl WrappedFuture where // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, + F: Future> + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, { @@ -139,7 +139,8 @@ where // 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), + Poll::Ready(Ok(v)) => T::lower_return(v).map(Poll::Ready), + Poll::Ready(Err(e)) => T::handle_failed_lift(e).map(Poll::Ready), }), ); match result { @@ -185,7 +186,7 @@ where unsafe impl Send for WrappedFuture where // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, + F: Future> + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, { @@ -195,7 +196,7 @@ where pub(super) struct RustFuture where // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, + F: Future> + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, { @@ -211,7 +212,7 @@ where impl RustFuture where // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, + F: Future> + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, { @@ -263,7 +264,7 @@ where impl Wake for RustFuture where // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, + F: Future> + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, { @@ -298,7 +299,7 @@ pub trait RustFutureFfi: Send + Sync { impl RustFutureFfi for RustFuture where // See rust_future_new for an explanation of these trait bounds - F: Future + Send + 'static, + F: Future> + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, { diff --git a/uniffi_core/src/ffi/rustfuture/mod.rs b/uniffi_core/src/ffi/rustfuture/mod.rs index 3d3505e5ef..01202c5752 100644 --- a/uniffi_core/src/ffi/rustfuture/mod.rs +++ b/uniffi_core/src/ffi/rustfuture/mod.rs @@ -12,7 +12,7 @@ use scheduler::*; #[cfg(test)] mod tests; -use crate::{derive_ffi_traits, Handle, HandleAlloc, LowerReturn, RustCallStatus}; +use crate::{derive_ffi_traits, Handle, HandleAlloc, LiftArgsError, LowerReturn, RustCallStatus}; /// Result code for [rust_future_poll]. This is passed to the continuation function. #[repr(i8)] @@ -42,7 +42,7 @@ where // 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, + 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, diff --git a/uniffi_core/src/ffi/rustfuture/tests.rs b/uniffi_core/src/ffi/rustfuture/tests.rs index 369ef2eabc..6bd9b6c93e 100644 --- a/uniffi_core/src/ffi/rustfuture/tests.rs +++ b/uniffi_core/src/ffi/rustfuture/tests.rs @@ -1,6 +1,7 @@ use once_cell::sync::OnceCell; use std::{ future::Future, + mem::ManuallyDrop, panic, pin::Pin, sync::{Arc, Mutex}, @@ -12,7 +13,7 @@ use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode}; // Sender/Receiver pair that we use for testing struct Channel { - result: Option>, + result: Option, LiftArgsError>>, waker: Option, } @@ -28,7 +29,21 @@ impl Sender { fn send(&self, value: Result) { let mut inner = self.0.lock().unwrap(); - if inner.result.replace(value).is_some() { + if inner.result.replace(Ok(value)).is_some() { + panic!("value already sent"); + } + if let Some(waker) = &inner.waker { + waker.wake_by_ref(); + } + } + + fn send_lift_args_error(&self, arg_name: &'static str, error: anyhow::Error) { + let mut inner = self.0.lock().unwrap(); + if inner + .result + .replace(Err(LiftArgsError { arg_name, error })) + .is_some() + { panic!("value already sent"); } if let Some(waker) = &inner.waker { @@ -40,12 +55,15 @@ impl Sender { struct Receiver(Arc>); impl Future for Receiver { - type Output = Result; + type Output = Result, LiftArgsError>; - fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll> { + fn poll( + self: Pin<&mut Self>, + context: &mut Context<'_>, + ) -> Poll, LiftArgsError>> { let mut inner = self.0.lock().unwrap(); - match &inner.result { - Some(v) => Poll::Ready(v.clone()), + match inner.result.take() { + Some(v) => Poll::Ready(v), None => { inner.waker = Some(context.waker().clone()); Poll::Pending @@ -126,15 +144,36 @@ fn test_error() { 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"), - ) - } + assert_eq!( + >::try_lift_from_rust_buffer(ManuallyDrop::into_inner( + call_status.error_buf + )) + .unwrap(), + TestError::from("Something went wrong"), + ) +} + +#[test] +fn test_lift_args_error() { + let (sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.send_lift_args_error("arg0", anyhow::anyhow!("Invalid handle")); + 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::UnexpectedError); + assert_eq!( + >::try_lift(ManuallyDrop::into_inner( + call_status.error_buf + )) + .unwrap(), + "Failed to convert arg 'arg0': Invalid handle", + ) } // Once `complete` is called, the inner future should be released, even if wakers still hold a @@ -204,7 +243,7 @@ fn test_wake_during_poll() { Poll::Pending } else { // The second time we're polled, we're ready - Poll::Ready("All done".to_owned()) + Poll::Ready(Ok("All done".to_owned())) } }); let rust_future: Arc> = RustFuture::new(future, crate::UniFfiTag); diff --git a/uniffi_core/src/ffi_converter_impls.rs b/uniffi_core/src/ffi_converter_impls.rs index 34ef5c6074..714fce16ba 100644 --- a/uniffi_core/src/ffi_converter_impls.rs +++ b/uniffi_core/src/ffi_converter_impls.rs @@ -23,8 +23,9 @@ /// "UT" means an arbitrary `UniFfiTag` type. use crate::{ check_remaining, derive_ffi_traits, ffi_converter_rust_buffer_lift_and_lower, metadata, - ConvertError, FfiConverter, Lift, LiftRef, LiftReturn, Lower, LowerError, LowerReturn, - MetadataBuffer, Result, RustBuffer, TypeId, UnexpectedUniFFICallbackError, + ConvertError, FfiConverter, Lift, LiftArgsError, LiftRef, LiftReturn, Lower, LowerError, + LowerReturn, MetadataBuffer, Result, RustBuffer, RustCallError, TypeId, + UnexpectedUniFFICallbackError, }; use anyhow::bail; use bytes::buf::{Buf, BufMut}; @@ -453,7 +454,7 @@ derive_ffi_traits!(impl TypeId for Arc where Arc: FfiConverter< unsafe impl LowerReturn for () { type ReturnType = (); - fn lower_return(_: ()) -> Result { + fn lower_return(_: ()) -> Result { Ok(()) } } @@ -480,17 +481,20 @@ where { type ReturnType = R::ReturnType; - fn lower_return(v: Self) -> Result { + fn lower_return(v: Self) -> Result { match v { Ok(r) => R::lower_return(r), - Err(e) => Err(E::lower_error(e)), + Err(e) => Err(RustCallError::Error(E::lower_error(e))), } } - fn handle_failed_lift(arg_name: &str, err: anyhow::Error) -> Self { - match err.downcast::() { - Ok(actual_error) => Err(actual_error), - Err(ohno) => panic!("Failed to convert arg '{arg_name}': {ohno}"), + fn handle_failed_lift(error: LiftArgsError) -> Result { + match error.error.downcast::() { + Ok(downcast) => Err(RustCallError::Error(E::lower_error(downcast))), + Err(e) => { + let msg = format!("Failed to convert arg '{}': {e}", error.arg_name); + Err(RustCallError::InternalError(msg)) + } } } } diff --git a/uniffi_core/src/ffi_converter_traits.rs b/uniffi_core/src/ffi_converter_traits.rs index 1b4fdb7333..8362bfdb26 100644 --- a/uniffi_core/src/ffi_converter_traits.rs +++ b/uniffi_core/src/ffi_converter_traits.rs @@ -50,14 +50,14 @@ //! 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. -use std::{borrow::Borrow, sync::Arc}; +use std::{borrow::Borrow, mem::ManuallyDrop, sync::Arc}; use anyhow::bail; use bytes::Buf; use crate::{ - FfiDefault, Handle, MetadataBuffer, Result, RustBuffer, RustCallStatus, RustCallStatusCode, - UnexpectedUniFFICallbackError, + FfiDefault, Handle, LiftArgsError, MetadataBuffer, Result, RustBuffer, RustCallError, + RustCallStatus, RustCallStatusCode, UnexpectedUniFFICallbackError, }; /// Generalized FFI conversions @@ -269,22 +269,29 @@ pub unsafe trait LowerReturn: Sized { /// When derived, it's the same as `FfiType`. type ReturnType: FfiDefault; - /// Lower this value for scaffolding function return + /// Lower the return value from an scaffolding call + /// + /// Returns values that [rust_call] expects: /// - /// This method converts values into the `Result<>` type that [rust_call] expects. For - /// successful calls, return `Ok(lower_return)`. For errors that should be translated into - /// thrown exceptions on the foreign code, serialize the error into a RustBuffer and return - /// `Err(buf)` - fn lower_return(obj: Self) -> Result; + /// - Ok(v) for `Ok` returns and non-result returns, where v is the lowered return value + /// - `Err(RustCallError::Error(buf))` for `Err` returns where `buf` is serialized error value. + fn lower_return(v: Self) -> Result; - /// If possible, get a serialized error for failed argument lifts + /// Lower the return value for failed argument lifts + /// + /// This is called when we fail to make a scaffolding call, because of an error lifting an + /// argument. It should return a value that [rust_call] expects: /// - /// By default, we just panic and let `rust_call` handle things. However, for `Result<_, E>` - /// returns, if the anyhow error can be downcast to `E`, then serialize that and return it. - /// This results in the foreign code throwing a "normal" exception, rather than an unexpected - /// exception. - fn handle_failed_lift(arg_name: &str, e: anyhow::Error) -> Self { - panic!("Failed to convert arg '{arg_name}': {e}") + /// - By default, this is `Err(RustCallError::InternalError(msg))` where `msg` is message + /// describing the failed lift. + /// - For Result types, if we can downcast the error to the `Err` value, then return + /// `Err(RustCallError::Error(buf))`. This results in better exception throws on the foreign + /// side. + fn handle_failed_lift(error: LiftArgsError) -> Result { + let LiftArgsError { arg_name, error } = error; + Err(RustCallError::InternalError(format!( + "Failed to convert arg '{arg_name}': {error}" + ))) } } @@ -339,12 +346,12 @@ pub unsafe trait LiftReturn: Sized { Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e)) }), RustCallStatusCode::Error => { - Self::lift_error(unsafe { call_status.error_buf.assume_init() }) + Self::lift_error(ManuallyDrop::into_inner(call_status.error_buf)) } _ => { - let e = >::try_lift(unsafe { - call_status.error_buf.assume_init() - }) + let e = >::try_lift( + ManuallyDrop::into_inner(call_status.error_buf), + ) .unwrap_or_else(|e| format!("(Error lifting message: {e}")); Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e)) } @@ -449,18 +456,24 @@ pub unsafe trait HandleAlloc: Send + Sync { /// 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; + /// # Safety + /// The handle must be valid. + unsafe 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 + /// # Safety + /// The handle must be valid. 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 { + unsafe 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; + /// # Safety + /// The handle must be valid. + unsafe fn consume_handle(handle: Handle) -> Arc; } /// Derive FFI traits @@ -540,8 +553,8 @@ macro_rules! derive_ffi_traits { { type ReturnType = >::FfiType; - fn lower_return(obj: Self) -> $crate::deps::anyhow::Result { - Ok(>::lower(obj)) + fn lower_return(v: Self) -> $crate::deps::anyhow::Result { + ::std::result::Result::Ok(>::lower(v)) } } }; @@ -596,19 +609,15 @@ macro_rules! derive_ffi_traits { $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>()); - } + unsafe fn clone_handle(handle: $crate::Handle) -> $crate::Handle { + ::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 fn consume_handle(handle: $crate::Handle) -> ::std::sync::Arc { + ::std::sync::Arc::::clone( + &std::sync::Arc::<::std::sync::Arc::>::from_raw(handle.as_pointer::<::std::sync::Arc>()) + ) } } }; @@ -626,12 +635,12 @@ unsafe impl HandleAlloc for T { Handle::from_pointer(Arc::into_raw(value)) } - fn clone_handle(handle: Handle) -> Handle { - unsafe { Arc::increment_strong_count(handle.as_pointer::()) }; + unsafe fn clone_handle(handle: Handle) -> Handle { + Arc::increment_strong_count(handle.as_pointer::()); handle } - fn consume_handle(handle: Handle) -> Arc { - unsafe { Arc::from_raw(handle.as_pointer()) } + unsafe fn consume_handle(handle: Handle) -> Arc { + Arc::from_raw(handle.as_pointer()) } } diff --git a/uniffi_core/src/lib.rs b/uniffi_core/src/lib.rs index 1135753371..2d55006ba7 100644 --- a/uniffi_core/src/lib.rs +++ b/uniffi_core/src/lib.rs @@ -176,7 +176,7 @@ macro_rules! ffi_converter_rust_buffer_lift_and_lower { let mut buf = vec.as_slice(); let value = >::try_read(&mut buf)?; match $crate::deps::bytes::Buf::remaining(&buf) { - 0 => Ok(value), + 0 => ::std::result::Result::Ok(value), n => $crate::deps::anyhow::bail!( "junk data left in buffer after lifting (count: {n})", ), diff --git a/uniffi_macros/Cargo.toml b/uniffi_macros/Cargo.toml index 518b7c441a..6825c4de51 100644 --- a/uniffi_macros/Cargo.toml +++ b/uniffi_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_macros" -version = "0.28.0" +version = "0.28.1" authors = ["Firefox Sync Team "] description = "a multi-language bindings generator for rust (convenience macros)" documentation = "https://mozilla.github.io/uniffi-rs" @@ -24,8 +24,8 @@ quote = "1.0" serde = { version = "1.0.136", features = ["derive"] } syn = { version = "2.0", features = ["full", "visit-mut"] } toml = "0.5.9" -uniffi_build = { path = "../uniffi_build", version = "=0.28.0", optional = true } -uniffi_meta = { path = "../uniffi_meta", version = "=0.28.0" } +uniffi_build = { path = "../uniffi_build", version = "=0.28.1", optional = true } +uniffi_meta = { path = "../uniffi_meta", version = "=0.28.1" } [features] default = [] diff --git a/uniffi_macros/src/custom.rs b/uniffi_macros/src/custom.rs index 5a52fc4964..991a74f305 100644 --- a/uniffi_macros/src/custom.rs +++ b/uniffi_macros/src/custom.rs @@ -41,7 +41,7 @@ pub(crate) fn expand_ffi_converter_custom_type( #lower(#from_custom(obj)) } - fn try_lift(v: Self::FfiType) -> uniffi::Result<#ident> { + fn try_lift(v: Self::FfiType) -> ::uniffi::Result<#ident> { #into_custom(#try_lift(v)?) } @@ -49,7 +49,7 @@ pub(crate) fn expand_ffi_converter_custom_type( #write(#from_custom(obj), buf); } - fn try_read(buf: &mut &[u8]) -> uniffi::Result<#ident> { + fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<#ident> { #into_custom(#try_read(buf)?) } @@ -85,8 +85,8 @@ fn custom_ffi_type_converter(ident: &Ident, builtin: &Path) -> syn::Result uniffi::Result { - Ok(#ident(val)) + fn into_custom(val: Self::Builtin) -> ::uniffi::Result { + ::std::result::Result::Ok(#ident(val)) } fn from_custom(obj: Self) -> Self::Builtin { diff --git a/uniffi_macros/src/enum_.rs b/uniffi_macros/src/enum_.rs index d17bca053d..b17b5ba639 100644 --- a/uniffi_macros/src/enum_.rs +++ b/uniffi_macros/src/enum_.rs @@ -211,7 +211,7 @@ fn enum_or_error_ffi_converter_impl( .collect(); if item.is_non_exhaustive() { write_match_arms.push(quote! { - _ => panic!("Unexpected variant in non-exhaustive enum"), + _ => ::std::panic!("Unexpected variant in non-exhaustive enum"), }) } let write_impl = quote! { @@ -238,7 +238,7 @@ fn enum_or_error_ffi_converter_impl( let try_read_impl = quote! { ::uniffi::check_remaining(buf, 4)?; - Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) { + ::std::result::Result::Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) { #(#try_read_match_arms)* v => ::uniffi::deps::anyhow::bail!(#error_format_string, v), }) diff --git a/uniffi_macros/src/error.rs b/uniffi_macros/src/error.rs index 03191ae404..39e3deca93 100644 --- a/uniffi_macros/src/error.rs +++ b/uniffi_macros/src/error.rs @@ -88,7 +88,7 @@ fn flat_error_ffi_converter_impl(item: &EnumItem, options: &DeriveOptions) -> To .collect(); if item.is_non_exhaustive() { match_arms.push(quote! { - _ => panic!("Unexpected variant in non-exhaustive enum"), + _ => ::std::panic!("Unexpected variant in non-exhaustive enum"), }) } @@ -127,7 +127,7 @@ fn flat_error_ffi_converter_impl(item: &EnumItem, options: &DeriveOptions) -> To type FfiType = ::uniffi::RustBuffer; fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result { - Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) { + ::std::result::Result::Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) { #(#match_arms)* v => ::uniffi::deps::anyhow::bail!("Invalid #ident enum value: {}", v), }) @@ -152,11 +152,11 @@ fn flat_error_ffi_converter_impl(item: &EnumItem, options: &DeriveOptions) -> To type FfiType = ::uniffi::RustBuffer; fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result { - panic!("Can't lift flat errors") + ::std::panic!("Can't lift flat errors") } fn try_lift(v: ::uniffi::RustBuffer) -> ::uniffi::deps::anyhow::Result { - panic!("Can't lift flat errors") + ::std::panic!("Can't lift flat errors") } } } diff --git a/uniffi_macros/src/export/callback_interface.rs b/uniffi_macros/src/export/callback_interface.rs index e9621913b7..23009712e1 100644 --- a/uniffi_macros/src/export/callback_interface.rs +++ b/uniffi_macros/src/export/callback_interface.rs @@ -150,11 +150,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))) + ::std::result::Result::Ok(::std::boxed::Box::new(<#trait_impl_ident>::new(v))) } fn try_read(buf: &mut &[u8]) -> ::uniffi::deps::anyhow::Result { - use uniffi::deps::bytes::Buf; + use ::uniffi::deps::bytes::Buf; ::uniffi::check_remaining(buf, 8)?; #try_lift_self(buf.get_u64()) } @@ -222,7 +222,7 @@ fn gen_method_impl(sig: &FnSignature, vtable_cell: &Ident) -> syn::Result #return_ty { let vtable = #vtable_cell.get(); - let mut uniffi_call_status = ::uniffi::RustCallStatus::new(); + let mut uniffi_call_status: ::uniffi::RustCallStatus = ::std::default::Default::default(); let mut uniffi_return_value: #lift_return_type = ::uniffi::FfiDefault::ffi_default(); (vtable.#ident)(self.handle, #(#lower_exprs,)* &mut uniffi_return_value, &mut uniffi_call_status); #lift_foreign_return(uniffi_return_value, uniffi_call_status) diff --git a/uniffi_macros/src/export/scaffolding.rs b/uniffi_macros/src/export/scaffolding.rs index 1dfbc43a2b..bc45648723 100644 --- a/uniffi_macros/src/export/scaffolding.rs +++ b/uniffi_macros/src/export/scaffolding.rs @@ -144,9 +144,13 @@ impl ScaffoldingBits { // pointer. quote! { { - let boxed_foreign_arc = unsafe { Box::from_raw(uniffi_self_lowered as *mut ::std::sync::Arc) }; + let boxed_foreign_arc = unsafe { + ::std::boxed::Box::from_raw( + uniffi_self_lowered as *mut ::std::sync::Arc, + ) + }; // Take a clone for our own use. - Ok(*boxed_foreign_arc) + ::std::result::Result::Ok(*boxed_foreign_arc) } } } else { @@ -155,8 +159,10 @@ impl ScaffoldingBits { let lift_closure = sig.lift_closure(Some(quote! { match #try_lift_self { - Ok(v) => v, - Err(e) => return Err(("self", e)) + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(e) => { + return ::std::result::Result::Err(("self", e)); + } } })); let call_params = sig.rust_call_params(true); @@ -259,15 +265,15 @@ pub(super) fn gen_ffi_function( ::uniffi::deps::log::debug!(#name); let uniffi_lift_args = #lift_closure; ::uniffi::rust_call(call_status, || { - #lower_return(match uniffi_lift_args() { - Ok(uniffi_args) => { + match uniffi_lift_args() { + ::std::result::Result::Ok(uniffi_args) => { let uniffi_result = #rust_fn_call; - #convert_result + #lower_return(#convert_result) } - Err((arg_name, anyhow_error)) => { - #handle_failed_lift(arg_name, anyhow_error) + ::std::result::Result::Err((arg_name, error)) => { + #handle_failed_lift(::uniffi::LiftArgsError { arg_name, error} ) }, - }) + } }) } @@ -286,24 +292,21 @@ pub(super) fn gen_ffi_function( #[no_mangle] pub extern "C" fn #ffi_ident(#(#param_names: #param_types,)*) -> ::uniffi::Handle { ::uniffi::deps::log::debug!(#name); - let uniffi_lift_args = #lift_closure; - match uniffi_lift_args() { - Ok(uniffi_args) => { - ::uniffi::rust_future_new::<_, #return_ty, _>( - async move { + let uniffi_lifted_args = (#lift_closure)(); + ::uniffi::rust_future_new::<_, #return_ty, _>( + async move { + match uniffi_lifted_args { + ::std::result::Result::Ok(uniffi_args) => { let uniffi_result = #future_expr.await; - #convert_result + Ok(#convert_result) + } + ::std::result::Result::Err((arg_name, error)) => { + Err(::uniffi::LiftArgsError { arg_name, error }) }, - crate::UniFfiTag - ) - }, - Err((arg_name, anyhow_error)) => { - ::uniffi::rust_future_new::<_, #return_ty, _>( - async move { #handle_failed_lift(arg_name, anyhow_error) }, - crate::UniFfiTag, - ) + } }, - } + crate::UniFfiTag + ) } #scaffolding_fn_ffi_buffer_version @@ -332,7 +335,7 @@ fn ffi_buffer_scaffolding_fn( ) { let mut arg_buf = unsafe { ::std::slice::from_raw_parts(arg_ptr, ::uniffi::ffi_buffer_size!(#(#type_list),*)) }; let mut return_buf = unsafe { ::std::slice::from_raw_parts_mut(return_ptr, ::uniffi::ffi_buffer_size!(#return_type, ::uniffi::RustCallStatus)) }; - let mut out_status = ::uniffi::RustCallStatus::default(); + let mut out_status: ::uniffi::RustCallStatus = ::std::default::Default::default(); let return_value = #fn_ident( #( diff --git a/uniffi_macros/src/export/trait_interface.rs b/uniffi_macros/src/export/trait_interface.rs index 51eb94d031..18d01a5d38 100644 --- a/uniffi_macros/src/export/trait_interface.rs +++ b/uniffi_macros/src/export/trait_interface.rs @@ -54,10 +54,14 @@ pub(super) fn gen_trait_scaffolding( ptr: *const ::std::ffi::c_void, call_status: &mut ::uniffi::RustCallStatus ) -> *const ::std::ffi::c_void { - uniffi::rust_call(call_status, || { - let ptr = ptr as *mut std::sync::Arc; - let arc = unsafe { ::std::sync::Arc::clone(&*ptr) }; - Ok(::std::boxed::Box::into_raw(::std::boxed::Box::new(arc)) as *const ::std::ffi::c_void) + ::uniffi::rust_call(call_status, || { + let ptr = ptr as *mut ::std::sync::Arc; + let arc: ::std::sync::Arc<_> = unsafe { ::std::clone::Clone::clone(&*ptr) }; + ::std::result::Result::Ok( + ::std::boxed::Box::into_raw( + ::std::boxed::Box::new(arc), + ) as *const ::std::ffi::c_void + ) }) } @@ -74,10 +78,14 @@ pub(super) fn gen_trait_scaffolding( ptr: *const ::std::ffi::c_void, 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) }); - Ok(()) + ::uniffi::rust_call(call_status, || { + ::std::assert!(!ptr.is_null()); + ::std::mem::drop(unsafe { + ::std::boxed::Box::from_raw( + ptr as *mut ::std::sync::Arc, + ) + }); + ::std::result::Result::Ok(()) }); } }; @@ -123,14 +131,16 @@ pub(crate) fn ffi_converter( let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name); quote! { 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))) + ::std::result::Result::Ok(::std::sync::Arc::new(<#trait_impl_ident>::new(v as u64))) } } } else { quote! { fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc> { unsafe { - Ok(*::std::boxed::Box::from_raw(v as *mut ::std::sync::Arc)) + ::std::result::Result::Ok( + *::std::boxed::Box::from_raw(v as *mut ::std::sync::Arc), + ) } } } @@ -148,7 +158,9 @@ pub(crate) fn ffi_converter( // if they are not, but unfortunately it fails with an unactionably obscure error message. // By asserting the requirement explicitly, we help Rust produce a more scrutable error message // 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); + ::uniffi::deps::static_assertions::assert_impl_all!( + dyn #trait_ident: ::core::marker::Sync, ::core::marker::Send + ); unsafe #impl_spec { type FfiType = *const ::std::os::raw::c_void; @@ -159,11 +171,11 @@ pub(crate) fn ffi_converter( #try_lift - fn write(obj: ::std::sync::Arc, buf: &mut Vec) { + fn write(obj: ::std::sync::Arc, buf: &mut ::std::vec::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_self(obj) as u64, + #lower_self(obj) as ::std::primitive::u64, ); } diff --git a/uniffi_macros/src/export/utrait.rs b/uniffi_macros/src/export/utrait.rs index 7663348931..6e133cedbd 100644 --- a/uniffi_macros/src/export/utrait.rs +++ b/uniffi_macros/src/export/utrait.rs @@ -103,14 +103,14 @@ pub(crate) fn expand_uniffi_trait_export( let method_eq = quote! { fn uniffi_trait_eq_eq(&self, other: &#self_ident) -> bool { use ::std::cmp::PartialEq; - uniffi::deps::static_assertions::assert_impl_all!(#self_ident: PartialEq); // This object has a trait method which requires `PartialEq` be implemented. + ::uniffi::deps::static_assertions::assert_impl_all!(#self_ident: PartialEq); // This object has a trait method which requires `PartialEq` be implemented. PartialEq::eq(self, other) } }; let method_ne = quote! { fn uniffi_trait_eq_ne(&self, other: &#self_ident) -> bool { use ::std::cmp::PartialEq; - uniffi::deps::static_assertions::assert_impl_all!(#self_ident: PartialEq); // This object has a trait method which requires `PartialEq` be implemented. + ::uniffi::deps::static_assertions::assert_impl_all!(#self_ident: PartialEq); // This object has a trait method which requires `PartialEq` be implemented. PartialEq::ne(self, other) } }; diff --git a/uniffi_macros/src/fnsig.rs b/uniffi_macros/src/fnsig.rs index d66aefeae2..19774dc3ba 100644 --- a/uniffi_macros/src/fnsig.rs +++ b/uniffi_macros/src/fnsig.rs @@ -159,14 +159,16 @@ impl FnSignature { let name = &arg.name; quote! { match #try_lift(#ident) { - Ok(v) => v, - Err(e) => return Err((#name, e)), + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(e) => { + return ::std::result::Result::Err((#name, e)) + } } } }); let all_lifts = self_lift.into_iter().chain(arg_lifts); quote! { - move || Ok(( + move || ::std::result::Result::Ok(( #(#all_lifts,)* )) } diff --git a/uniffi_macros/src/object.rs b/uniffi_macros/src/object.rs index 280a673d4d..0907bbfd3b 100644 --- a/uniffi_macros/src/object.rs +++ b/uniffi_macros/src/object.rs @@ -67,9 +67,9 @@ pub fn expand_object(input: DeriveInput, options: DeriveOptions) -> syn::Result< ptr: *const ::std::ffi::c_void, call_status: &mut ::uniffi::RustCallStatus ) -> *const ::std::ffi::c_void { - uniffi::rust_call(call_status, || { + ::uniffi::rust_call(call_status, || { unsafe { ::std::sync::Arc::increment_strong_count(ptr) }; - Ok(ptr) + ::std::result::Result::Ok(ptr) }) } @@ -79,13 +79,13 @@ pub fn expand_object(input: DeriveInput, options: DeriveOptions) -> syn::Result< ptr: *const ::std::ffi::c_void, call_status: &mut ::uniffi::RustCallStatus ) { - uniffi::rust_call(call_status, || { + ::uniffi::rust_call(call_status, || { assert!(!ptr.is_null()); let ptr = ptr.cast::<#ident>(); unsafe { ::std::sync::Arc::decrement_strong_count(ptr); } - Ok(()) + ::std::result::Result::Ok(()) }); } @@ -119,7 +119,9 @@ fn interface_impl(object: &ObjectItem, options: &DeriveOptions) -> TokenStream { // if they are not, but unfortunately it fails with an unactionably obscure error message. // By asserting the requirement explicitly, we help Rust produce a more scrutable error message // and thus help the user debug why the requirement isn't being met. - uniffi::deps::static_assertions::assert_impl_all!(#ident: ::core::marker::Sync, ::core::marker::Send); + ::uniffi::deps::static_assertions::assert_impl_all!( + #ident: ::core::marker::Sync, ::core::marker::Send + ); #[doc(hidden)] #[automatically_derived] @@ -148,7 +150,7 @@ fn interface_impl(object: &ObjectItem, options: &DeriveOptions) -> TokenStream { /// When lifting, we receive an owned `Arc` that the foreign language code cloned. fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc> { let v = v as *const #ident; - Ok(unsafe { ::std::sync::Arc::::from_raw(v) }) + ::std::result::Result::Ok(unsafe { ::std::sync::Arc::::from_raw(v) }) } /// When writing as a field of a complex structure, make a clone and transfer ownership @@ -159,9 +161,9 @@ fn interface_impl(object: &ObjectItem, options: &DeriveOptions) -> TokenStream { /// 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) { + fn write(obj: ::std::sync::Arc, buf: &mut ::std::vec::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_arc(obj) as u64); + ::uniffi::deps::bytes::BufMut::put_u64(buf, #lower_arc(obj) as ::std::primitive::u64); } /// When reading as a field of a complex structure, we receive a "borrow" of the `Arc` @@ -183,7 +185,7 @@ fn interface_impl(object: &ObjectItem, options: &DeriveOptions) -> TokenStream { unsafe #lower_return_impl_spec { type ReturnType = #lower_return_type_arc; - fn lower_return(obj: Self) -> ::std::result::Result { + fn lower_return(obj: Self) -> ::std::result::Result { #lower_return_arc(::std::sync::Arc::new(obj)) } } diff --git a/uniffi_macros/src/record.rs b/uniffi_macros/src/record.rs index ec4b46303b..74629d1915 100644 --- a/uniffi_macros/src/record.rs +++ b/uniffi_macros/src/record.rs @@ -94,7 +94,7 @@ fn record_ffi_converter_impl( } fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result { - Ok(Self { #try_read_fields }) + ::std::result::Result::Ok(Self { #try_read_fields }) } const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_RECORD) diff --git a/uniffi_macros/src/setup_scaffolding.rs b/uniffi_macros/src/setup_scaffolding.rs index c82e9389bb..83bb5ce83b 100644 --- a/uniffi_macros/src/setup_scaffolding.rs +++ b/uniffi_macros/src/setup_scaffolding.rs @@ -36,7 +36,7 @@ pub fn setup_scaffolding(namespace: String) -> Result { #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub extern "C" fn #ffi_contract_version_ident() -> u32 { + pub extern "C" fn #ffi_contract_version_ident() -> ::std::primitive::u32 { #UNIFFI_CONTRACT_VERSION } @@ -45,13 +45,15 @@ pub fn setup_scaffolding(namespace: String) -> Result { /// /// See `uniffi_bindgen::macro_metadata` for how this is used. - const #namespace_const_ident: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::NAMESPACE) - .concat_str(#module_path) - .concat_str(#namespace); + const #namespace_const_ident: ::uniffi::MetadataBuffer = + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::NAMESPACE) + .concat_str(#module_path) + .concat_str(#namespace); #[doc(hidden)] #[no_mangle] - pub static #namespace_static_ident: [u8; #namespace_const_ident.size] = #namespace_const_ident.into_array(); + pub static #namespace_static_ident: [::std::primitive::u8; #namespace_const_ident.size] = + #namespace_const_ident.into_array(); // Everybody gets basic buffer support, since it's needed for passing complex types over the FFI. // @@ -60,29 +62,42 @@ pub fn setup_scaffolding(namespace: String) -> Result { #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub extern "C" fn #ffi_rustbuffer_alloc_ident(size: u64, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { - uniffi::ffi::uniffi_rustbuffer_alloc(size, call_status) + pub extern "C" fn #ffi_rustbuffer_alloc_ident( + size: ::std::primitive::u64, + call_status: &mut ::uniffi::RustCallStatus, + ) -> ::uniffi::RustBuffer { + ::uniffi::ffi::uniffi_rustbuffer_alloc(size, call_status) } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rustbuffer_from_bytes_ident(bytes: uniffi::ForeignBytes, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { - uniffi::ffi::uniffi_rustbuffer_from_bytes(bytes, call_status) + pub unsafe extern "C" fn #ffi_rustbuffer_from_bytes_ident( + bytes: ::uniffi::ForeignBytes, + call_status: &mut ::uniffi::RustCallStatus, + ) -> ::uniffi::RustBuffer { + ::uniffi::ffi::uniffi_rustbuffer_from_bytes(bytes, call_status) } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rustbuffer_free_ident(buf: uniffi::RustBuffer, call_status: &mut uniffi::RustCallStatus) { - uniffi::ffi::uniffi_rustbuffer_free(buf, call_status); + pub unsafe extern "C" fn #ffi_rustbuffer_free_ident( + buf: ::uniffi::RustBuffer, + call_status: &mut ::uniffi::RustCallStatus, + ) { + ::uniffi::ffi::uniffi_rustbuffer_free(buf, call_status); } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rustbuffer_reserve_ident(buf: uniffi::RustBuffer, additional: u64, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { - uniffi::ffi::uniffi_rustbuffer_reserve(buf, additional, call_status) + pub unsafe extern "C" fn #ffi_rustbuffer_reserve_ident( + buf: ::uniffi::RustBuffer, + additional: ::std::primitive::u64, + call_status: &mut ::uniffi::RustCallStatus, + ) -> ::uniffi::RustBuffer { + ::uniffi::ffi::uniffi_rustbuffer_reserve(buf, additional, call_status) } #ffi_rust_future_scaffolding_fns @@ -121,7 +136,9 @@ 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: ::std::marker::Sized; fn from_custom(obj: Self) -> Self::Builtin; } }) diff --git a/uniffi_macros/src/test.rs b/uniffi_macros/src/test.rs index da7a343dbc..e6ae142ce1 100644 --- a/uniffi_macros/src/test.rs +++ b/uniffi_macros/src/test.rs @@ -30,16 +30,16 @@ pub(crate) fn build_foreign_language_testcases(tokens: TokenStream) -> TokenStre ); let run_test = match test_file_pathbuf.extension() { Some("kts") => quote! { - uniffi::kotlin_run_test + ::uniffi::kotlin_test::run_test }, Some("swift") => quote! { - uniffi::swift_run_test + ::uniffi::swift_test::run_test }, Some("py") => quote! { - uniffi::python_run_test + ::uniffi::python_test::run_test }, Some("rb") => quote! { - uniffi::ruby_run_test + ::uniffi::ruby_test::run_test }, _ => panic!("Unexpected extension for test script: {test_file_name}"), }; @@ -51,7 +51,7 @@ pub(crate) fn build_foreign_language_testcases(tokens: TokenStream) -> TokenStre quote! { #maybe_ignore #[test] - fn #test_name () -> uniffi::deps::anyhow::Result<()> { + fn #test_name () -> ::uniffi::deps::anyhow::Result<()> { #run_test( std::env!("CARGO_TARGET_TMPDIR"), std::env!("CARGO_PKG_NAME"), diff --git a/uniffi_meta/Cargo.toml b/uniffi_meta/Cargo.toml index ed87fcb10c..92b58470f3 100644 --- a/uniffi_meta/Cargo.toml +++ b/uniffi_meta/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_meta" -version = "0.28.0" +version = "0.28.1" edition = "2021" description = "uniffi_meta" homepage = "https://mozilla.github.io/uniffi-rs" @@ -13,4 +13,4 @@ readme = "../README.md" anyhow = "1" bytes = "1.3" siphasher = "0.3" -uniffi_checksum_derive = { version = "0.28.0", path = "../uniffi_checksum_derive" } +uniffi_checksum_derive = { version = "0.28.1", path = "../uniffi_checksum_derive" } diff --git a/uniffi_meta/src/types.rs b/uniffi_meta/src/types.rs index 51bf156b50..7fe55d81ac 100644 --- a/uniffi_meta/src/types.rs +++ b/uniffi_meta/src/types.rs @@ -143,10 +143,59 @@ impl Type { }; Box::new(std::iter::once(self).chain(nested_types)) } + + pub fn name(&self) -> Option { + match self { + Type::Object { name, .. } => Some(name.to_string()), + Type::Record { name, .. } => Some(name.to_string()), + Type::Enum { name, .. } => Some(name.to_string()), + Type::External { name, .. } => Some(name.to_string()), + Type::Custom { name, .. } => Some(name.to_string()), + Type::Optional { inner_type } | Type::Sequence { inner_type } => inner_type.name(), + _ => None, + } + } + + fn rename(&mut self, new_name: String) { + match self { + Type::Object { name, .. } => *name = new_name, + Type::Record { name, .. } => *name = new_name, + Type::Enum { name, .. } => *name = new_name, + Type::External { name, .. } => *name = new_name, + Type::Custom { name, .. } => *name = new_name, + _ => {} + } + } + + pub fn rename_recursive(&mut self, name_transformer: &impl Fn(&str) -> String) { + // Rename the current type if it has a name + if let Some(name) = self.name() { + self.rename(name_transformer(&name)); + } + + // Recursively rename nested types + match self { + Type::Optional { inner_type } | Type::Sequence { inner_type } => { + inner_type.rename_recursive(name_transformer); + } + Type::Map { + key_type, + value_type, + .. + } => { + key_type.rename_recursive(name_transformer); + value_type.rename_recursive(name_transformer); + } + Type::Custom { builtin, .. } => { + builtin.rename_recursive(name_transformer); + } + _ => {} + } + } } // A trait so various things can turn into a type. -pub trait AsType: core::fmt::Debug { +pub trait AsType: ::core::fmt::Debug { fn as_type(&self) -> Type; } diff --git a/uniffi_testing/Cargo.toml b/uniffi_testing/Cargo.toml index ce4e6ba144..992c919370 100644 --- a/uniffi_testing/Cargo.toml +++ b/uniffi_testing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_testing" -version = "0.28.0" +version = "0.28.1" 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_testing/src/lib.rs b/uniffi_testing/src/lib.rs index f8a0d8499a..444a7f8146 100644 --- a/uniffi_testing/src/lib.rs +++ b/uniffi_testing/src/lib.rs @@ -37,17 +37,15 @@ static CARGO_BUILD_MESSAGES: Lazy> = Lazy::new(get_cargo_build_mess /// - The fixture crate produces a cdylib library /// - The fixture crate, and any external-crates, has 1 UDL file in it's src/ directory pub struct UniFFITestHelper { - name: String, - package: Package, + crate_name: String, + cdylib: Utf8PathBuf, } impl UniFFITestHelper { - pub fn new(name: &str) -> Result { - let package = Self::find_package(name)?; - Ok(Self { - name: name.to_string(), - package, - }) + pub fn new(package_name: &str) -> Result { + let package = Self::find_package(package_name)?; + let (crate_name, cdylib) = Self::find_name_and_cdylib_path(&package)?; + Ok(Self { crate_name, cdylib }) } fn find_package(name: &str) -> Result { @@ -62,7 +60,7 @@ impl UniFFITestHelper { } } - fn find_cdylib_path(package: &Package) -> Result { + fn find_name_and_cdylib_path(package: &Package) -> Result<(String, Utf8PathBuf)> { let cdylib_targets: Vec<&Target> = package .targets .iter() @@ -72,6 +70,7 @@ impl UniFFITestHelper { 1 => cdylib_targets[0], n => bail!("Found {n} cdylib targets for {}", package.name), }; + let target_name = target.name.replace('-', "_"); let artifacts = CARGO_BUILD_MESSAGES .iter() @@ -97,7 +96,7 @@ impl UniFFITestHelper { .collect(); match cdylib_files.len() { - 1 => Ok(cdylib_files[0].to_owned()), + 1 => Ok((target_name, cdylib_files[0].to_owned())), n => bail!("Found {n} cdylib files for {}", package.name), } } @@ -119,7 +118,7 @@ impl UniFFITestHelper { temp_dir: impl AsRef, script_path: impl AsRef, ) -> Result { - let dirname = format!("{}-{}", self.name, hash_path(script_path.as_ref())); + let dirname = format!("{}-{}", self.crate_name, hash_path(script_path.as_ref())); let out_dir = temp_dir.as_ref().join(dirname); if out_dir.exists() { // Clean out any files from previous runs @@ -144,7 +143,15 @@ impl UniFFITestHelper { /// Get the path to the cdylib file for this package pub fn cdylib_path(&self) -> Result { - Self::find_cdylib_path(&self.package) + Ok(self.cdylib.clone()) + } + + pub fn crate_name(&self) -> &str { + &self.crate_name + } + + pub fn cargo_metadata(&self) -> Metadata { + CARGO_METADATA.clone() } } @@ -156,7 +163,8 @@ fn get_cargo_metadata() -> Metadata { fn get_cargo_build_messages() -> Vec { let mut child = Command::new(env!("CARGO")) - .arg("build") + .arg("test") + .arg("--no-run") .arg("--message-format=json") .stdout(Stdio::piped()) .spawn() diff --git a/uniffi_udl/Cargo.toml b/uniffi_udl/Cargo.toml index 6aab799090..beaba92245 100644 --- a/uniffi_udl/Cargo.toml +++ b/uniffi_udl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniffi_udl" -version = "0.28.0" +version = "0.28.1" description = "udl parsing for the uniffi project" documentation = "https://mozilla.github.io/uniffi-rs" homepage = "https://mozilla.github.io/uniffi-rs" @@ -16,5 +16,5 @@ weedle2 = { version = "5.0.0", path = "../weedle2" } # Don't include the `unicode-linebreak` or `unicode-width` since that functionality isn't needed for # docstrings. textwrap = { version = "0.16", features=["smawk"], default-features = false } -uniffi_meta = { path = "../uniffi_meta", version = "=0.28.0" } -uniffi_testing = { path = "../uniffi_testing", version = "=0.28.0" } +uniffi_meta = { path = "../uniffi_meta", version = "=0.28.1" } +uniffi_testing = { path = "../uniffi_testing", version = "=0.28.1" } diff --git a/uniffi_udl/src/finder.rs b/uniffi_udl/src/finder.rs index 259557ad07..6a2b6084b8 100644 --- a/uniffi_udl/src/finder.rs +++ b/uniffi_udl/src/finder.rs @@ -129,43 +129,89 @@ impl TypeFinder for weedle::TypedefDefinition<'_> { }, ) } else { + let typedef_type = match &self.type_.type_ { + weedle::types::Type::Single(weedle::types::SingleType::NonAny( + weedle::types::NonAnyType::Identifier(weedle::types::MayBeNull { + type_: i, + .. + }), + )) => i.0, + _ => bail!("Failed to get typedef type: {:?}", self), + }; + 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::CallbackTrait) => Type::Object { - module_path, - name, - imp: ObjectImpl::CallbackTrait, - }, - 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 + + let ty = match attrs.external_tagged() { 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, + // Not external, not custom, not Rust - so we basically + // pretend it is Rust, thus soft-deprecating it. + // We use `type_` + match typedef_type { + "dictionary" | "record" | "struct" => Type::Record { + module_path, + name, + }, + "enum" => Type::Enum { + module_path, + name, + }, + "custom" => panic!("don't know builtin"), + "interface" | "impl" => Type::Object { + module_path, + name, + imp: ObjectImpl::Struct, + }, + "trait" => Type::Object { + module_path, + name, + imp: ObjectImpl::Trait, + }, + "callback" | "trait_with_foreign" => Type::Object { + module_path, + name, + imp: ObjectImpl::CallbackTrait, + }, + _ => bail!("Can't work out the type - no attributes and unknown extern type '{typedef_type}'"), + } + } + Some(tagged) => { + // Must be either `[Rust..]` or `[Extern..]` + 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::CallbackTrait) => Type::Object { + module_path, + name, + imp: ObjectImpl::CallbackTrait, + }, + 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"); + 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(self.identifier.0, ty) } } @@ -190,7 +236,7 @@ impl TypeFinder for weedle::CallbackInterfaceDefinition<'_> { #[cfg(test)] mod test { use super::*; - use uniffi_meta::ExternalKind; + use uniffi_meta::{ExternalKind, ObjectImpl}; // A helper to take valid UDL and a closure to check what's in it. fn test_a_finding(udl: &str, tester: F) @@ -289,6 +335,74 @@ mod test { ); } + #[test] + fn test_extern_local_types() { + // should test more, but these are already deprecated + test_a_finding( + r#" + typedef interface Interface; + typedef impl Interface2; + typedef trait Trait; + typedef callback Callback; + + typedef dictionary R1; + typedef record R2; + typedef record R3; + typedef enum Enum; + "#, + |types| { + assert!(matches!( + types.get_type_definition("Interface").unwrap(), + Type::Object { name, module_path, imp: ObjectImpl::Struct } if name == "Interface" && module_path.is_empty())); + assert!(matches!( + types.get_type_definition("Interface2").unwrap(), + Type::Object { name, module_path, imp: ObjectImpl::Struct } if name == "Interface2" && module_path.is_empty())); + assert!(matches!( + types.get_type_definition("Trait").unwrap(), + Type::Object { name, module_path, imp: ObjectImpl::Trait } if name == "Trait" && module_path.is_empty())); + assert!(matches!( + types.get_type_definition("Callback").unwrap(), + Type::Object { name, module_path, imp: ObjectImpl::CallbackTrait } if name == "Callback" && module_path.is_empty())); + assert!(matches!( + types.get_type_definition("R1").unwrap(), + Type::Record { name, module_path } if name == "R1" && module_path.is_empty())); + assert!(matches!( + types.get_type_definition("R2").unwrap(), + Type::Record { name, module_path } if name == "R2" && module_path.is_empty())); + assert!(matches!( + types.get_type_definition("R3").unwrap(), + Type::Record { name, module_path } if name == "R3" && module_path.is_empty())); + assert!(matches!( + types.get_type_definition("Enum").unwrap(), + Type::Enum { name, module_path } if name == "Enum" && module_path.is_empty())); + }, + ); + } + + #[test] + fn test_rust_attr_types() { + // should test more, but these are already deprecated + test_a_finding( + r#" + [Rust="interface"] + typedef extern LocalInterface; + + [Rust="dictionary"] + typedef extern Dict; + "#, + |types| { + assert!( + matches!(types.get_type_definition("LocalInterface").unwrap(), Type::Object { name, module_path, imp: ObjectImpl::Struct } + if name == "LocalInterface" && module_path.is_empty()) + ); + assert!( + matches!(types.get_type_definition("Dict").unwrap(), Type::Record { name, module_path } + if name == "Dict" && module_path.is_empty()) + ); + }, + ); + } + fn get_err(udl: &str) -> String { let parsed = weedle::parse(udl).unwrap(); let mut types = TypeCollector::default(); @@ -299,9 +413,8 @@ mod test { } #[test] - #[should_panic] - fn test_typedef_error_on_no_attr() { - // Sorry, still working out what we want for non-imported typedefs.. - get_err("typedef string Custom;"); + fn test_local_type_unknown_typedef() { + let e = get_err("typedef xyz Foo;"); + assert!(e.contains("unknown extern type 'xyz'")); } }