diff --git a/Cargo.lock b/Cargo.lock index 44581c8f..8a3570be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -793,6 +793,17 @@ dependencies = [ "uniffi-dart", ] +[[package]] +name = "dart_namespaces" +version = "0.1.0" +dependencies = [ + "anyhow", + "uniffi", + "uniffi-dart", + "uniffi-fixture-namespaces-media", + "uniffi-fixture-namespaces-net", +] + [[package]] name = "displaydoc" version = "0.2.7" @@ -2421,6 +2432,8 @@ dependencies = [ "uniffi-fixture-callbacks", "uniffi-fixture-coverall", "uniffi-fixture-docstring", + "uniffi-fixture-namespaces-media", + "uniffi-fixture-namespaces-net", "uniffi-fixture-time", "uniffi-fixture-trait-methods", "uniffi-reserved-field-name", @@ -2472,6 +2485,8 @@ dependencies = [ "uniffi-fixture-ext-types-custom-types", "uniffi-fixture-ext-types-lib-one", "uniffi-fixture-ext-types-proc-macro", + "uniffi-fixture-namespaces-media", + "uniffi-fixture-namespaces-net", "uniffi-fixture-proc-macro", "uniffi-fixture-simple-fns", "uniffi-fixture-simple-iface", @@ -2945,6 +2960,23 @@ dependencies = [ "uniffi", ] +[[package]] +name = "uniffi-fixture-namespaces-media" +version = "0.1.0" +dependencies = [ + "thiserror 2.0.21", + "uniffi", + "uniffi-fixture-namespaces-net", +] + +[[package]] +name = "uniffi-fixture-namespaces-net" +version = "0.1.0" +dependencies = [ + "thiserror 2.0.21", + "uniffi", +] + [[package]] name = "uniffi-fixture-proc-macro" version = "0.22.0" diff --git a/Cargo.toml b/Cargo.toml index f727a051..69285006 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ members = [ "uniffi/dart/fixtures/library_config", "uniffi/dart/fixtures/map_type", "uniffi/dart/fixtures/metadata", + "uniffi/dart/fixtures/namespaces", "uniffi/dart/fixtures/proc-macro", "uniffi/dart/fixtures/proc-macro-no-implicit-prelude", "uniffi/dart/fixtures/simple-fns", @@ -86,6 +87,8 @@ members = [ "uniffi/fixtures/metadata", "uniffi/fixtures/multi-arch-extract", "uniffi/fixtures/mutable-records", + "uniffi/fixtures/namespaces/media", + "uniffi/fixtures/namespaces/net", "uniffi/fixtures/proc-macro", "uniffi/fixtures/proc-macro-no-implicit-prelude", "uniffi/fixtures/regressions/cdylib-crate-type-dependency/cdylib-dependency", diff --git a/quest/m2/README.md b/quest/m2/README.md index fb6b5868..2b54bab9 100644 --- a/quest/m2/README.md +++ b/quest/m2/README.md @@ -22,7 +22,6 @@ deletes the quests that follow it. - [Shared ownership](/quest/m2/ts-shared.md) - `Arc`/`Rc`/`Weak` keep exact counts, and locks and atomics become plain values - [Collections](/quest/m2/ts-collections.md) - maps, sets, and queues keep Rust's key semantics - [Translate moq-net's model to TypeScript](/quest/m2/ts-model.md) - the driver grows from moq-net's codec to its model, for moq to compare against WASM -- [Namespaced external types](/quest/m2/uniffi-namespaces.md) - two crates linked into one library can each export a same-named type in every generator - [Zero-copy byte returns](/quest/m2/ffi-bytes-return.md) - returned payloads reach the host without a copy, if the payload numbers justify it - [Cheaper async](/quest/m2/ffi-async.md) - `RustFuture` exports cost fewer crossings, if the numbers show they matter - [Namespaced FFI contract](/quest/m2/uniffi-contract.md) - bindings and a cdylib from different UniFFI forks refuse to load together diff --git a/quest/m2/uniffi-contract.md b/quest/m2/uniffi-contract.md index 45c292b4..983f2b11 100644 --- a/quest/m2/uniffi-contract.md +++ b/quest/m2/uniffi-contract.md @@ -26,7 +26,3 @@ anyway. Public API: the generated contract check, and the scaffolding's exported contract symbol. - -## Related - -- [Namespaced external types](/quest/m2/uniffi-namespaces.md) - may change the contract first diff --git a/quest/m2/uniffi-namespaces.md b/quest/m2/uniffi-namespaces.md deleted file mode 100644 index 6c957b81..00000000 --- a/quest/m2/uniffi-namespaces.md +++ /dev/null @@ -1,37 +0,0 @@ -# [L] Namespaced external types in the UniFFI fork - -## Goal - -Two crates linked into one library can each export a type with the same -name, and a type in one can take the other's: `media.GroupConsumer(group: -GroupConsumer)` generates and runs in Kotlin, Swift, Python, Go, Dart, C++, -and C from [the fork](/uniffi/README.md). - -## Plan - -Decided with the maintainer on 2026-10-13 in moq, and moved here on -2026-10-23 since the fix lands entirely in the fork: fix -[mozilla/uniffi-rs#3026](https://github.com/mozilla/uniffi-rs/issues/3026) -in the fork instead of waiting for upstream. That issue shows closed only -because [moq-dev/moq#5498](https://github.com/moq-dev/moq/pull/5498) -auto-closed it; it remains unfixed upstream. - -- `TypeUniverse` keys types by `(module_path, name)` instead of bare name, - and each generator qualifies cross-crate references through the other - crate's module. moq's probes - ([moq-dev/moq#5460](https://github.com/moq-dev/moq/pull/5460) and - [moq-dev/moq#5473](https://github.com/moq-dev/moq/pull/5473)) found Python - and Go already reference external types that way. Kotlin and Dart shadow - them, Swift needs per-crate modules plus an import, C++ sorts by bare name, - and generated C refuses external types. -- Reuse the two-crate spike harness from moq-dev/moq#5460 as the regression: - both layers export `GroupConsumer` and `Frame`, and every generator compiles - and runs the consumer. -- Re-running moq's per-layer crate split decision (rejected in - moq-dev/moq#5460) is a moq follow-up once this lands. - -Public API: the fork's generated code for cross-crate types. - -## Related - -- [Namespaced FFI contract](/quest/m2/uniffi-contract.md) - lands first if this changes the FFI contract diff --git a/uniffi/MIGRATE.md b/uniffi/MIGRATE.md new file mode 100644 index 00000000..94e96f59 --- /dev/null +++ b/uniffi/MIGRATE.md @@ -0,0 +1,10 @@ +# Migrating + +## Unreleased + +- `uniffi_bindgen`: a name no longer identifies a type across crates. + `ComponentInterface::is_name_used_as_error(name)` is now `is_used_as_error(&ty)`, + `ComponentInterface::get_type(name)` finds only the crate's own types, and + `VisitMut::visit_error_name` takes the module path of the crate that defines the error. +- Generated C: every type and function name starts with the crate's namespace, so a crate + whose items did not already start with it gets renamed C symbols. diff --git a/uniffi/README.md b/uniffi/README.md index 173f39a4..fb5b4aea 100644 --- a/uniffi/README.md +++ b/uniffi/README.md @@ -73,6 +73,14 @@ uniffi-rs: consumer. - Test expectations that hold a path relative to the workspace root (`file!()` output and trybuild snapshots) gain the `uniffi/` prefix. +- A crate can use another crate's type of the same name + ([mozilla/uniffi-rs#3026](https://github.com/mozilla/uniffi-rs/issues/3026)): the type + universe, the errors, and the recursion check key types by crate and name, and an external + type is walked through its own crate's interface. Kotlin spells external types with their + package, Swift qualifies an ambiguous one with its module (so same-named types need one + Swift module per crate), and Python keeps both definitions (mozilla/uniffi-rs#2985, from + upstream `main`). Container helpers for an external type carry its namespace. The + `namespaces` fixture covers it in every generator. - Scaffolding is written to a temporary file and renamed into place. Two trybuild cases expanding `generate_and_include_scaffolding!` on one UDL write it concurrently, and the in-place truncate let one include a partial file. @@ -82,6 +90,8 @@ uniffi-bindgen-go, on top of upstream v0.7.1+v0.31.0: - The UniFFI 0.32 port ([NordSecurity/uniffi-bindgen-go#98](https://github.com/NordSecurity/uniffi-bindgen-go/pull/98)). - Generated async calls take a `context.Context` and cancel the Rust future. - An exported `Display` on an enum error renders as its `Error()`. +- Container helpers for another crate's type carry its namespace, so they don't collide with + a same-named local type's. - `Types.go` renders its object, enum, error, record, callback interface, and custom type templates in their own functions, with byte-identical output. Inlined into one, the borrow checker took about 16 GB to check it, more than a CI runner has. @@ -94,6 +104,9 @@ uniffi-dart, on top of upstream `main` at acterglobal/uniffi-dart@72d26c1: disposed objects refused as arguments. - Record errors keep their converter name, and futures cancel per operation (moq's `nix/uniffi-dart-record-error.patch` and `nix/uniffi-dart-cancellation.patch`). +- Another crate's library is imported with a `uniffi_` prefix that its types, + converters, and container helpers go through, the shared runtime comes from the same crate + every run, and the test harness drives library mode. uniffi-bindgen-cpp, on top of upstream `main` at NordSecurity/uniffi-bindgen-cpp@20bef75: @@ -101,3 +114,7 @@ uniffi-bindgen-cpp, on top of upstream `main` at NordSecurity/uniffi-bindgen-cpp - `error_style = "expected"`, `strip_type_prefix`, exported `Display` and `Debug` on enums, newline-terminated files, released async results, and the C backend (`--lang c`). - The CMake test suite finds the fixtures in the root workspace's target directory. +- Another crate's types are qualified from the global namespace, with aliased converters and + namespaced container helpers, definitions sort per crate, and `strip_type_prefix` renames + per crate. The C backend supports external types, and prefixes every C name with its + namespace. diff --git a/uniffi/cpp/README.md b/uniffi/cpp/README.md index 6efcbe8d..322724c2 100644 --- a/uniffi/cpp/README.md +++ b/uniffi/cpp/README.md @@ -60,8 +60,10 @@ and RTTI may be off) and include only `.h` from C. uniffi-bindgen-cpp --lang c --library your_rust_library.so --out-dir output_directory ``` -Names are the uniffi names in snake case: `MyObject.do_thing` is `my_object_do_thing`, and its -handle is `my_object *`, released with `my_object_free`. The rest follows from a few rules: +Names are the uniffi names in snake case, prefixed with the namespace unless they already start +with it, since every crate's C API shares one scope: in namespace `app`, `MyObject.do_thing` is +`app_my_object_do_thing`, and its handle is `app_my_object *`, released with +`app_my_object_free`; `AppObject` is `app_object`. The rest follows from a few rules: * A call that can fail returns an error struct, or NULL on success, and writes its result to a trailing `out` pointer. An error is a tagged union with a `message`; free it with `_free`. @@ -79,9 +81,14 @@ handle is `my_object *`, released with `my_object_free`. The rest follows from a or will never run. * Callbacks run on the default dispatcher thread unless `_set_dispatcher` installs a host's own, which receives `_work *` items to run with `_work_run`. - -The C backend refuses what it cannot render faithfully, such as callback interfaces, custom and -external types, timestamps, durations, and exported traits other than `Display` on errors. +* A type from another crate keeps that crate's C name and `_free`, and its header is included. + Generate the C API of every crate involved (one `--crate` each, or all at once) and compile + each one's `.cpp` and `_c.cpp`; a crate's implementation converts its + own types for the others. + +The C backend refuses what it cannot render faithfully, such as callback interfaces, custom +types, timestamps, durations, and exported traits other than `Display` on errors, including in +another crate whose types it uses. # Configuration options diff --git a/uniffi/cpp/bindgen/src/bindings/c/gen_c.rs b/uniffi/cpp/bindgen/src/bindings/c/gen_c.rs index 63a700ea..356221f5 100644 --- a/uniffi/cpp/bindgen/src/bindings/c/gen_c.rs +++ b/uniffi/cpp/bindgen/src/bindings/c/gen_c.rs @@ -34,6 +34,8 @@ pub(crate) struct Bindings { struct Header<'a> { ns: &'a str, guard: String, + /// The namespaces whose headers declare the external types this one uses. + includes: Vec, body: &'a str, } @@ -43,6 +45,7 @@ struct Source<'a> { ns: &'a str, namespace: &'a str, objects: &'a str, + externals: &'a str, converters: &'a str, body: &'a str, } @@ -63,7 +66,7 @@ pub(crate) fn generate(ci: &ComponentInterface, config: &Config) -> Result Result Result, } impl CType { @@ -238,11 +247,22 @@ fn decl(ty: &str, name: &str) -> String { } } -/// The C name of a uniffi type or function: its name in snake case. +/// A uniffi name in snake case. fn c_name(name: &str) -> String { name.to_snake_case() } +/// The C name of a type or function: its name in snake case, prefixed with the snake-case +/// namespace unless it already is, since every crate's C API shares one global scope. +fn prefixed(ns: &str, name: &str) -> String { + let c = c_name(name); + if c.starts_with(&format!("{ns}_")) { + c + } else { + format!("{ns}_{c}") + } +} + fn c_field_name(field: &Field, index: usize) -> String { if field.name().is_empty() { format!("v{index}") @@ -272,6 +292,8 @@ struct Gen<'a> { types: BTreeMap, /// Every global C identifier, to refuse a collision instead of emitting one. names: BTreeSet, + /// The crates whose types this one uses, each checked once for C support. + externals: BTreeSet, } impl<'a> Gen<'a> { @@ -282,9 +304,21 @@ impl<'a> Gen<'a> { ns: ci.namespace().to_snake_case(), types: BTreeMap::new(), names: BTreeSet::new(), + externals: BTreeSet::new(), } } + /// The namespaces of the crates whose types this one uses. + fn external_namespaces(&self) -> Vec { + self.types + .values() + .filter_map(|ct| ct.external.as_ref()) + .map(|(namespace, _)| namespace.clone()) + .collect::>() + .into_iter() + .collect() + } + fn claim(&mut self, name: &str) -> Result<()> { if !self.names.insert(name.to_string()) { bail!("the C backend would emit `{name}` twice; rename one of the uniffi items"); @@ -300,9 +334,6 @@ impl<'a> Gen<'a> { cbi.name() ); } - if let Some(ty) = self.ci.iter_external_types().next() { - bail!("the C backend does not support external types, but `{ty:?}` is one"); - } for obj in self.ci.object_definitions() { if obj.has_callback_interface() { bail!( @@ -316,7 +347,7 @@ impl<'a> Gen<'a> { obj.name() ); } - if self.ci.is_name_used_as_error(obj.name()) { + if self.ci.is_used_as_error(obj) { bail!( "the C backend does not support objects as errors, but `{}` is one", obj.name() @@ -358,7 +389,7 @@ impl<'a> Gen<'a> { } let traits = e.uniffi_trait_methods(); // An error's Display output is its message; nothing else is rendered. - let display_ok = self.ci.is_name_used_as_error(e.name()); + let display_ok = self.ci.is_used_as_error(e); if (traits.display_fmt.is_some() && !display_ok) || traits.debug_fmt.is_some() || traits.eq_eq.is_some() @@ -377,14 +408,17 @@ impl<'a> Gen<'a> { /// Maps a uniffi type, registering it and everything it contains for converters. fn ctype(&mut self, ty: &Type) -> Result { let code = CppCodeOracle.find(ty); - let canon = code.canonical_name(); + let canon = code.canonical_name(self.ci); if let Some(ct) = self.types.get(&canon) { return Ok(ct.clone()); } let cpp = code.type_label(self.ci); + if self.ci.is_external(ty) { + return self.external_ctype(ty, canon, cpp); + } let ns = self.ns.clone(); let named = |name: &str| { - let c = c_name(name); + let c = prefixed(&ns, name); let slug = c.strip_prefix(&format!("{ns}_")).unwrap_or(&c).to_string(); (c, slug) }; @@ -413,7 +447,7 @@ impl<'a> Gen<'a> { .ci .get_enum_definition(name) .with_context(|| format!("enum `{name}` not found"))?; - let kind = if filters::is_enum_struct(self.ci, name) { + let kind = if filters::is_enum_struct(self.ci, ty) { Kind::DataEnum } else { Kind::FlatEnum @@ -477,6 +511,36 @@ impl<'a> Gen<'a> { cpp, canon: canon.clone(), slug, + external: None, + }; + self.types.insert(canon, ct.clone()); + Ok(ct) + } + + /// Maps a type from another crate to the C type that crate's API declares, after checking + /// that its C API can be generated at all. + fn external_ctype(&mut self, ty: &Type, canon: String, cpp: String) -> Result { + let ext = filters::defining_interface(self.ci, ty); + let mut own = Gen::new(ext, self.config); + if self.externals.insert(ext.crate_name().to_string()) { + own.check_supported().with_context(|| { + format!( + "`{}` uses types from `{}`", + self.ci.namespace(), + ext.namespace() + ) + })?; + } + let own = own.ctype(ty)?; + let ct = CType { + ty: ty.clone(), + kind: own.kind, + c: own.c.clone(), + cpp, + canon: canon.clone(), + // The whole C name, so a compound of it never matches one of a local namesake. + slug: own.c, + external: Some((ext.namespace().to_string(), own.canon)), }; self.types.insert(canon, ct.clone()); Ok(ct) @@ -505,6 +569,12 @@ impl<'a> Gen<'a> { let types: Vec = self.types.values().cloned().collect(); + // Another crate's header defines its types, and its implementation converts them. + let types: Vec = types + .into_iter() + .filter(|ct| ct.external.is_none()) + .collect(); + // Flat enums first: C cannot forward-declare an enum. for ct in types.iter().filter(|ct| matches!(ct.kind, Kind::FlatEnum)) { self.render_flat_enum(h, ct)?; @@ -599,7 +669,7 @@ impl<'a> Gen<'a> { } for func in self.ci.function_definitions() { - let name = c_name(func.name()); + let name = prefixed(&self.ns, func.name()); let call = format!( "::{}::{}", self.ci.namespace(), @@ -609,7 +679,8 @@ impl<'a> Gen<'a> { } // The header declared the types known up front; one found later would lack its struct. - if self.types.len() != types.len() { + let local = self.types.values().filter(|ct| ct.external.is_none()); + if local.count() != types.len() { bail!("the C backend found a type the interface does not list; this is a bug"); } Ok(()) @@ -693,7 +764,7 @@ impl<'a> Gen<'a> { .collect(), }; for field in fields { - let dep = CppCodeOracle.find(&field.as_type()).canonical_name(); + let dep = CppCodeOracle.find(&field.as_type()).canonical_name(self.ci); if structs.contains_key(&dep) { self.visit(&dep, structs, done, visiting, sorted)?; } @@ -812,7 +883,7 @@ impl<'a> Gen<'a> { fn render_data_enum(&mut self, h: &mut String, s: &mut String, ct: &CType) -> Result<()> { let e = self.enum_def(&ct.ty)?; - let error = self.ci.is_name_used_as_error(e.name()); + let error = self.ci.is_used_as_error(e); let tag = format!("{}_tag", ct.c); self.claim(&tag)?; @@ -924,7 +995,7 @@ impl<'a> Gen<'a> { } fn render_object(&mut self, h: &mut String, s: &mut String, obj: &Object) -> Result<()> { - let c = c_name(obj.name()); + let c = prefixed(&self.ns, obj.name()); let class = CppCodeOracle.class_name(obj.name()); let namespace = self.ci.namespace(); @@ -1197,8 +1268,41 @@ impl<'a> Gen<'a> { Ok(()) } - /// Declares every converter, then defines them, so their order does not matter. - fn render_converters(&self, out: &mut String) -> Result<()> { + /// Declares every converter, then defines them, so their order does not matter. An external + /// type's converters forward to its own crate's, which `externals` declares. + fn render_converters(&self, out: &mut String, externals: &mut String) -> Result<()> { + let mut by_namespace: BTreeMap<&str, String> = BTreeMap::new(); + for ct in self.types.values() { + if let Some((namespace, canon)) = &ct.external { + let decls = by_namespace.entry(namespace).or_default(); + let field = ct.field(); + writeln!( + decls, + "{};", + decl(&field, &format!("to_c_{canon}(const {} &value)", ct.cpp)) + )?; + writeln!( + decls, + "{} from_c_{canon}({});", + ct.cpp, + decl(&ct.view(), "value") + )?; + writeln!( + decls, + "void free_c_{canon}({});", + decl(&format!("{field} &"), "value") + )?; + } + } + for (namespace, decls) in by_namespace { + writeln!( + externals, + "namespace {namespace}::c_detail {{ +{decls}}} // namespace {namespace}::c_detail +" + )?; + } + for ct in self.types.values() { let field = ct.field(); writeln!( @@ -1247,6 +1351,16 @@ impl<'a> Gen<'a> { ct.canon, decl(&format!("{field} &"), "value") ); + if let Some((namespace, canon)) = &ct.external { + let own = format!("::{namespace}::c_detail"); + writeln!(out, "{to} {{\n return {own}::to_c_{canon}(value);\n}}\n")?; + writeln!( + out, + "{from} {{\n return {own}::from_c_{canon}(value);\n}}\n" + )?; + writeln!(out, "{free} {{\n {own}::free_c_{canon}(value);\n}}\n")?; + return Ok(()); + } match &ct.kind { Kind::Scalar => { writeln!(out, "{to} {{\n return value;\n}}\n")?; @@ -1311,7 +1425,7 @@ impl<'a> Gen<'a> { let mut from_items = Vec::new(); let mut free_body = String::new(); for field in rec.fields() { - let fcanon = CppCodeOracle.find(&field.as_type()).canonical_name(); + let fcanon = CppCodeOracle.find(&field.as_type()).canonical_name(self.ci); let name = filters::var_name(field.name()).unwrap(); writeln!(to_body, " out.{name} = to_c_{fcanon}(value.{name});")?; from_items.push(format!(" from_c_{fcanon}(value.{name})")); @@ -1349,6 +1463,18 @@ impl<'a> Gen<'a> { Kind::Optional(inner) => { let icanon = &inner.canon; match inner.kind { + // Another crate's object struct is private to its implementation. + Kind::Object if inner.external.is_some() => { + writeln!( + out, + "{to} {{\n return value ? to_c_{icanon}(value) : nullptr;\n}}\n" + )?; + writeln!( + out, + "{from} {{\n return value ? from_c_{icanon}(value) : nullptr;\n}}\n" + )?; + writeln!(out, "{free} {{\n free_c_{icanon}(value);\n}}\n")?; + } // A null shared_ptr is the C++ binding's empty optional object. Kind::Object => { writeln!( @@ -1448,7 +1574,7 @@ impl<'a> Gen<'a> { free: &str, ) -> Result<()> { let e = self.enum_def(&ct.ty)?; - let error = self.ci.is_name_used_as_error(e.name()); + let error = self.ci.is_used_as_error(e); let display = e.uniffi_trait_methods().display_fmt.is_some(); // Under the expected style each variant of a flat error carries its message. let flat_error = error && e.is_flat(); @@ -1479,7 +1605,7 @@ impl<'a> Gen<'a> { let mut from_items = Vec::new(); let mut free_case = String::new(); for (i, f) in variant.fields().iter().enumerate() { - let fcanon = CppCodeOracle.find(&f.as_type()).canonical_name(); + let fcanon = CppCodeOracle.find(&f.as_type()).canonical_name(self.ci); let name = c_field_name(f, i + 1); writeln!( to_cases, diff --git a/uniffi/cpp/bindgen/src/bindings/c/mod.rs b/uniffi/cpp/bindgen/src/bindings/c/mod.rs index 4fb5acd6..325955d8 100644 --- a/uniffi/cpp/bindgen/src/bindings/c/mod.rs +++ b/uniffi/cpp/bindgen/src/bindings/c/mod.rs @@ -3,7 +3,8 @@ //! The C++ backend already lifts and lowers every type, drives Rust futures, and owns the //! async dispatcher, so the C backend renders it in the expected error style and wraps it: //! `.h` is the C11 header, and `_c.cpp` converts between its structs -//! and the C++ types. Consumers compile `.cpp` and `_c.cpp` as C++17. +//! and the C++ types. Consumers compile `.cpp` and `_c.cpp` as C++17, for +//! this crate and every crate whose types it uses: each crate's `_c.cpp` converts its own types. mod gen_c; diff --git a/uniffi/cpp/bindgen/src/bindings/c/templates/c_header.h b/uniffi/cpp/bindgen/src/bindings/c/templates/c_header.h index a6af6524..e0bb7e1f 100644 --- a/uniffi/cpp/bindgen/src/bindings/c/templates/c_header.h +++ b/uniffi/cpp/bindgen/src/bindings/c/templates/c_header.h @@ -5,6 +5,9 @@ #include #include #include +{%- for namespace in includes %} +#include "{{ namespace }}.h" +{%- endfor %} #ifdef __cplusplus extern "C" { diff --git a/uniffi/cpp/bindgen/src/bindings/c/templates/c_source.cpp b/uniffi/cpp/bindgen/src/bindings/c/templates/c_source.cpp index 2c33ea94..1023f684 100644 --- a/uniffi/cpp/bindgen/src/bindings/c/templates/c_source.cpp +++ b/uniffi/cpp/bindgen/src/bindings/c/templates/c_source.cpp @@ -97,6 +97,10 @@ struct {{ ns }}_work { }; {{ objects }} +{%- if !externals.is_empty() %} + +{{ externals }} +{%- endif %} namespace {{ namespace }}::c_detail { diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/callback_interface.rs b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/callback_interface.rs index fdbcc33b..cf059e70 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/callback_interface.rs +++ b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/callback_interface.rs @@ -1,7 +1,10 @@ use uniffi_bindgen::{interface::Literal, ComponentInterface}; use crate::bindings::cpp::{ - gen_cpp::filters::{callback_interface_name, external_namespace_prefix, CppCodeOracle}, + gen_cpp::filters::{ + callback_interface_name, external_canonical_prefix, external_namespace_prefix, + CppCodeOracle, + }, CodeType, }; @@ -22,12 +25,16 @@ impl CodeType for CallbackInterfaceCodeType { format!( "std::shared_ptr<{}{}>", external_namespace_prefix(ci, &self.module_path), - self.canonical_name() + CppCodeOracle.class_name(&self.id) ) } - fn canonical_name(&self) -> String { - CppCodeOracle.class_name(&self.id) + fn canonical_name(&self, ci: &ComponentInterface) -> String { + format!( + "{}{}", + external_canonical_prefix(ci, &self.module_path), + CppCodeOracle.class_name(&self.id) + ) } fn literal(&self, _literal: &Literal, _ci: &ComponentInterface) -> String { diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/compounds.rs b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/compounds.rs index 21b52d82..62a37328 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/compounds.rs +++ b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/compounds.rs @@ -19,7 +19,7 @@ impl OptionalCodeType { pub(crate) fn can_dereference(inner_type: &Type, ci: &ComponentInterface) -> bool { match inner_type { Type::Object { .. } | Type::CallbackInterface { .. } => true, - Type::Enum { name, .. } => is_error_class(ci, name), + Type::Enum { .. } => is_error_class(ci, inner_type), _ => false, } } @@ -37,10 +37,10 @@ impl CodeType for OptionalCodeType { } } - fn canonical_name(&self) -> String { + fn canonical_name(&self, ci: &ComponentInterface) -> String { format!( "Optional{}", - CppCodeOracle.find(&self.inner).canonical_name(), + CppCodeOracle.find(&self.inner).canonical_name(ci), ) } @@ -77,10 +77,10 @@ impl CodeType for SequenceCodeType { ) } - fn canonical_name(&self) -> String { + fn canonical_name(&self, ci: &ComponentInterface) -> String { format!( "Sequence{}", - CppCodeOracle.find(&self.inner).canonical_name(), + CppCodeOracle.find(&self.inner).canonical_name(ci), ) } @@ -121,11 +121,11 @@ impl CodeType for MapCodeType { ) } - fn canonical_name(&self) -> String { + fn canonical_name(&self, ci: &ComponentInterface) -> String { format!( "Map{}{}", - CppCodeOracle.find(self.key()).canonical_name(), - CppCodeOracle.find(self.value()).canonical_name(), + CppCodeOracle.find(self.key()).canonical_name(ci), + CppCodeOracle.find(self.value()).canonical_name(ci), ) } diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/custom.rs b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/custom.rs index 85d8b1f6..221e7b5c 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/custom.rs +++ b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/custom.rs @@ -1,6 +1,9 @@ use uniffi_bindgen::ComponentInterface; -use crate::bindings::cpp::{gen_cpp::filters::external_namespace_prefix, CodeType}; +use crate::bindings::cpp::{ + gen_cpp::filters::{external_canonical_prefix, external_namespace_prefix}, + CodeType, +}; #[derive(Debug)] pub struct CustomCodeType { @@ -23,7 +26,11 @@ impl CodeType for CustomCodeType { ) } - fn canonical_name(&self) -> String { - format!("Type{}", self.name) + fn canonical_name(&self, ci: &ComponentInterface) -> String { + format!( + "{}Type{}", + external_canonical_prefix(ci, &self.module_path), + self.name + ) } } diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/enum_.rs b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/enum_.rs index 98d24fc6..a3945034 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/enum_.rs +++ b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/enum_.rs @@ -1,7 +1,12 @@ -use uniffi_bindgen::{interface::Literal, ComponentInterface}; +use uniffi_bindgen::{ + interface::{Literal, Type}, + ComponentInterface, +}; use crate::bindings::cpp::{ - gen_cpp::filters::{external_namespace_prefix, is_error_class, CppCodeOracle}, + gen_cpp::filters::{ + external_canonical_prefix, external_namespace_prefix, is_error_class, CppCodeOracle, + }, CodeType, }; #[derive(Debug)] @@ -14,15 +19,22 @@ impl EnumCodeType { pub(crate) fn new(id: String, module_path: String) -> Self { Self { id, module_path } } + + fn as_type(&self) -> Type { + Type::Enum { + name: self.id.clone(), + module_path: self.module_path.clone(), + } + } } impl CodeType for EnumCodeType { fn type_label(&self, ci: &ComponentInterface) -> String { - if is_error_class(ci, &self.id) { + if is_error_class(ci, &self.as_type()) { format!( "std::shared_ptr<{}{}>", external_namespace_prefix(ci, &self.module_path), - self.canonical_name() + CppCodeOracle.class_name(&self.id) ) } else { format!( @@ -33,8 +45,12 @@ impl CodeType for EnumCodeType { } } - fn canonical_name(&self) -> String { - CppCodeOracle.class_name(&self.id) + fn canonical_name(&self, ci: &ComponentInterface) -> String { + format!( + "{}{}", + external_canonical_prefix(ci, &self.module_path), + CppCodeOracle.class_name(&self.id) + ) } fn literal(&self, _: &Literal, _ci: &ComponentInterface) -> String { diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/filters/mod.rs b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/filters/mod.rs index 8abb724c..9e5a6650 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/filters/mod.rs +++ b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/filters/mod.rs @@ -1,6 +1,6 @@ use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use uniffi_bindgen::{ - interface::{Argument, AsType, DefaultValue, FfiType, Literal, Object, Type, Variant}, + interface::{Argument, AsType, DefaultValue, Enum, FfiType, Literal, Object, Type, Variant}, ComponentInterface, }; @@ -233,20 +233,58 @@ pub(crate) fn type_name(as_ct: &impl AsCodeType, ci: &ComponentInterface) -> Res Ok(as_ct.as_codetype().type_label(ci)) } +/// The class an error is thrown as, qualified when another crate defines it. +pub(crate) fn thrown_class(as_type: &impl AsType, ci: &ComponentInterface) -> Result { + let ty = renamed(as_type.as_type()); + match (ty.name(), ty.module_path()) { + (Some(name), Some(module_path)) => Ok(format!( + "{}{}", + external_namespace_prefix(ci, module_path), + CppCodeOracle.class_name(name) + )), + _ => Err(askama::Error::Custom( + format!("{ty:?} cannot be thrown").into(), + )), + } +} + +/// How a type from another crate is qualified. From the global namespace, since a member or +/// variant of a local type may share the other crate's namespace name. pub(crate) fn external_namespace_prefix(ci: &ComponentInterface, module_path: &str) -> String { + match external_namespace(ci, module_path) { + Some(namespace) => format!("::{namespace}::"), + None => String::new(), + } +} + +/// What an external type's canonical name starts with: its namespace in UpperCamelCase. +pub(crate) fn external_canonical_prefix(ci: &ComponentInterface, module_path: &str) -> String { + external_namespace(ci, module_path) + .map(|namespace| namespace.to_upper_camel_case()) + .unwrap_or_default() +} + +/// The namespace of a type from another crate, or `None` for a local one. +fn external_namespace<'a>(ci: &'a ComponentInterface, module_path: &str) -> Option<&'a str> { match ci.namespace_for_module_path(module_path) { - Ok(namespace) if namespace != ci.namespace() => format!("{namespace}::"), - Ok(_) => String::new(), + Ok(namespace) if namespace != ci.namespace() => Some(namespace), + Ok(_) => None, Err(e) => std::panic!("could not resolve external namespace: {e}"), } } -pub(crate) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result { - Ok(as_ct.as_codetype().ffi_converter_name()) +pub(crate) fn ffi_converter_name( + as_ct: &impl AsCodeType, + ci: &ComponentInterface, +) -> Result { + Ok(as_ct.as_codetype().ffi_converter_name(ci)) } -pub(crate) fn ffi_error_converter_name(as_type: &impl AsType) -> Result { - let mut name = ffi_converter_name(as_type)?; +pub(crate) fn ffi_error_converter_name( + as_type: &impl AsType, + ci: &ComponentInterface, +) -> Result { + let mut name = ffi_converter_name(as_type, ci)?; if matches!(&as_type.as_type(), Type::Object { .. }) { name.push_str("__as_error"); @@ -263,8 +301,8 @@ pub(crate) fn callback_interface_name(nm: &str) -> Result { Ok(format!("UniffiCallbackInterface{}", nm)) } -pub(crate) fn canonical_name(as_ct: &impl AsCodeType) -> Result { - Ok(as_ct.as_codetype().canonical_name()) +pub(crate) fn canonical_name(as_ct: &impl AsCodeType, ci: &ComponentInterface) -> Result { + Ok(as_ct.as_codetype().canonical_name(ci)) } pub(crate) fn fn_name(nm: &str) -> Result { @@ -303,38 +341,41 @@ pub(crate) fn literal_cpp( } } -pub(crate) fn lift_fn(as_ct: &impl AsCodeType) -> Result { +pub(crate) fn lift_fn(as_ct: &impl AsCodeType, ci: &ComponentInterface) -> Result { Ok(format!( "{}::lift", - as_ct.as_codetype().ffi_converter_name() + as_ct.as_codetype().ffi_converter_name(ci) )) } -pub(crate) fn lower_fn(as_ct: &impl AsCodeType) -> Result { +pub(crate) fn lower_fn(as_ct: &impl AsCodeType, ci: &ComponentInterface) -> Result { Ok(format!( "{}::lower", - as_ct.as_codetype().ffi_converter_name() + as_ct.as_codetype().ffi_converter_name(ci) )) } -pub(crate) fn read_fn(as_ct: &impl AsCodeType) -> Result { +pub(crate) fn read_fn(as_ct: &impl AsCodeType, ci: &ComponentInterface) -> Result { Ok(format!( "{}::read", - as_ct.as_codetype().ffi_converter_name() + as_ct.as_codetype().ffi_converter_name(ci) )) } -pub(crate) fn write_fn(as_ct: &impl AsCodeType) -> Result { +pub(crate) fn write_fn(as_ct: &impl AsCodeType, ci: &ComponentInterface) -> Result { Ok(format!( "{}::write", - as_ct.as_codetype().ffi_converter_name() + as_ct.as_codetype().ffi_converter_name(ci) )) } -pub(crate) fn allocation_size_fn(as_ct: &impl AsCodeType) -> Result { +pub(crate) fn allocation_size_fn( + as_ct: &impl AsCodeType, + ci: &ComponentInterface, +) -> Result { Ok(format!( "{}::allocation_size", - as_ct.as_codetype().ffi_converter_name() + as_ct.as_codetype().ffi_converter_name(ci) )) } @@ -392,13 +433,7 @@ pub(crate) fn by_ref(ci: &ComponentInterface, arg: &Argument) -> bool { | Type::Float64 | Type::Boolean | Type::Optional { .. } => false, - Type::Enum { - module_path: _, - name, - } => match ci.get_enum_definition(&name) { - Some(_) => !is_enum_struct(ci, &name), - None => false, - }, + ty @ Type::Enum { .. } => !is_enum_struct(ci, &ty), _ => true, } } @@ -429,30 +464,49 @@ pub(crate) fn can_dereference_optional(type_: &Type, ci: &ComponentInterface) -> /// Whether an error enum renders as an exception class held by `std::shared_ptr`. Under the /// expected style it is a plain value type like any other enum. -pub(crate) fn is_error_class(ci: &ComponentInterface, name: &str) -> bool { - ci.is_name_used_as_error(name) && error_style() == ErrorStyle::Exceptions +pub(crate) fn is_error_class(ci: &ComponentInterface, ty: &Type) -> bool { + ci.is_used_as_error(ty) && error_style() == ErrorStyle::Exceptions } /// Whether an enum renders as a struct holding a `std::variant` rather than an `enum class`. /// A flat error is a struct under the expected style because each variant carries a message. -pub(crate) fn is_enum_struct(ci: &ComponentInterface, name: &str) -> bool { - let flat = ci.get_enum_definition(name).is_some_and(|e| e.is_flat()); - !flat || (ci.is_name_used_as_error(name) && error_style() == ErrorStyle::Expected) +pub(crate) fn is_enum_struct(ci: &ComponentInterface, ty: &Type) -> bool { + let flat = enum_definition(ci, ty).is_some_and(|e| e.is_flat()); + !flat || (ci.is_used_as_error(ty) && error_style() == ErrorStyle::Expected) } -pub(crate) fn error_class(name: &str, ci: &ComponentInterface) -> Result { - Ok(is_error_class(ci, name)) +/// The definition of an enum, from whichever crate defines it. +pub(crate) fn enum_definition<'a>(ci: &'a ComponentInterface, ty: &Type) -> Option<&'a Enum> { + let Type::Enum { name, .. } = ty else { + return None; + }; + defining_interface(ci, ty).get_enum_definition(name) +} + +/// The interface of the crate that defines a named type. +pub(crate) fn defining_interface<'a>( + ci: &'a ComponentInterface, + ty: &Type, +) -> &'a ComponentInterface { + match ty.crate_name() { + Some(crate_name) if crate_name != ci.crate_name() => ci + .find_component_interface(crate_name) + .unwrap_or_else(|| panic!("no interface for crate {crate_name}")), + _ => ci, + } } -pub(crate) fn enum_struct(name: &str, ci: &ComponentInterface) -> Result { - Ok(is_enum_struct(ci, name)) +pub(crate) fn error_class(ty: &Type, ci: &ComponentInterface) -> Result { + Ok(is_error_class(ci, ty)) +} + +pub(crate) fn enum_struct(ty: &Type, ci: &ComponentInterface) -> Result { + Ok(is_enum_struct(ci, ty)) } pub(crate) fn cpp_deref(type_: Type, ci: &ComponentInterface) -> Result { - if let Type::Enum { name, .. } = type_ { - if is_error_class(ci, &name) { - return Ok("*".to_string()); - } + if matches!(type_, Type::Enum { .. }) && is_error_class(ci, &type_) { + return Ok("*".to_string()); } Ok("".to_string()) } diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/miscellany.rs b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/miscellany.rs index ef32ec7e..0422d62a 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/miscellany.rs +++ b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/miscellany.rs @@ -13,7 +13,7 @@ macro_rules! impl_code_type_for_miscellany { format!("{}", $label) } - fn canonical_name(&self) -> String { + fn canonical_name(&self, _ci: &ComponentInterface) -> String { format!("{}", $canonical_name) } diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/mod.rs b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/mod.rs index 9e89a62a..fa0970b7 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/mod.rs +++ b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/mod.rs @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; use topological_sort::{DependencyLink, TopologicalSort}; use uniffi_bindgen::{interface::*, ComponentInterface}; -use crate::bindings::cpp::gen_cpp::filters::callback_interface_name; +use crate::bindings::cpp::gen_cpp::filters::{callback_interface_name, defining_interface}; #[derive(Serialize, Deserialize, Clone, Debug, Default)] pub enum EnumStyle { @@ -67,24 +67,59 @@ thread_local! { // uniffi's `rename` misses some types, such as a constructor's error, so the oracle renames // each type it renders as well. - static RENAMES: RefCell> = RefCell::default(); + static RENAMES: RefCell = RefCell::default(); } pub(crate) fn error_style() -> ErrorStyle { ERROR_STYLE.with(Cell::get) } +/// New type names by crate and old name, since another crate may use the old name for a type +/// of its own. +pub(crate) type Renames = HashMap<(String, String), String>; + /// `ty` with every type it names under its new name. pub(crate) fn renamed(mut ty: Type) -> Type { RENAMES.with(|renames| { let renames = renames.borrow(); if !renames.is_empty() { - ty.rename_recursive(&|name| renames.get(name).map_or(name, String::as_str).into()); + rename_type(&mut ty, &renames); } }); ty } +fn rename_type(ty: &mut Type, renames: &Renames) { + let crate_name = ty.crate_name().map(str::to_string); + match ty { + Type::Object { name, .. } + | Type::Record { name, .. } + | Type::Enum { name, .. } + | Type::CallbackInterface { name, .. } + | Type::Custom { name, .. } => { + if let Some(new) = renames.get(&(crate_name.unwrap_or_default(), name.clone())) { + *name = new.clone(); + } + } + _ => {} + } + match ty { + Type::Optional { inner_type } + | Type::Sequence { inner_type } + | Type::Set { inner_type } + | Type::Box { inner_type } => rename_type(inner_type, renames), + Type::Map { + key_type, + value_type, + } => { + rename_type(key_type, renames); + rename_type(value_type, renames); + } + Type::Custom { builtin, .. } => rename_type(builtin, renames), + _ => {} + } +} + #[derive(Clone, Deserialize, Serialize, Debug, Default)] pub(crate) struct Config { #[serde(default)] @@ -95,9 +130,9 @@ pub(crate) struct Config { error_style: ErrorStyle, #[serde(default)] strip_type_prefix: Option, - /// Every type renamed by `strip_type_prefix`, across all components, old name to new. + /// Every type renamed by `strip_type_prefix`, across all components. #[serde(skip)] - pub(crate) renames: HashMap, + pub(crate) renames: Renames, } impl Config { @@ -253,7 +288,11 @@ impl<'a> CppWrapperHeader<'a> { } }) .flat_map(|(name, types)| { + // An external type is defined in its own namespace's header, which we + // `#include`, so it never orders a local definition. It may also share its + // bare name with a local type. types + .filter(|t| !self.ci.is_external(t)) .filter_map(type_name) .map(|field_name| DependencyLink { prec: field_name, @@ -279,13 +318,8 @@ impl<'a> CppWrapperHeader<'a> { let found = self .ci .iter_local_types() - .chain(self.ci.iter_external_types()) .find(|t| type_name(t) == Some(name)); match found { - // External types are defined in their own namespace's header with their own - // converters declared, which we `#include`. They must not enter the local - // definition ordering, or we'd try to emit a definition we don't have. - Some(type_) if self.ci.is_external(type_) => {} Some(type_) => sorted.push(type_.clone()), None => { panic!("Type {} not found", name) @@ -317,23 +351,29 @@ impl<'a> CppWrapperHeader<'a> { .collect() } - pub(crate) fn external_ffi_converters(&self) -> Vec { - self.ci - .iter_external_types() - .filter_map(|ty| { - let namespace = self.ci.namespace_for_type(ty).ok()?; - if namespace == self.ci.namespace() { - return None; - } - Some(format!( - "{}::uniffi::{}", - namespace, - CppCodeOracle.find(ty).ffi_converter_name() - )) - }) - .collect::>() - .into_iter() - .collect() + /// Each external converter as `(local name, its own namespace's name)`. The local name + /// carries the namespace, so it never shadows the converter of a local namesake. + pub(crate) fn external_ffi_converters(&self) -> Vec<(String, String)> { + let mut converters = BTreeSet::new(); + for ty in self.ci.iter_external_types() { + let Ok(namespace) = self.ci.namespace_for_type(ty) else { + continue; + }; + if namespace == self.ci.namespace() { + continue; + } + let code = CppCodeOracle.find(ty); + let own = code.ffi_converter_name(defining_interface(self.ci, ty)); + let local = code.ffi_converter_name(self.ci); + if matches!(ty, Type::Object { .. }) && self.ci.is_used_as_error(ty) { + converters.insert(( + format!("{local}__as_error"), + format!("::{namespace}::uniffi::{own}__as_error"), + )); + } + converters.insert((local, format!("::{namespace}::uniffi::{own}"))); + } + converters.into_iter().collect() } pub(crate) fn includes(&self) -> Vec { @@ -392,7 +432,9 @@ impl<'a> CppWrapper<'a> { }) .filter_map(|t| { let namespace = self.ci.namespace_for_type(t).ok()?; - let canonical = CppCodeOracle.find(t).canonical_name(); + let canonical = CppCodeOracle + .find(t) + .canonical_name(defining_interface(self.ci, t)); Some(format!( "{}::uniffi::{}::init", namespace, @@ -469,3 +511,38 @@ pub(crate) fn generate_cpp_scaffolding( cpp_scaffolding_source, }) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn renames_only_the_crate_that_stripped() { + let record = |module_path: &str, name: &str| Type::Record { + module_path: module_path.to_string(), + name: name.to_string(), + }; + let renames: Renames = [(("net".into(), "MoqFrame".into()), "Frame".into())].into(); + let mut ty = Type::Map { + key_type: Box::new(Type::String), + value_type: Box::new(Type::Optional { + inner_type: Box::new(record("net::frame", "MoqFrame")), + }), + }; + rename_type(&mut ty, &renames); + let Type::Map { value_type, .. } = &ty else { + unreachable!() + }; + assert_eq!( + **value_type, + Type::Optional { + inner_type: Box::new(record("net::frame", "Frame")) + } + ); + + // `media` did not strip its prefix, so its own `MoqFrame` keeps the name. + let mut ty = record("media", "MoqFrame"); + rename_type(&mut ty, &renames); + assert_eq!(ty, record("media", "MoqFrame")); + } +} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/object.rs b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/object.rs index 4dd7fa23..c15d39ac 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/object.rs +++ b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/object.rs @@ -4,8 +4,11 @@ use uniffi_bindgen::{ }; use crate::bindings::cpp::{ - gen_cpp::filters::callback_interface_name, gen_cpp::filters::external_namespace_prefix, - gen_cpp::filters::CppCodeOracle, CodeType, + gen_cpp::filters::{ + callback_interface_name, external_canonical_prefix, external_namespace_prefix, + CppCodeOracle, + }, + CodeType, }; #[derive(Debug)] @@ -30,12 +33,16 @@ impl CodeType for ObjectCodeType { format!( "std::shared_ptr<{}{}>", external_namespace_prefix(ci, &self.module_path), - self.canonical_name() + CppCodeOracle.class_name(&self.id) ) } - fn canonical_name(&self) -> String { - CppCodeOracle.class_name(&self.id) + fn canonical_name(&self, ci: &ComponentInterface) -> String { + format!( + "{}{}", + external_canonical_prefix(ci, &self.module_path), + CppCodeOracle.class_name(&self.id) + ) } fn literal(&self, _literal: &Literal, _ci: &ComponentInterface) -> String { @@ -46,7 +53,7 @@ impl CodeType for ObjectCodeType { self.imp.has_callback_interface().then(|| { format!( "uniffi::{}::init", - callback_interface_name(&self.canonical_name()).unwrap() + callback_interface_name(&CppCodeOracle.class_name(&self.id)).unwrap() ) }) } diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/primitives.rs b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/primitives.rs index 6b90d822..c5827fc8 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/primitives.rs +++ b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/primitives.rs @@ -41,7 +41,7 @@ macro_rules! impl_code_type_for_primitive { $cpp_name.into() } - fn canonical_name(&self) -> String { + fn canonical_name(&self, _ci: &ComponentInterface) -> String { $canonical_name.into() } diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/record.rs b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/record.rs index 73d66050..f34d854e 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/record.rs +++ b/uniffi/cpp/bindgen/src/bindings/cpp/gen_cpp/record.rs @@ -1,7 +1,8 @@ use uniffi_bindgen::{interface::Literal, ComponentInterface}; use crate::bindings::cpp::{ - gen_cpp::filters::external_namespace_prefix, gen_cpp::filters::CppCodeOracle, CodeType, + gen_cpp::filters::{external_canonical_prefix, external_namespace_prefix, CppCodeOracle}, + CodeType, }; #[derive(Debug)] @@ -25,8 +26,12 @@ impl CodeType for RecordCodeType { ) } - fn canonical_name(&self) -> String { - format!("Type{}", self.id) + fn canonical_name(&self, ci: &ComponentInterface) -> String { + format!( + "{}Type{}", + external_canonical_prefix(ci, &self.module_path), + self.id + ) } fn literal(&self, _literal: &Literal, _ci: &ComponentInterface) -> String { diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/mod.rs b/uniffi/cpp/bindgen/src/bindings/cpp/mod.rs index 603e3a17..0116d113 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/mod.rs +++ b/uniffi/cpp/bindgen/src/bindings/cpp/mod.rs @@ -29,16 +29,17 @@ pub trait CodeType: Debug { /// identifier. e.g. `read_foo()`, or `FooInternals`. /// /// This is especially useful when creating specialized objects or methods to deal - /// with this type only. - fn canonical_name(&self) -> String; + /// with this type only. Another crate's type is spelled with its namespace in front, so + /// the helpers for it never collide with those for a local type of the same name. + fn canonical_name(&self, ci: &ComponentInterface) -> String; fn literal(&self, _literal: &Literal, ci: &ComponentInterface) -> String { unimplemented!("Unimplemented for {}", self.type_label(ci)) } /// Name of the FfiConverter - fn ffi_converter_name(&self) -> String { - format!("FfiConverter{}", self.canonical_name()) + fn ffi_converter_name(&self, ci: &ComponentInterface) -> String { + format!("FfiConverter{}", self.canonical_name(ci)) } /// A list of imports that are needed if this type is in use. @@ -80,10 +81,13 @@ impl BindingGenerator for CppBindingGenerator { return Ok(()); } - let flat: HashMap = renames - .values() - .flatten() - .filter_map(|(old, new)| Some((old.clone(), new.as_str()?.to_string()))) + let flat: HashMap<(String, String), String> = renames + .iter() + .flat_map(|(crate_name, table)| { + table.iter().filter_map(move |(old, new)| { + Some(((crate_name.clone(), old.clone()), new.as_str()?.to_string())) + }) + }) .collect(); for component in components.iter_mut() { rename(&mut component.ci, &renames); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/arith_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/arith_conv.hpp index 1d511cba..fe682c48 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/arith_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/arith_conv.hpp @@ -1,4 +1,4 @@ -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static {{ type_name }} lift({{ type_name }}); static {{ type_name }} lower({{ type_name }}); static {{ type_name }} read(RustStream &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/bool_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/bool_conv.hpp index 8f8031db..c7e44a21 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/bool_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/bool_conv.hpp @@ -1,4 +1,4 @@ -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static {{ type_name }} lift(uint8_t); static uint8_t lower({{ type_name }}); static {{ type_name }} read(RustStream &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/bytes_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/bytes_conv.hpp index f1e2d894..8612ec67 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/bytes_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/bytes_conv.hpp @@ -1,4 +1,4 @@ -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} struct {{ ffi_converter_name|class_name }} { static {{ type_name }} lift(RustBuffer); static RustBuffer lower(const {{ type_name }} &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback.hpp index 0bbc9a01..af1ba37e 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback.hpp @@ -1,5 +1,5 @@ {%- let class_name = type_name|class_name %} -{%- let canonical_type_name = typ|canonical_name %} +{%- let canonical_type_name = typ|canonical_name(ci) %} {%- let trait_impl = canonical_type_name|callback_interface_name %} {% call macros::docstring_value(interface_docstring, 0) %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback_conv.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback_conv.cpp index e74587a4..69d38de1 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback_conv.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback_conv.cpp @@ -1,4 +1,4 @@ -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} {%- let class_name = ffi_converter_name|class_name %} {{ type_name }} {{ class_name }}::lift(uint64_t handle) { diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback_conv.hpp index 1ed0d3fe..05ab0ac5 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback_conv.hpp @@ -1,7 +1,7 @@ -{%- let ffi_converter_name = typ|ffi_converter_name %} -{%- let canonical_type_name = typ|canonical_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} +{%- let canonical_type_name = typ|canonical_name(ci) %} -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static {{ type_name }} lift(uint64_t); static uint64_t lower(const {{ type_name }} &); static {{ type_name }} read(RustStream &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback_iface_tmpl.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback_iface_tmpl.cpp index d3fc7325..a39145cb 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback_iface_tmpl.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/callback_iface_tmpl.cpp @@ -1,6 +1,6 @@ -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} {%- let class_name = ffi_converter_name|class_name %} -{%- let canonical_type_name = typ|canonical_name %} +{%- let canonical_type_name = typ|canonical_name(ci) %} {%- let trait_impl = canonical_type_name|callback_interface_name %} {%- for (ffi_callback, method) in vtable_methods.iter() %} @@ -35,18 +35,18 @@ } {%- match method.throws_type() %} {%- when Some(error) %} - catch ({{ error|canonical_name }} &e) { + catch ({{ error|thrown_class(ci) }} &e) { result.call_status.code = 1; - result.call_status.error_buf = {{ error|lower_fn }}(e); + result.call_status.error_buf = {{ error|lower_fn(ci) }}(e); } {%- when None %} {%- endmatch %} catch (std::exception &e) { result.call_status.code = 2; - result.call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(e.what()); + result.call_status.error_buf = {{ Type::String.borrow()|lower_fn(ci) }}(e.what()); } catch (...) { result.call_status.code = 2; - result.call_status.error_buf = {{ Type::String.borrow()|lower_fn }}("Unknown C++ exception"); + result.call_status.error_buf = {{ Type::String.borrow()|lower_fn(ci) }}("Unknown C++ exception"); } complete(uniffi_callback_data, result); }; @@ -60,13 +60,13 @@ Result result{}; try { - result.return_value = {{ t|lower_fn }}(value); + result.return_value = {{ t|lower_fn(ci) }}(value); } catch (std::exception &e) { result.call_status.code = 2; - result.call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(e.what()); + result.call_status.error_buf = {{ Type::String.borrow()|lower_fn(ci) }}(e.what()); } catch (...) { result.call_status.code = 2; - result.call_status.error_buf = {{ Type::String.borrow()|lower_fn }}("Unknown C++ exception"); + result.call_status.error_buf = {{ Type::String.borrow()|lower_fn(ci) }}("Unknown C++ exception"); } complete(uniffi_callback_data, result); }; @@ -87,7 +87,7 @@ throw std::runtime_error("UniFFI callback handle has the wrong implementation type"); } {%- for arg in method.arguments() %} - auto arg{{ loop.index0 }} = {{- arg|lift_fn }}({{ arg.name()|var_name }}); + auto arg{{ loop.index0 }} = {{- arg|lift_fn(ci) }}({{ arg.name()|var_name }}); {%- endfor %} auto future = obj->{{ method.name()|var_name }}( {%- for arg in method.arguments() %} @@ -104,7 +104,7 @@ auto make_call = [&]() {% match method.return_type() %}{% when Some(t) %}-> {{ t|type_name(ci) }}{% when None %}{% endmatch %} { {%- for arg in method.arguments() %} - auto arg{{ loop.index0 }} = {{- arg|lift_fn }}({{ arg.name()|var_name }}); + auto arg{{ loop.index0 }} = {{- arg|lift_fn(ci) }}({{ arg.name()|var_name }}); {%- endfor -%} {%- if method.return_type().is_some() %}return {% endif -%} @@ -118,7 +118,7 @@ {% match method.return_type() %} {% when Some(t) %} auto write_value = [&]({{ t|type_name(ci) }} v) { - uniffi_out_return = {{ t|lower_fn }}(v); + uniffi_out_return = {{ t|lower_fn(ci) }}(v); }; {% when None %} (void)uniffi_out_return; @@ -127,7 +127,7 @@ {% match method.throws_type() %} {% when Some(error) %} - rust_call_trait_interface_with_error<{{ error|canonical_name }}>(out_status, make_call, write_value, {{ error|lower_fn }}); + rust_call_trait_interface_with_error<{{ error|thrown_class(ci) }}>(out_status, make_call, write_value, {{ error|lower_fn(ci) }}); {% when None %} rust_call_trait_interface(out_status, make_call, write_value); {% endmatch %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp index d8591e42..890b50b2 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp @@ -99,7 +99,7 @@ typedef int ForeignCallback(uint64_t handle, uint32_t method, uint8_t *args_data {% include "map_conv.hpp" %} {%- when Type::Enum { module_path, name } %} {%- let e = ci.get_enum_definition(name).unwrap() %} -{%- if ci.is_name_used_as_error(name) %} +{%- if ci.is_used_as_error(typ) %} {% for variant in e.variants() %} {% include "scaffolding/err.hpp" %} {% endfor %} @@ -108,7 +108,7 @@ typedef int ForeignCallback(uint64_t handle, uint32_t method, uint8_t *args_data {%- endif %} {%- when Type::Object { module_path, name, imp } %} {% include "scaffolding/obj.hpp" %} -ObjectMap<{{ typ|canonical_name }}> {{ name }}_map; +ObjectMap<{{ typ|canonical_name(ci) }}> {{ name }}_map; {%- when Type::CallbackInterface { module_path, name } %} {% include "scaffolding/callback.hpp" %} {%- when Type::Custom { module_path, name, builtin } %} @@ -176,7 +176,7 @@ UNIFFI_EXPORT {%- call macros::fn_definition(ffi_func) %} { UNIFFI_EXPORT void {{ ffi_func.name() }}(ForeignCallback callback_stub, RustCallStatus *out_status) { out_status->code = UNIFFI_CALL_STATUS_OK; - {{ func|ffi_converter_name }}::fn_handle.store(reinterpret_cast(callback_stub)); + {{ func|ffi_converter_name(ci) }}::fn_handle.store(reinterpret_cast(callback_stub)); } {% endfor %} @@ -191,7 +191,7 @@ UNIFFI_EXPORT {%- call macros::fn_definition(ffi_ctor) %} { std::shared_ptr<{{ obj.name() }}> obj = std::make_shared<{{ obj.name() }}>( {%- for arg in ctor.arguments() %} - {{- arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} + {{- arg|lift_fn(ci) }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} {% endfor %}); return (void*){{ obj.name() }}_map.insert(obj); } @@ -257,8 +257,8 @@ void rustbuffer_free(RustBuffer& buf) { {%- for typ in ci.iter_local_types() %} {%- let type_name = typ|type_name(ci) %} -{%- let ffi_converter_name = typ|ffi_converter_name %} -{%- let canonical_type_name = typ|canonical_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} +{%- let canonical_type_name = typ|canonical_name(ci) %} {%- let contains_object_references = ci.item_contains_object_references(typ) %} {%- match typ %} @@ -302,7 +302,7 @@ void rustbuffer_free(RustBuffer& buf) { {% include "map_tmpl.cpp" %} {%- when Type::Enum { module_path, name } %} {%- let e = ci.get_enum_definition(name).unwrap() %} -{%- if ci.is_name_used_as_error(name) %} +{%- if ci.is_used_as_error(typ) %} {% for variant in e.variants() %} {% include "scaffolding/err.cpp" %} {% endfor %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/custom.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/custom.cpp index f077bc5c..487ec170 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/custom.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/custom.cpp @@ -3,7 +3,7 @@ {%- match type_config.type_name %} {%- when Some with (type_name) %} {{ type_name }} {{ ffi_converter_name }}::lift(RustBuffer buff) { - auto builtin_val = {{ builtin|lift_fn }}(buff); + auto builtin_val = {{ builtin|lift_fn(ci) }}(buff); return {{ type_config.lift("builtin_val") }}; } @@ -11,11 +11,11 @@ RustBuffer {{ ffi_converter_name }}::lower(const {{ type_name }} &val) { auto builtin_val = {{ type_config.lower("val") }}; - return {{ builtin|lower_fn }}(builtin_val); + return {{ builtin|lower_fn(ci) }}(builtin_val); } {{ type_name }} {{ ffi_converter_name }}::read(RustStream &stream) { - auto builtin_val = {{ builtin|read_fn }}(stream); + auto builtin_val = {{ builtin|read_fn(ci) }}(stream); return {{ type_config.lift("builtin_val") }}; } @@ -23,13 +23,13 @@ RustBuffer {{ ffi_converter_name }}::lower(const {{ type_name }} &val) { void {{ ffi_converter_name }}::write(RustStream &stream, const {{ type_name }} &val) { auto builtin_val = {{ type_config.lower("val") }}; - {{ builtin|write_fn }}(stream, builtin_val); + {{ builtin|write_fn(ci) }}(stream, builtin_val); } uint64_t {{ ffi_converter_name }}::allocation_size(const {{ type_name }} &val) { auto builtin_val = {{ type_config.lower("val") }}; - return {{ builtin|allocation_size_fn }}(builtin_val); + return {{ builtin|allocation_size_fn(ci) }}(builtin_val); } {%- else %} {%- endmatch %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/custom.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/custom.hpp index 2c2dfe1b..9e8a4615 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/custom.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/custom.hpp @@ -2,7 +2,7 @@ {%- when Some with (type_config) %} {%- match type_config.type_name %} {%- when Some with (type_name) %} -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static {{ name }} lift(RustBuffer); static RustBuffer lower(const {{ name }} &); static {{ name }} read(RustStream &); @@ -12,5 +12,5 @@ struct {{ typ|ffi_converter_name }} { {%- else %} {%- endmatch %} {%- else -%} -typedef struct {{ builtin|ffi_converter_name }} {{ typ|ffi_converter_name }}; +typedef struct {{ builtin|ffi_converter_name(ci) }} {{ typ|ffi_converter_name(ci) }}; {%- endmatch %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/duration_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/duration_conv.hpp index 820093d9..adc8e1ec 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/duration_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/duration_conv.hpp @@ -1,4 +1,4 @@ -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static {{ type_name }} lift(RustBuffer); static RustBuffer lower(const {{ type_name }} &); static {{ type_name }} read(RustStream &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum.cpp index 7a82cbee..ea431323 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum.cpp @@ -1,16 +1,16 @@ -{%- if name|enum_struct(ci) && !(name|error_class(ci)) %} +{%- if typ|enum_struct(ci) && !(typ|error_class(ci)) %} {%- let trait_methods = e.uniffi_trait_methods() %} {%- match trait_methods.display_fmt %} {%- when Some with (fmt) %} std::string {{ type_name }}::to_string() const { - return uniffi::{{ Type::String.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("uniffi::" ~ ffi_converter_name ~ "::lower(*this)", fmt) %}); + return uniffi::{{ Type::String.borrow()|lift_fn(ci) }}({% call macros::rust_call_with_prefix("uniffi::" ~ ffi_converter_name ~ "::lower(*this)", fmt) %}); } {%- else %} {%- endmatch %} {%- match trait_methods.debug_fmt %} {%- when Some with (fmt) %} std::string {{ type_name }}::to_debug_string() const { - return uniffi::{{ Type::String.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("uniffi::" ~ ffi_converter_name ~ "::lower(*this)", fmt) %}); + return uniffi::{{ Type::String.borrow()|lift_fn(ci) }}({% call macros::rust_call_with_prefix("uniffi::" ~ ffi_converter_name ~ "::lower(*this)", fmt) %}); } {%- else %} {%- endmatch %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum.hpp index 2fae6230..82b98972 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum.hpp @@ -1,6 +1,6 @@ -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} {%- let ffi_converter_name = ffi_converter_name|class_name %} -{%- let as_struct = name|enum_struct(ci) %} +{%- let as_struct = typ|enum_struct(ci) %} {%- let flat_error = e.is_flat() && as_struct %} {%- if !as_struct %} {% call macros::docstring(e, 0) %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum_conv.hpp index 820093d9..adc8e1ec 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum_conv.hpp @@ -1,4 +1,4 @@ -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static {{ type_name }} lift(RustBuffer); static RustBuffer lower(const {{ type_name }} &); static {{ type_name }} read(RustStream &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum_tmpl.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum_tmpl.cpp index b19e1e4e..801ff204 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum_tmpl.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/enum_tmpl.cpp @@ -1,4 +1,4 @@ -{%- let as_struct = name|enum_struct(ci) %} +{%- let as_struct = typ|enum_struct(ci) %} {%- let flat_error = e.is_flat() && as_struct %} {%- if !as_struct %} {{ type_name }} {{ ffi_converter_name }}::lift(RustBuffer buf) { @@ -76,10 +76,10 @@ RustBuffer {{ ffi_converter_name }}::lower(const {{ type_name }} &val) { case {{ loop.index }}: return {{ type_name }}::{{ variant|variant_name(config.enum_style) }} { {%- for field in variant.fields() %} - {{ field|read_fn }}(stream), + {{ field|read_fn(ci) }}(stream), {%- endfor %} {%- if flat_error %} - {{ Type::String.borrow()|read_fn }}(stream), + {{ Type::String.borrow()|read_fn(ci) }}(stream), {%- endif %} }; {% endfor %} @@ -98,10 +98,10 @@ void {{ ffi_converter_name }}::write(RustStream &stream, const {{ type_name }} & {%- for variant in e.variants() %} {% if !loop.first %}else {% endif %}if constexpr (std::is_same_v) { {%- for field in variant.fields() %} - {{ field|write_fn }}(stream, {{ field.as_type()|cpp_deref(ci) }}arg.{% call macros::field_name(field, loop.index) %}); + {{ field|write_fn(ci) }}(stream, {{ field.as_type()|cpp_deref(ci) }}arg.{% call macros::field_name(field, loop.index) %}); {%- endfor %} {%- if flat_error %} - {{ Type::String.borrow()|write_fn }}(stream, arg.message); + {{ Type::String.borrow()|write_fn(ci) }}(stream, arg.message); {%- endif %} } {%- endfor %} @@ -123,10 +123,10 @@ uint64_t {{ ffi_converter_name }}::allocation_size(const {{ type_name|class_name {% if !loop.first %}else {% endif %}if constexpr (std::is_same_v) { uint64_t size = 0; {%- for field in variant.fields() %} - size += {{ field|allocation_size_fn }}({{ field.as_type()|cpp_deref(ci) }}arg.{% call macros::field_name(field, loop.index) %}); + size += {{ field|allocation_size_fn(ci) }}({{ field.as_type()|cpp_deref(ci) }}arg.{% call macros::field_name(field, loop.index) %}); {%- endfor %} {%- if flat_error %} - size += {{ Type::String.borrow()|allocation_size_fn }}(arg.message); + size += {{ Type::String.borrow()|allocation_size_fn(ci) }}(arg.message); {%- endif %} return size; } diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/err.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/err.hpp index bcec06f7..bc5605e4 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/err.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/err.hpp @@ -1,5 +1,5 @@ -{%- let class_name = typ|canonical_name %} -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let class_name = typ|canonical_name(ci) %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} {%- let ffi_converter_name = ffi_converter_name|class_name %} namespace uniffi { struct {{ ffi_converter_name }}; diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/err_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/err_conv.hpp index 941176ac..65f2c7df 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/err_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/err_conv.hpp @@ -1,5 +1,5 @@ -{%- let class_name = typ|canonical_name %} -struct {{ typ|ffi_converter_name }} { +{%- let class_name = typ|canonical_name(ci) %} +struct {{ typ|ffi_converter_name(ci) }} { static {{ type_name }} lift(RustBuffer buf); static RustBuffer lower(const {{ class_name }} &); static {{ type_name }} read(RustStream &stream); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/err_tmpl.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/err_tmpl.cpp index c0657bfc..4efe56de 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/err_tmpl.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/err_tmpl.cpp @@ -1,4 +1,4 @@ -{%- let class_name = typ|canonical_name %} +{%- let class_name = typ|canonical_name(ci) %} {{ type_name }} {{ ffi_converter_name }}::lift(RustBuffer buf) { auto stream = RustStream(&buf); auto ret = {{ ffi_converter_name }}::read(stream); @@ -25,13 +25,13 @@ RustBuffer {{ ffi_converter_name }}::lower(const {{ class_name }} &val) { {%- for variant in e.variants() %} {%- if e.is_flat() %} case {{ loop.index }}: - return std::make_shared<{{ class_name|to_lower_snake_case }}::{{ variant.name()|class_name }}>({{ Type::String.borrow()|read_fn }}(stream)); + return std::make_shared<{{ class_name|to_lower_snake_case }}::{{ variant.name()|class_name }}>({{ Type::String.borrow()|read_fn(ci) }}(stream)); {% else %} case {{ loop.index }}: { {{ class_name|to_lower_snake_case }}::{{ variant.name() }} var; {%- for field in variant.fields() %} - var.{% call macros::field_name(field, loop.index) %} = {{ field|read_fn }}(stream); + var.{% call macros::field_name(field, loop.index) %} = {{ field|read_fn(ci) }}(stream); {%- endfor %} return std::make_shared<{{ class_name|to_lower_snake_case }}::{{ variant.name() }}>(var); } @@ -46,7 +46,7 @@ void {{ ffi_converter_name }}::write(RustStream &stream, const {{ class_name }} stream << val.get_variant_idx(); {%- if e.is_flat() %} - {{ Type::String.borrow()|write_fn }}(stream, val.what()); + {{ Type::String.borrow()|write_fn(ci) }}(stream, val.what()); {%- else %} switch (val.get_variant_idx()) { {%- for variant in e.variants() %} @@ -54,7 +54,7 @@ void {{ ffi_converter_name }}::write(RustStream &stream, const {{ class_name }} { auto var = static_cast(val); {%- for field in variant.fields() %} - {{ field|write_fn }}(stream, {{ field.as_type()|cpp_deref(ci) }}var.{% call macros::field_name(field, loop.index) %}); + {{ field|write_fn(ci) }}(stream, {{ field.as_type()|cpp_deref(ci) }}var.{% call macros::field_name(field, loop.index) %}); {%- endfor %} break; } @@ -75,7 +75,7 @@ uint64_t {{ ffi_converter_name }}::allocation_size(const {{ class_name }} &val) auto var = static_cast(val); return static_cast(sizeof(int32_t) {%- for field in variant.fields() %} - + {{ field|allocation_size_fn }}({{ field.as_type()|cpp_deref(ci) }}var.{% call macros::field_name(field, loop.index) %}) + + {{ field|allocation_size_fn(ci) }}({{ field.as_type()|cpp_deref(ci) }}var.{% call macros::field_name(field, loop.index) %}) {%- endfor %}); } {%- endfor %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/fn_def.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/fn_def.cpp index 8c519f3f..1fea45e3 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/fn_def.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/fn_def.cpp @@ -6,12 +6,12 @@ return {% call macros::rust_call_async(func, return_type|type_name(ci)) %}; {%- else if fallible %} return uniffi::lift_expected<{{ return_type|type_name(ci) }}>({% call macros::rust_call(func) %}, [](auto ret) { - return uniffi::{{ return_type|lift_fn }}(ret); + return uniffi::{{ return_type|lift_fn(ci) }}(ret); }); {%- else %} auto ret = {% call macros::rust_call(func) %}; - return uniffi::{{ return_type|lift_fn }}(ret); + return uniffi::{{ return_type|lift_fn(ci) }}(ret); {%- endif %} } {%- when None -%} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/internal_types.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/internal_types.cpp index ebebf02d..17692dbd 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/internal_types.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/internal_types.cpp @@ -2,8 +2,8 @@ {%- for typ in ci.iter_local_types() %} {%- let type_name = typ|type_name(ci) %} -{%- let ffi_converter_name = typ|ffi_converter_name %} -{%- let canonical_type_name = typ|canonical_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} +{%- let canonical_type_name = typ|canonical_name(ci) %} {%- let contains_object_references = ci.item_contains_object_references(typ) %} {%- let namespace = ci.namespace() %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/macros.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/macros.cpp index fb645210..194c75de 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/macros.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/macros.cpp @@ -3,7 +3,7 @@ {{ func.ffi_func().name() }}, {%- match func.throws_type() %} {% when Some with (e) %} - uniffi::{{ e|ffi_error_converter_name }}::lift + uniffi::{{ e|ffi_error_converter_name(ci) }}::lift {%- else %} nullptr {%- endmatch %} @@ -16,7 +16,7 @@ {{ func.ffi_func().name() }}, {%- match func.throws_type() %} {% when Some with (e) %} - uniffi::{{ e|ffi_error_converter_name }}::lift, + uniffi::{{ e|ffi_error_converter_name(ci) }}::lift, {%- else %} nullptr, {%- endmatch %} @@ -36,10 +36,10 @@ {{ func.ffi_rust_future_cancel(ci) }}, {{ func.ffi_rust_future_complete(ci) }}, {{ func.ffi_rust_future_free(ci) }}, - [](auto value) { return uniffi::{{ func.return_type().unwrap()|lift_fn }}(value); }, + [](auto value) { return uniffi::{{ func.return_type().unwrap()|lift_fn(ci) }}(value); }, {%- match func.throws_type() %} {% when Some with (e) %} - uniffi::{{ e|ffi_error_converter_name }}::lift + uniffi::{{ e|ffi_error_converter_name(ci) }}::lift {%- else %} nullptr {%- endmatch %} @@ -60,7 +60,7 @@ nullptr, {%- match func.throws_type() %} {% when Some with (e) %} - uniffi::{{ e|ffi_error_converter_name }}::lift + uniffi::{{ e|ffi_error_converter_name(ci) }}::lift {%- else %} nullptr {%- endmatch %} @@ -79,10 +79,10 @@ {{ func.ffi_rust_future_cancel(ci) }}, {{ func.ffi_rust_future_complete(ci) }}, {{ func.ffi_rust_future_free(ci) }}, - [](auto value) { return uniffi::{{ func.return_type().unwrap()|lift_fn }}(value); }, + [](auto value) { return uniffi::{{ func.return_type().unwrap()|lift_fn(ci) }}(value); }, {%- match func.throws_type() %} {% when Some with (e) %} - uniffi::{{ e|ffi_error_converter_name }}::lift + uniffi::{{ e|ffi_error_converter_name(ci) }}::lift {%- else %} nullptr {%- endmatch %} @@ -104,7 +104,7 @@ nullptr, {%- match func.throws_type() %} {% when Some with (e) %} - uniffi::{{ e|ffi_error_converter_name }}::lift + uniffi::{{ e|ffi_error_converter_name(ci) }}::lift {%- else %} nullptr {%- endmatch %} @@ -128,7 +128,7 @@ v{{- field_num -}} {% macro arg_list_lowered(func) %} {%- for arg in func.arguments() -%} -uniffi::{{ arg|lower_fn }}({{ arg.name()|var_name }}) +uniffi::{{ arg|lower_fn(ci) }}({{ arg.name()|var_name }}) {%- if !loop.last -%}, {% endif -%} {% endfor -%} {% endmacro %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/map_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/map_conv.hpp index f1e2d894..8612ec67 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/map_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/map_conv.hpp @@ -1,4 +1,4 @@ -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} struct {{ ffi_converter_name|class_name }} { static {{ type_name }} lift(RustBuffer); static RustBuffer lower(const {{ type_name }} &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/map_tmpl.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/map_tmpl.cpp index 725795c0..05537fe7 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/map_tmpl.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/map_tmpl.cpp @@ -1,4 +1,4 @@ -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} {%- let class_name = ffi_converter_name|class_name %} {{ type_name }} {{ class_name }}::lift(RustBuffer buf) { auto stream = RustStream(&buf); @@ -26,7 +26,7 @@ RustBuffer {{ class_name }}::lower(const {{ type_name }} &val) { ret.reserve(count); for (decltype(count) i = 0; i < count; i++) { - ret.insert({ {{ key_type|read_fn }}(stream), {{ value_type|read_fn }}(stream) }); + ret.insert({ {{ key_type|read_fn(ci) }}(stream), {{ value_type|read_fn(ci) }}(stream) }); } return ret; @@ -36,8 +36,8 @@ void {{ class_name }}::write(RustStream &stream, const {{ type_name }} &val) { stream << static_cast(val.size()); for (auto &entry : val) { - {{ key_type|write_fn }}(stream, {{ key_type.as_type()|cpp_deref(ci) }}entry.first); - {{ value_type|write_fn }}(stream, {{ value_type.as_type()|cpp_deref(ci) }}entry.second); + {{ key_type|write_fn(ci) }}(stream, {{ key_type.as_type()|cpp_deref(ci) }}entry.first); + {{ value_type|write_fn(ci) }}(stream, {{ value_type.as_type()|cpp_deref(ci) }}entry.second); } } @@ -45,8 +45,8 @@ uint64_t {{ class_name }}::allocation_size(const {{ type_name }} &val) { uint64_t size = sizeof(int32_t); for (auto &entry : val) { - size += {{ key_type|allocation_size_fn }}({{ key_type.as_type()|cpp_deref(ci) }}entry.first); - size += {{ value_type|allocation_size_fn }}({{ value_type.as_type()|cpp_deref(ci) }}entry.second); + size += {{ key_type|allocation_size_fn(ci) }}({{ key_type.as_type()|cpp_deref(ci) }}entry.first); + size += {{ value_type|allocation_size_fn(ci) }}({{ value_type.as_type()|cpp_deref(ci) }}entry.second); } return size; diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj.cpp index ba749aaa..ce67468c 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj.cpp @@ -2,8 +2,8 @@ {%- let (interface_name, impl_class_name) = obj|object_names %} {%- let foreign_interface_name = interface_name %} {%- let class_name = type_name|class_name %} -{%- let ffi_converter_name = typ|ffi_converter_name %} -{%- let canonical_type_name = typ|canonical_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} +{%- let canonical_type_name = typ|canonical_name(ci) %} {%- if obj.has_callback_interface() %} {%- let vtable = obj.vtable().expect("trait interface should have a vtable") %} @@ -23,7 +23,7 @@ namespace uniffi { } } -{% if ci.is_name_used_as_error(name) && !config.expected() %} +{% if ci.is_used_as_error(typ) && !config.expected() %} void {{ impl_class_name }}::throw_underlying() { throw *this; } @@ -71,10 +71,10 @@ namespace uniffi { return {% call macros::rust_call_async_with_prefix("ptr", method, return_type|type_name(ci)) %}; {%- else if config.expected() && method.throws_type().is_some() %} return uniffi::lift_expected<{{ return_type|type_name(ci) }}>({% call macros::rust_call_with_prefix("ptr", method) %}, [](auto ret) { - return uniffi::{{ return_type|lift_fn }}(ret); + return uniffi::{{ return_type|lift_fn(ci) }}(ret); }); {%- else %} - return uniffi::{{ return_type|lift_fn }}({% call macros::rust_call_with_prefix("ptr", method) %}); + return uniffi::{{ return_type|lift_fn(ci) }}({% call macros::rust_call_with_prefix("ptr", method) %}); {%- endif %} {%- else %} {%- if method.is_async() %} @@ -108,26 +108,26 @@ uint64_t {{ impl_class_name }}::_uniffi_internal_clone_pointer() const { {% match method %} {% when UniffiTrait::Display { fmt } %} std::string {{ impl_class_name }}::to_string() const { - return uniffi::{{ Type::String.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("this->_uniffi_internal_clone_pointer()", fmt) %}); + return uniffi::{{ Type::String.borrow()|lift_fn(ci) }}({% call macros::rust_call_with_prefix("this->_uniffi_internal_clone_pointer()", fmt) %}); } {% when UniffiTrait::Debug { fmt } %} std::string {{ impl_class_name }}::to_debug_string() const { - return uniffi::{{ Type::String.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("this->_uniffi_internal_clone_pointer()", fmt) %}); + return uniffi::{{ Type::String.borrow()|lift_fn(ci) }}({% call macros::rust_call_with_prefix("this->_uniffi_internal_clone_pointer()", fmt) %}); } {% when UniffiTrait::Eq { eq, ne } %} bool {{ impl_class_name }}::eq(const {{ type_name }} &other) const { - return uniffi::{{ Type::Boolean.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("this->_uniffi_internal_clone_pointer()", eq) %}); + return uniffi::{{ Type::Boolean.borrow()|lift_fn(ci) }}({% call macros::rust_call_with_prefix("this->_uniffi_internal_clone_pointer()", eq) %}); } bool {{ impl_class_name }}::ne(const {{ type_name }} &other) const { - return uniffi::{{ Type::Boolean.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("this->_uniffi_internal_clone_pointer()", ne) %}); + return uniffi::{{ Type::Boolean.borrow()|lift_fn(ci) }}({% call macros::rust_call_with_prefix("this->_uniffi_internal_clone_pointer()", ne) %}); } {% when UniffiTrait::Hash { hash } %} uint64_t {{ impl_class_name }}::hash() const { - return uniffi::{{ Type::UInt64.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("this->_uniffi_internal_clone_pointer()", hash) %}); + return uniffi::{{ Type::UInt64.borrow()|lift_fn(ci) }}({% call macros::rust_call_with_prefix("this->_uniffi_internal_clone_pointer()", hash) %}); } {% when UniffiTrait::Ord { cmp } %} int8_t {{ impl_class_name }}::cmp(const {{ type_name }} &other) const { - return uniffi::{{ Type::Int8.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("this->_uniffi_internal_clone_pointer()", cmp) %}); + return uniffi::{{ Type::Int8.borrow()|lift_fn(ci) }}({% call macros::rust_call_with_prefix("this->_uniffi_internal_clone_pointer()", cmp) %}); } {% endmatch %} {%- endfor %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj.hpp index fa73c530..f3064e52 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj.hpp @@ -1,8 +1,8 @@ {%- let obj = ci.get_object_definition(name).unwrap() %} {%- let (interface_name, impl_class_name) = obj|object_names %} {%- let class_name = type_name|class_name %} -{%- let ffi_converter_name = typ|ffi_converter_name %} -{%- let canonical_type_name = typ|canonical_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} +{%- let canonical_type_name = typ|canonical_name(ci) %} {%- if obj.has_callback_interface() %} {%- if obj.has_async_method() %} struct {{ canonical_type_name }} { @@ -34,7 +34,7 @@ struct {{ impl_class_name }} we don't need to complex branching for multiple inheritance #} {% if obj.has_callback_interface() %} : public {% if obj.has_async_method() %}{{ canonical_type_name }}{% else %}{{ interface_name }}{% endif %} {% endif %} -{% if ci.is_name_used_as_error(name) %} : public std::exception {% endif %} +{% if ci.is_used_as_error(typ) %} : public std::exception {% endif %} { friend uniffi::{{ ffi_converter_name|class_name }}; @@ -98,7 +98,7 @@ struct {{ impl_class_name }} {%- endmatch %} {%- endfor %} - {% if ci.is_name_used_as_error(name) && !config.expected() %} + {% if ci.is_used_as_error(typ) && !config.expected() %} void throw_underlying(); {%- endif -%} private: diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj_conv.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj_conv.cpp index 2bb7bbe2..ff9df175 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj_conv.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj_conv.cpp @@ -1,9 +1,9 @@ {%- let obj = ci.get_object_definition(name).unwrap() %} {%- let (interface_name, impl_class_name) = obj|object_names %} -{%- let is_error = ci.is_name_used_as_error(name) %} +{%- let is_error = ci.is_used_as_error(typ) %} {%- if is_error %} -{{ type_name }} {{ typ|ffi_error_converter_name}}::lift(RustBuffer buf) { +{{ type_name }} {{ typ|ffi_error_converter_name(ci)}}::lift(RustBuffer buf) { auto stream = RustStream(&buf); auto val = {{ ffi_converter_name }}::read(stream); rustbuffer_free(buf); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj_conv.hpp index e19f8023..9a132e23 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/obj_conv.hpp @@ -1,15 +1,15 @@ {%- let obj = ci.get_object_definition(name).unwrap() %} -{%- let canonical_type_name = typ|canonical_name %} +{%- let canonical_type_name = typ|canonical_name(ci) %} {%- let trait_impl = canonical_type_name|callback_interface_name %} -{%- let is_error = ci.is_name_used_as_error(name) %} +{%- let is_error = ci.is_used_as_error(typ) %} {%- if is_error %} -struct {{ typ|ffi_error_converter_name}} { +struct {{ typ|ffi_error_converter_name(ci)}} { static {{ type_name }} lift(RustBuffer); }; {% endif %} -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static {{ type_name }} lift(uint64_t); static uint64_t lower(const {{ type_name }} &); static {{ type_name }} read(RustStream &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/opt_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/opt_conv.hpp index feb3ef5d..5629b60f 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/opt_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/opt_conv.hpp @@ -1,4 +1,4 @@ -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static {{ type_name }} lift(RustBuffer buf); static RustBuffer lower(const {{ type_name }}& val); static {{ type_name }} read(RustStream &stream); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/opt_tmpl.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/opt_tmpl.cpp index 49c23a31..dc1f4b01 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/opt_tmpl.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/opt_tmpl.cpp @@ -23,13 +23,13 @@ RustBuffer {{ ffi_converter_name }}::lower(const {{ type_name }}& val) { {%- if typ|can_dereference_optional(ci) %} if (has_value) { - return {{ inner_type|read_fn }}(stream); + return {{ inner_type|read_fn(ci) }}(stream); } else { return nullptr; } {%- else %} if (has_value) { - return std::make_optional({{ inner_type|read_fn }}(stream)); + return std::make_optional({{ inner_type|read_fn(ci) }}(stream)); } else { return std::nullopt; } @@ -41,9 +41,9 @@ void {{ ffi_converter_name }}::write(RustStream &stream, const {{ type_name }}& if (value) { {%- if typ|can_dereference_optional(ci) %} - {{ inner_type|write_fn }}(stream, {{ inner_type.as_type()|cpp_deref(ci) }}value); + {{ inner_type|write_fn(ci) }}(stream, {{ inner_type.as_type()|cpp_deref(ci) }}value); {%- else %} - {{ inner_type|write_fn }}(stream, value.value()); + {{ inner_type|write_fn(ci) }}(stream, value.value()); {%- endif %} } } @@ -53,9 +53,9 @@ uint64_t {{ ffi_converter_name }}::allocation_size(const {{ type_name }} &val) { if (val) { {%- if typ|can_dereference_optional(ci) %} - ret += {{ inner_type|allocation_size_fn }}({{ inner_type.as_type()|cpp_deref(ci) }}val); + ret += {{ inner_type|allocation_size_fn(ci) }}({{ inner_type.as_type()|cpp_deref(ci) }}val); {%- else %} - ret += {{ inner_type|allocation_size_fn }}(val.value()); + ret += {{ inner_type|allocation_size_fn(ci) }}(val.value()); {%- endif %} } diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/rec.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/rec.cpp index be4e45be..1f3e19a0 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/rec.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/rec.cpp @@ -21,14 +21,14 @@ RustBuffer {{ ffi_converter_name }}::lower(const {{ class_name }} &val) { {{ class_name }} {{ ffi_converter_name }}::read(RustStream &stream) { return { {%- for field in rec.fields() %} - {{ field|read_fn }}(stream){% if !loop.last %},{% endif %} + {{ field|read_fn(ci) }}(stream){% if !loop.last %},{% endif %} {%- endfor %} }; } void {{ ffi_converter_name }}::write(RustStream &stream, const {{ class_name }} &val) { {%- for field in rec.fields() %} - {{ field|write_fn }}(stream, {{ field.as_type()|cpp_deref(ci) }}val.{{ field.name()|var_name }}); + {{ field|write_fn(ci) }}(stream, {{ field.as_type()|cpp_deref(ci) }}val.{{ field.name()|var_name }}); {%- endfor %} } @@ -38,7 +38,7 @@ uint64_t {{ ffi_converter_name }}::allocation_size(const {{ class_name }} &val) return 0; {% else %} return {% for field in rec.fields() %} - {{ field|allocation_size_fn}}({{ field.as_type()|cpp_deref(ci) }}val.{{ field.name()|var_name() }}){% if !loop.last %} +{% else -%};{%- endif %} + {{ field|allocation_size_fn(ci)}}({{ field.as_type()|cpp_deref(ci) }}val.{{ field.name()|var_name() }}){% if !loop.last %} +{% else -%};{%- endif %} {%- endfor %} {% endif %} } diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/rec_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/rec_conv.hpp index 4d937cc8..8146c4c5 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/rec_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/rec_conv.hpp @@ -1,5 +1,5 @@ {%- let class_name = type_name|class_name %} -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static {{ class_name }} lift(RustBuffer); static RustBuffer lower(const {{ class_name }} &); static {{ class_name }} read(RustStream &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/callback.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/callback.cpp index db4582b1..8f027f8d 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/callback.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/callback.cpp @@ -1,7 +1,7 @@ -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} {%- let type_name = typ|type_name(ci) %} {%- let class_name = type_name|class_name %} -{%- let canonical_type_name = typ|canonical_name %} +{%- let canonical_type_name = typ|canonical_name(ci) %} {%- let iface = ci.get_callback_interface_definition(name).unwrap() %} namespace { class {{ iface.name() }}Proxy: public {{ iface.name() }} { @@ -39,13 +39,13 @@ class {{ iface.name() }}Proxy: public {{ iface.name() }} { int32_t size = 0; {% for arg in m.arguments() %} - size += {{ arg|allocation_size_fn }}({{ arg.name() }}); + size += {{ arg|allocation_size_fn(ci) }}({{ arg.name() }}); {% endfor %} RustBuffer in_buf = rustbuffer_alloc(size); RustStream in_stream(&in_buf); {% for arg in m.arguments() %} - {{ arg|write_fn }}(in_stream, {{ arg.name() }}); + {{ arg|write_fn(ci) }}(in_stream, {{ arg.name() }}); {% endfor %} auto ret = callback_stub(this->handle, {{ loop.index }}, in_buf.data, size, &out_buf); @@ -55,7 +55,7 @@ class {{ iface.name() }}Proxy: public {{ iface.name() }} { {% match m.return_type() %} {% when Some with (return_type) %} RustStream out_stream(&out_buf); - auto result = {{ return_type|read_fn }}(out_stream); + auto result = {{ return_type|read_fn(ci) }}(out_stream); rustbuffer_free(out_buf); return result; @@ -72,7 +72,7 @@ class {{ iface.name() }}Proxy: public {{ iface.name() }} { {%- let err_type = m.throws_type().unwrap()|type_name(ci) %} {%- let err_enum = ci.get_enum_definition(err_type).unwrap() %} {%- for variant in err_enum.variants() %} - {%- let converter_name = err_enum|ffi_converter_name %} + {%- let converter_name = err_enum|ffi_converter_name(ci) %} case {{ loop.index }}: { auto result = {{ converter_name }}{{ variant.name() }}::read(out_stream, v); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/callback.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/callback.hpp index 8c01a50b..6f3498d0 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/callback.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/callback.hpp @@ -1,7 +1,7 @@ -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} {%- let type_name = typ|type_name(ci) %} {%- let class_name = type_name|class_name %} -{%- let canonical_type_name = typ|canonical_name %} +{%- let canonical_type_name = typ|canonical_name(ci) %} struct {{ ffi_converter_name|class_name }} { static {{ type_name }} lift(uint64_t); static uint64_t lower(const {{ type_name }} &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/custom.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/custom.cpp index 18442ae0..ad8f3ed5 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/custom.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/custom.cpp @@ -1,8 +1,8 @@ -{%- let converter = typ|ffi_converter_name -%} +{%- let converter = typ|ffi_converter_name(ci) -%} {%- let custom_converter = "UniffiCustomTypeConverter{}"|format(typ|type_name(ci)) -%} {{ type_name }} {{ converter }}::lift(RustBuffer buff) { auto stream = RustStream(&buff); - auto builtin_val = {{ builtin|read_fn }}(stream); + auto builtin_val = {{ builtin|read_fn(ci) }}(stream); return {{ custom_converter }}::into_custom(builtin_val); } @@ -12,13 +12,13 @@ RustBuffer {{ converter }}::lower(const {{ type_name }} &val) { auto stream = RustStream(&buf); auto builtin_val = {{ custom_converter }}::from_custom(val); - {{ builtin|write_fn }}(stream, builtin_val); + {{ builtin|write_fn(ci) }}(stream, builtin_val); return std::move(buf); } {{ type_name }} {{ converter }}::read(RustStream &stream) { - auto builtin_val = {{ builtin|read_fn }}(stream); + auto builtin_val = {{ builtin|read_fn(ci) }}(stream); return {{ custom_converter }}::into_custom(builtin_val); } @@ -26,12 +26,12 @@ RustBuffer {{ converter }}::lower(const {{ type_name }} &val) { void {{ converter }}::write(RustStream &stream, const {{ type_name }} &val) { auto builtin_val = {{ custom_converter }}::from_custom(val); - {{ builtin|write_fn }}(stream, builtin_val); + {{ builtin|write_fn(ci) }}(stream, builtin_val); } int32_t {{ converter }}::allocation_size(const {{ type_name }} &val) { auto builtin_val = {{ custom_converter }}::from_custom(val); - return {{ builtin|allocation_size_fn }}(builtin_val); + return {{ builtin|allocation_size_fn(ci) }}(builtin_val); } diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/custom.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/custom.hpp index 9134afe6..ee6497e2 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/custom.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/custom.hpp @@ -1,5 +1,5 @@ {% let type_name = typ|type_name(ci) %} -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static {{ type_name }} lift(RustBuffer); static RustBuffer lower(const {{ type_name }} &); static {{ type_name }} read(RustStream &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/err.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/err.cpp index 3bf8f648..bf3a7ba8 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/err.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/err.cpp @@ -1,6 +1,6 @@ {%- let type_name = e|type_name(ci) %} {%- let class_name = type_name|class_name %} -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} {%- let namespace = type_name|to_lower_snake_case %} {{ namespace }}::{{ variant.name() }} {{ ffi_converter_name }}{{ variant.name() }}::lift(RustBuffer buf, int32_t v) { @@ -31,7 +31,7 @@ RustBuffer {{ ffi_converter_name }}{{ variant.name() }}::lower(const {{ namespac {%- else %} {{ namespace }}::{{ variant.name() }} var; {%- for field in variant.fields() %} - var.{{ field.name()|var_name }} = {{ field|read_fn }}(stream); + var.{{ field.name()|var_name }} = {{ field|read_fn(ci) }}(stream); {%- endfor %} return var; {%- endif %} @@ -41,10 +41,10 @@ void {{ ffi_converter_name }}{{ variant.name() }}::write(RustStream &stream, con stream << int32_t({{ loop.index }}); {%- if e.is_flat() %} - {{ Type::String.borrow()|write_fn }}(stream, val.what()); + {{ Type::String.borrow()|write_fn(ci) }}(stream, val.what()); {%- else %} {%- for field in variant.fields() %} - {{ field|write_fn }}(stream, val.{{ field.name()|var_name }}); + {{ field|write_fn(ci) }}(stream, val.{{ field.name()|var_name }}); {%- endfor %} {%- endif %} } @@ -52,10 +52,10 @@ void {{ ffi_converter_name }}{{ variant.name() }}::write(RustStream &stream, con int32_t {{ ffi_converter_name }}{{ variant.name() }}::allocation_size(const {{ namespace }}::{{ variant.name() }} &val) { int32_t size = sizeof(int32_t); {%- if e.is_flat() %} - size += {{ Type::String.borrow()|allocation_size_fn }}(val.what()); + size += {{ Type::String.borrow()|allocation_size_fn(ci) }}(val.what()); {%- else %} {%- for field in variant.fields() %} - size += {{ field|allocation_size_fn }}(val.{{ field.name()|var_name }}); + size += {{ field|allocation_size_fn(ci) }}(val.{{ field.name()|var_name }}); {%- endfor %} {%- endif %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/err.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/err.hpp index ae5862b6..7b1042a3 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/err.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/err.hpp @@ -1,6 +1,6 @@ {%- let type_name = e|type_name(ci) %} {%- let class_name = type_name|class_name %} -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} {%- let namespace = type_name|to_lower_snake_case %} struct {{ ffi_converter_name }}{{ variant.name() }} { static {{ namespace }}::{{ variant.name() }} lift(RustBuffer buf, int32_t v); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp index ed3a4d62..0d9a6f92 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp @@ -11,7 +11,7 @@ {%- let err_type = func.throws_type().unwrap()|type_name(ci) %} {%- let err_enum = ci.get_enum_definition(err_type).unwrap() %} {%- for variant in err_enum.variants() %} -{%- let converter_name = err_enum|ffi_converter_name %} +{%- let converter_name = err_enum|ffi_converter_name(ci) %} } catch (const {{ err_type|to_lower_snake_case }}::{{ variant.name() }} &e) { out_status->code = UNIFFI_CALL_STATUS_ERROR; out_status->error_buf = {{ converter_name }}{{ variant.name() }}::lower(e); @@ -19,7 +19,7 @@ {%- endif %} } catch (const std::exception &e) { out_status->code = UNIFFI_CALL_STATUS_PANIC; - out_status->error_buf = {{ Type::String.borrow()|ffi_converter_name }}::lower(e.what()); + out_status->error_buf = {{ Type::String.borrow()|ffi_converter_name(ci) }}::lower(e.what()); } catch (...) { out_status->code = UNIFFI_CALL_STATUS_PANIC; } @@ -36,13 +36,13 @@ {% when Some with (return_type) %} auto ret = {{ namespace }}::{{ scaffolding_fn.name() }}( {%- for arg in scaffolding_fn.arguments() %} -{{- arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} +{{- arg|lift_fn(ci) }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} {% endfor %}); -return {{ return_type|lower_fn }}(ret); +return {{ return_type|lower_fn(ci) }}(ret); {% when None %} {{ namespace }}::{{ scaffolding_fn.name() }}( {%- for arg in scaffolding_fn.arguments() %} -{{- arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} +{{- arg|lift_fn(ci) }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} {% endfor %}); {% endmatch %} {% endmacro %} @@ -53,14 +53,14 @@ return {{ return_type|lower_fn }}(ret); auto ret = obj->{{ scaffolding_fn.name() }}( {% if scaffolding_fn.takes_self_by_arc() %}obj{% if !scaffolding_fn.arguments().is_empty() %},{% endif %}{% endif %} {%- for arg in scaffolding_fn.arguments() %} -{{- arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} +{{- arg|lift_fn(ci) }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} {% endfor %}); -return {{ return_type|lower_fn }}(ret); +return {{ return_type|lower_fn(ci) }}(ret); {% when None %} obj->{{ scaffolding_fn.name() }}( {% if scaffolding_fn.takes_self_by_arc() %}obj{% if !scaffolding_fn.arguments().is_empty() %},{% endif %}{% endif %} {%- for arg in scaffolding_fn.arguments() %} -{{- arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} +{{- arg|lift_fn(ci) }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} {% endfor %}); {% endmatch %} {% endmacro %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/obj.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/obj.hpp index c2acefe3..c432b963 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/obj.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/scaffolding/obj.hpp @@ -1,5 +1,5 @@ {%- let type_name = typ|type_name(ci) %} -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static {{ type_name }} lift(uint64_t); static uint64_t lower(const {{ type_name }} &); static {{ type_name }} read(RustStream &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/seq_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/seq_conv.hpp index f1e2d894..8612ec67 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/seq_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/seq_conv.hpp @@ -1,4 +1,4 @@ -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} struct {{ ffi_converter_name|class_name }} { static {{ type_name }} lift(RustBuffer); static RustBuffer lower(const {{ type_name }} &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/seq_tmpl.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/seq_tmpl.cpp index 06f27cee..48459291 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/seq_tmpl.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/seq_tmpl.cpp @@ -1,4 +1,4 @@ -{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} {%- let class_name = ffi_converter_name|class_name %} {{ type_name }} {{ class_name }}::lift(RustBuffer buf) { auto stream = RustStream(&buf); @@ -26,7 +26,7 @@ RustBuffer {{ class_name }}::lower(const {{ type_name }} &val) { ret.reserve(count); for (decltype(count) i = 0; i < count; i++) { - ret.push_back({{ inner_type|read_fn }}(stream)); + ret.push_back({{ inner_type|read_fn(ci) }}(stream)); } return ret; @@ -36,7 +36,7 @@ void {{ class_name }}::write(RustStream &stream, const {{ type_name }} &val) { stream << static_cast(val.size()); for (auto &elem : val) { - {{ inner_type|write_fn }}(stream, {{ inner_type.as_type()|cpp_deref(ci) }}elem); + {{ inner_type|write_fn(ci) }}(stream, {{ inner_type.as_type()|cpp_deref(ci) }}elem); } } @@ -44,7 +44,7 @@ uint64_t {{ class_name }}::allocation_size(const {{ type_name }} &val) { uint64_t size = sizeof(int32_t); for (auto &elem : val) { - size += {{ inner_type|allocation_size_fn }}({{inner_type.as_type()|cpp_deref(ci) }}elem); + size += {{ inner_type|allocation_size_fn(ci) }}({{inner_type.as_type()|cpp_deref(ci) }}elem); } return size; diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/str_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/str_conv.hpp index 7cc0f6ca..ca614499 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/str_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/str_conv.hpp @@ -1,4 +1,4 @@ -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static std::string lift(RustBuffer buf); static RustBuffer lower(const std::string &); static std::string read(RustStream &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/timestamp_conv.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/timestamp_conv.hpp index 820093d9..adc8e1ec 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/timestamp_conv.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/timestamp_conv.hpp @@ -1,4 +1,4 @@ -struct {{ typ|ffi_converter_name }} { +struct {{ typ|ffi_converter_name(ci) }} { static {{ type_name }} lift(RustBuffer); static RustBuffer lower(const {{ type_name }} &); static {{ type_name }} read(RustStream &); diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/types.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/types.cpp index ba110668..de01edcb 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/types.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/types.cpp @@ -2,8 +2,8 @@ {%- for typ in ci.iter_local_types() %} {%- let type_name = typ|type_name(ci) %} -{%- let ffi_converter_name = typ|ffi_converter_name %} -{%- let canonical_type_name = typ|canonical_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} +{%- let canonical_type_name = typ|canonical_name(ci) %} {%- let contains_object_references = ci.item_contains_object_references(typ) %} {%- let namespace = ci.namespace() %} @@ -20,15 +20,15 @@ namespace uniffi { {%- for typ in ci.iter_local_types() %} {%- let type_name = typ|type_name(ci) %} -{%- let ffi_converter_name = typ|ffi_converter_name %} -{%- let canonical_type_name = typ|canonical_name %} +{%- let ffi_converter_name = typ|ffi_converter_name(ci) %} +{%- let canonical_type_name = typ|canonical_name(ci) %} {%- let contains_object_references = ci.item_contains_object_references(typ) %} {%- let namespace = ci.namespace() %} {%- match typ %} {%- when Type::Enum { name, module_path } %} {%- let e = ci.get_enum_definition(name).unwrap() %} -{%- if name|error_class(ci) %} +{%- if typ|error_class(ci) %} {% include "err_tmpl.cpp" %} {%- else %} {% include "enum_tmpl.cpp" %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/wrapper.cpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/wrapper.cpp index bb141d0a..10ca7905 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/wrapper.cpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/wrapper.cpp @@ -55,7 +55,7 @@ void check_rust_call(const RustCallStatus &status, F) { case 2: if (status.error_buf.len > 0) { - auto message = "Rust panic: " + {{ Type::String.borrow()|lift_fn }}(status.error_buf); + auto message = "Rust panic: " + {{ Type::String.borrow()|lift_fn(ci) }}(status.error_buf); ::uniffi::detail::fatal(message.c_str()); } @@ -131,7 +131,7 @@ void check_rust_call(const RustCallStatus &status, F error_cb) { case 2: if (status.error_buf.len > 0) { - throw std::runtime_error({{ Type::String.borrow()|lift_fn }}(status.error_buf)); + throw std::runtime_error({{ Type::String.borrow()|lift_fn(ci) }}(status.error_buf)); } throw std::runtime_error("A Rust panic has occurred"); @@ -175,7 +175,7 @@ void rust_call_trait_interface(RustCallStatus* status, F make_call, W write_valu } } catch (std::exception &e) { status->code = 2; - status->error_buf = {{ Type::String.borrow()|lower_fn }}(e.what()); + status->error_buf = {{ Type::String.borrow()|lower_fn(ci) }}(e.what()); } } @@ -198,7 +198,7 @@ void rust_call_trait_interface_with_error(RustCallStatus* status, F make_call, W } } catch (std::exception &e) { status->code = 2; - status->error_buf = {{ Type::String.borrow()|lower_fn }}(e.what()); + status->error_buf = {{ Type::String.borrow()|lower_fn(ci) }}(e.what()); } } {%- endif %} diff --git a/uniffi/cpp/bindgen/src/bindings/cpp/templates/wrapper.hpp b/uniffi/cpp/bindgen/src/bindings/cpp/templates/wrapper.hpp index 8833304a..5f4654e9 100644 --- a/uniffi/cpp/bindgen/src/bindings/cpp/templates/wrapper.hpp +++ b/uniffi/cpp/bindgen/src/bindings/cpp/templates/wrapper.hpp @@ -62,7 +62,7 @@ namespace {{ namespace }} { {%- match typ %} {%- when Type::Enum { module_path, name } %} {%- let e = ci.get_enum_definition(name).unwrap() %} -{%- if ci.is_name_used_as_error(name) || !e.is_flat() %} +{%- if ci.is_used_as_error(typ) || !e.is_flat() %} struct {{ name }}; {%- else %} enum class {{ name }}; @@ -93,7 +93,7 @@ typedef {{ type_name }} {{ name }}; {%- match typ %} {%- when Type::Enum { module_path, name } %} {%- let e = ci.get_enum_definition(name).unwrap() %} -{%- if name|error_class(ci) %} +{%- if typ|error_class(ci) %} {% include "err.hpp" %} {%- else %} {% include "enum.hpp" %} @@ -120,8 +120,8 @@ namespace uniffi { using ::uniffi::RustStream; using ::uniffi::RustStreamBuffer; using ::uniffi::Future; -{%- for converter in self.external_ffi_converters() %} -using {{ converter }}; +{%- for (converter, external) in self.external_ffi_converters() %} +using {{ converter }} = {{ external }}; {%- endfor %} RustBuffer rustbuffer_alloc(uint64_t); @@ -165,7 +165,7 @@ void rustbuffer_free(RustBuffer); {% include "duration_conv.hpp" %} {%- when Type::Enum { module_path, name } %} {%- let e = ci.get_enum_definition(name).unwrap() %} -{%- if name|error_class(ci) %} +{%- if typ|error_class(ci) %} {% include "err_conv.hpp" %} {%- else %} {% include "enum_conv.hpp" %} diff --git a/uniffi/cpp/cpp-tests/CMakeLists.txt b/uniffi/cpp/cpp-tests/CMakeLists.txt index ecb221f0..6c614743 100644 --- a/uniffi/cpp/cpp-tests/CMakeLists.txt +++ b/uniffi/cpp/cpp-tests/CMakeLists.txt @@ -124,6 +124,21 @@ list(APPEND BINDING_FILES ${BINDINGS_SRC_DIR}/ext_types_export.cpp ${BINDINGS_SRC_DIR}/ext_types_import.cpp) +# Two crates that share their type names, one using the other's. +add_executable(namespaces-test + tests/namespaces/main.cpp + ${BINDINGS_SRC_DIR}/net.cpp + ${BINDINGS_SRC_DIR}/media.cpp) +target_include_directories(namespaces-test PRIVATE ${BINDINGS_SRC_DIR} include) +target_link_directories(namespaces-test PRIVATE ${BINDINGS_BUILD_DIR}) +target_link_libraries(namespaces-test uniffi_bindgen_cpp_fixtures Threads::Threads ${ADDITIONAL_LIBS}) +add_test(NAME namespaces-test COMMAND namespaces-test) +memcheck_test(namespaces-test) +add_dependencies(namespaces-test bindings) +list(APPEND BINDING_FILES + ${BINDINGS_SRC_DIR}/net.cpp + ${BINDINGS_SRC_DIR}/media.cpp) + # `error_style = "expected"` bindings, built the way Unreal builds: no exceptions, no RTTI. set(EXPECTED_BINDINGS_DIR ${BINDINGS_BUILD_DIR}/bindings-expected) add_executable(expected-test @@ -169,11 +184,16 @@ set_tests_properties(strip-type-prefix-collision-test PROPERTIES # The C backend: a C11 header over expected-style C++ bindings, built without exceptions. set(C_BINDINGS_DIR ${BINDINGS_BUILD_DIR}/bindings-c) set(C_BINDING_FILES ${C_BINDINGS_DIR}/capi.cpp ${C_BINDINGS_DIR}/capi_c.cpp) +# A crate's C API needs the C and C++ files of every crate whose types it uses. +set(C_NAMESPACES_BINDING_FILES + ${C_BINDINGS_DIR}/net.cpp ${C_BINDINGS_DIR}/net_c.cpp + ${C_BINDINGS_DIR}/media.cpp ${C_BINDINGS_DIR}/media_c.cpp) +# The arguments after the test name are the generated files it compiles. macro(c_test_case TEST_NAME) add_executable(${TEST_NAME}-test tests/${TEST_NAME}/main.c tests/c_support/support.cpp - ${C_BINDING_FILES}) + ${ARGN}) target_include_directories(${TEST_NAME}-test PRIVATE ${C_BINDINGS_DIR} include) target_link_directories(${TEST_NAME}-test PRIVATE ${BINDINGS_BUILD_DIR}) target_link_libraries(${TEST_NAME}-test uniffi_bindgen_cpp_fixtures Threads::Threads ${ADDITIONAL_LIBS}) @@ -191,8 +211,9 @@ set_tests_properties(${TEST_NAME}-test PROPERTIES TIMEOUT 30) memcheck_test(${TEST_NAME}-test) endmacro(c_test_case) -c_test_case(c_api) -c_test_case(c_dispatcher) +c_test_case(c_api ${C_BINDING_FILES}) +c_test_case(c_dispatcher ${C_BINDING_FILES}) +c_test_case(c_namespaces ${C_NAMESPACES_BINDING_FILES}) # The C backend refuses what it cannot render instead of emitting half of it. add_test(NAME c-refuses-callbacks-test @@ -231,9 +252,13 @@ add_custom_target(bindings ALL add_custom_target(bindings-c ALL DEPENDS libs - BYPRODUCTS ${C_BINDING_FILES} + BYPRODUCTS ${C_BINDING_FILES} ${C_NAMESPACES_BINDING_FILES} COMMAND ./uniffi-bindgen-cpp --lang c --library ${UNIFFI_FIXTURES_LIB} --crate uniffi_c_api --out-dir bindings-c + COMMAND ./uniffi-bindgen-cpp --lang c --library ${UNIFFI_FIXTURES_LIB} + --crate uniffi_namespaces_net --out-dir bindings-c + COMMAND ./uniffi-bindgen-cpp --lang c --library ${UNIFFI_FIXTURES_LIB} + --crate uniffi_namespaces_media --out-dir bindings-c WORKING_DIRECTORY ${BINDINGS_BUILD_DIR} COMMENT "Generating C bindings" ) diff --git a/uniffi/cpp/cpp-tests/tests/c_namespaces/main.c b/uniffi/cpp/cpp-tests/tests/c_namespaces/main.c new file mode 100644 index 00000000..ea730b0f --- /dev/null +++ b/uniffi/cpp/cpp-tests/tests/c_namespaces/main.c @@ -0,0 +1,188 @@ +/* The namespaces fixture through the C API: `media`'s types hold or refer to `net`'s, and + * both crates name their types alike. */ +#include +#include +#include + +static net_group_consumer *net_group(uint64_t sequence, net_priority priority, const net_frame *frames, size_t len) { + net_frame_list list = {frames, len}; + return net_group_consumer_new(sequence, priority, &list); +} + +static net_frame frame_of(const uint8_t *data, size_t len) { + net_frame frame; + frame.payload.data = data; + frame.payload.len = len; + return frame; +} + +static bool payload_is(const net_frame *frame, const uint8_t *data, size_t len) { + return frame->payload.len == len && (len == 0 || memcmp(frame->payload.data, data, len) == 0); +} + +static void object_wraps_its_namesake(void) { + const uint8_t first[] = {1, 'a'}; + const uint8_t second[] = {0, 'b'}; + net_frame frames[] = {frame_of(first, 2), frame_of(second, 2)}; + net_group_consumer *inner = net_group(3, NET_PRIORITY_LOW, frames, 2); + media_group_consumer *group = media_group_consumer_new(inner); + net_group_consumer_free(inner); + + net_group_consumer *held = media_group_consumer_group(group); + CHECK(net_group_consumer_sequence(held) == 3); + net_group_consumer_free(held); + + media_priority *priority = media_group_consumer_priority(group); + CHECK(priority->tag == MEDIA_PRIORITY_NET); + CHECK(priority->net.priority == NET_PRIORITY_LOW); + media_priority_free(priority); + + media_frame *frame = NULL; + CHECK(media_group_consumer_read_frame(group, &frame) == NULL); + CHECK(payload_is(&frame->raw, first, 2)); + CHECK(frame->keyframe); + media_frame_free(frame); + CHECK(media_group_consumer_read_frame(group, &frame) == NULL); + CHECK(!frame->keyframe); + media_frame_free(frame); + + /* Each layer's error, both named `GroupError`. */ + media_group_error *error = media_group_consumer_read_frame(group, &frame); + CHECK(error != NULL); + CHECK(frame == NULL); + CHECK(error->tag == MEDIA_GROUP_ERROR_NET); + CHECK(error->net.source.tag == NET_GROUP_ERROR_CLOSED); + CHECK(error->net.source.closed.code == 7); + CHECK(media_group_error_message(error) != NULL); + media_group_error_free(error); + + net_frame empty = frame_of(NULL, 0); + error = media_decode(&empty, &frame); + CHECK(error != NULL); + CHECK(error->tag == MEDIA_GROUP_ERROR_EMPTY); + media_group_error_free(error); + + media_group_consumer_free(group); +} + +typedef struct { + test_latch *done; + net_group_error *error; + media_frame *frame; +} next_result; + +static void on_next(void *user_data, net_group_error *error, media_frame *value) { + next_result *r = (next_result *)user_data; + r->error = error; + r->frame = value; + test_latch_signal(r->done); +} + +static void async_throws_the_net_error(void) { + const uint8_t only[] = {1}; + net_frame frames[] = {frame_of(only, 1)}; + net_group_consumer *inner = net_group(1, NET_PRIORITY_HIGH, frames, 1); + media_group_consumer *group = media_group_consumer_new(inner); + net_group_consumer_free(inner); + + media_priority *priority = media_group_consumer_priority(group); + CHECK(priority->tag == MEDIA_PRIORITY_REALTIME); + media_priority_free(priority); + + next_result r = {test_latch_new(), NULL, NULL}; + media_task *task = media_group_consumer_next_frame(group, on_next, &r); + test_latch_wait(r.done); + media_task_free(task); + CHECK(r.error == NULL); + CHECK(payload_is(&r.frame->raw, only, 1)); + media_frame_free(r.frame); + + r.frame = NULL; + task = media_group_consumer_next_frame(group, on_next, &r); + test_latch_wait(r.done); + media_task_free(task); + CHECK(r.frame == NULL); + CHECK(r.error != NULL); + CHECK(r.error->tag == NET_GROUP_ERROR_CLOSED); + CHECK(r.error->closed.code == 7); + net_group_error_free(r.error); + + test_latch_free(r.done); + media_group_consumer_free(group); +} + +static void containers_of_both(void) { + const uint8_t z[] = {0, 'z'}; + net_frame raw = frame_of(z, 2); + media_frame *frame = NULL; + CHECK(media_decode(&raw, &frame) == NULL); + net_frame *encoded = media_encode(frame); + CHECK(payload_is(encoded, z, 2)); + net_frame_free(encoded); + media_frame_free(frame); + + const uint8_t a[] = {0, 'a'}; + const uint8_t b[] = {1, 'b'}; + net_frame raws[] = {frame_of(a, 2), frame_of(NULL, 0), frame_of(b, 2)}; + media_net_frame_list raw_list = {raws, 3}; + media_frame_list *frames = media_decode_all(&raw_list); + CHECK(frames->len == 2); + CHECK(!frames->data[0].keyframe); + CHECK(frames->data[1].keyframe); + + media_bounds *bounds = media_find_bounds(frames); + CHECK(bounds->keyframe != NULL); + CHECK(payload_is(&bounds->keyframe->raw, b, 2)); + CHECK(bounds->last != NULL); + CHECK(payload_is(bounds->last, b, 2)); + media_bounds_free(bounds); + media_frame_list_free(frames); + + media_frame_list none = {NULL, 0}; + bounds = media_find_bounds(&none); + CHECK(bounds->keyframe == NULL); + CHECK(bounds->last == NULL); + media_bounds_free(bounds); + + net_group_consumer *inners[] = { + net_group(1, NET_PRIORITY_LOW, NULL, 0), + net_group(2, NET_PRIORITY_HIGH, NULL, 0), + }; + media_net_group_consumer_list inner_list = {inners, 2}; + media_groups *groups = media_wrap_all(&inner_list); + net_group_consumer_free(inners[0]); + net_group_consumer_free(inners[1]); + + CHECK(groups->groups.len == 2); + for (size_t i = 0; i < 2; ++i) { + net_group_consumer *held = media_group_consumer_group(groups->groups.data[i]); + CHECK(net_group_consumer_sequence(held) == i + 1); + net_group_consumer_free(held); + } + CHECK(groups->net.len == 2); + CHECK(groups->media.len == 2); + for (size_t i = 0; i < 2; ++i) { + uint64_t sequence = groups->net.keys[i]; + net_priority net = groups->net.values[i]; + CHECK(net == (sequence == 1 ? NET_PRIORITY_LOW : NET_PRIORITY_HIGH)); + } + for (size_t i = 0; i < 2; ++i) { + uint64_t sequence = groups->media.keys[i]; + const media_priority *media = &groups->media.values[i]; + if (sequence == 1) { + CHECK(media->tag == MEDIA_PRIORITY_NET); + CHECK(media->net.priority == NET_PRIORITY_LOW); + } else { + CHECK(media->tag == MEDIA_PRIORITY_REALTIME); + } + } + media_groups_free(groups); +} + +int main(void) { + object_wraps_its_namesake(); + async_throws_the_net_error(); + containers_of_both(); + media_shutdown_dispatcher(); + return 0; +} diff --git a/uniffi/cpp/cpp-tests/tests/namespaces/main.cpp b/uniffi/cpp/cpp-tests/tests/namespaces/main.cpp new file mode 100644 index 00000000..bbdf33e9 --- /dev/null +++ b/uniffi/cpp/cpp-tests/tests/namespaces/main.cpp @@ -0,0 +1,104 @@ +// Two crates that each export `GroupConsumer`, `Frame`, `Priority`, and `GroupError`, where +// `media`'s types hold or refer to `net`'s. +#include +#include + +namespace { + +std::shared_ptr net_group( + uint64_t sequence, + net::Priority priority, + std::vector> payloads +) { + std::vector frames; + for (auto &payload : payloads) { + frames.push_back(net::Frame{std::move(payload)}); + } + return net::GroupConsumer::init(sequence, priority, frames); +} + +bool is_net(const media::Priority &priority, net::Priority expected) { + auto *inner = std::get_if(&priority.get_variant()); + return inner && inner->priority == expected; +} + +bool is_realtime(const media::Priority &priority) { + return std::holds_alternative(priority.get_variant()); +} + +void object_wraps_its_namesake() { + auto inner = net_group(3, net::Priority::kLow, {{1, 'a'}, {0, 'b'}}); + auto group = media::GroupConsumer::init(inner); + ASSERT_EQ(group->group()->sequence(), 3); + ASSERT_TRUE(is_net(group->priority(), net::Priority::kLow)); + + media::Frame frame = group->read_frame(); + ASSERT_TRUE(frame.raw.payload == (std::vector{1, 'a'})); + ASSERT_TRUE(frame.keyframe); + ASSERT_FALSE(group->read_frame().keyframe); + + // Each layer's error, both named `GroupError`. + try { + group->read_frame(); + ASSERT_TRUE(false && "read_frame didn't throw"); + } catch (const media::group_error::Net &error) { + auto *closed = dynamic_cast(error.source.get()); + ASSERT_TRUE(closed != nullptr); + ASSERT_EQ(closed->code, 7); + } + EXPECT_EXCEPTION(media::decode(net::Frame{{}}), media::group_error::Empty); +} + +void async_throws_the_net_error() { + auto group = media::GroupConsumer::init(net_group(1, net::Priority::kHigh, {{1}})); + ASSERT_TRUE(is_realtime(group->priority())); + + auto frame = group->next_frame().get(); + ASSERT_TRUE(frame.raw.payload == (std::vector{1})); + try { + group->next_frame().get(); + ASSERT_TRUE(false && "next_frame didn't throw"); + } catch (const net::group_error::Closed &error) { + ASSERT_EQ(error.code, 7); + } +} + +void containers_of_both() { + auto frame = media::decode(net::Frame{{0, 'z'}}); + ASSERT_TRUE(media::encode(frame).payload == (std::vector{0, 'z'})); + + auto frames = media::decode_all({net::Frame{{0, 'a'}}, net::Frame{{}}, net::Frame{{1, 'b'}}}); + ASSERT_EQ(frames.size(), 2); + ASSERT_FALSE(frames[0].keyframe); + ASSERT_TRUE(frames[1].keyframe); + + auto bounds = media::find_bounds(frames); + ASSERT_TRUE(bounds.keyframe.has_value()); + ASSERT_TRUE(bounds.keyframe->raw.payload == frames[1].raw.payload); + ASSERT_TRUE(bounds.last.has_value()); + ASSERT_TRUE(bounds.last->payload == (std::vector{1, 'b'})); + auto empty = media::find_bounds({}); + ASSERT_FALSE(empty.keyframe.has_value()); + ASSERT_FALSE(empty.last.has_value()); + + auto groups = media::wrap_all({ + net_group(1, net::Priority::kLow, {}), + net_group(2, net::Priority::kHigh, {}), + }); + ASSERT_EQ(groups.groups.size(), 2); + ASSERT_EQ(groups.groups[0]->group()->sequence(), 1); + ASSERT_EQ(groups.groups[1]->group()->sequence(), 2); + ASSERT_TRUE(groups.net.at(1) == net::Priority::kLow); + ASSERT_TRUE(groups.net.at(2) == net::Priority::kHigh); + ASSERT_TRUE(is_net(groups.media.at(1), net::Priority::kLow)); + ASSERT_TRUE(is_realtime(groups.media.at(2))); +} + +} // namespace + +int main() { + object_wraps_its_namesake(); + async_throws_the_net_error(); + containers_of_both(); + return 0; +} diff --git a/uniffi/cpp/fixtures/Cargo.toml b/uniffi/cpp/fixtures/Cargo.toml index 5d958ea4..3697b44a 100644 --- a/uniffi/cpp/fixtures/Cargo.toml +++ b/uniffi/cpp/fixtures/Cargo.toml @@ -23,6 +23,8 @@ uniffi-fixture-coverall = { path = "../../fixtures/coverall" } uniffi-fixture-docstring = { path = "../../fixtures/docstring" } uniffi-fixture-time = { path = "../../fixtures/uniffi-fixture-time" } uniffi-fixture-trait-methods = { path = "../../fixtures/trait-methods" } +uniffi-fixture-namespaces-media = { path = "../../fixtures/namespaces/media" } +uniffi-fixture-namespaces-net = { path = "../../fixtures/namespaces/net" } uniffi-async-calls = { path = "async-calls" } uniffi-c-api = { path = "c-api" } diff --git a/uniffi/cpp/fixtures/src/lib.rs b/uniffi/cpp/fixtures/src/lib.rs index b49a9515..59898d0a 100644 --- a/uniffi/cpp/fixtures/src/lib.rs +++ b/uniffi/cpp/fixtures/src/lib.rs @@ -24,6 +24,9 @@ mod uniffi_fixtures { uniffi_ext_types_export::uniffi_reexport_scaffolding!(); uniffi_ext_types_import::uniffi_reexport_scaffolding!(); + + uniffi_namespaces_net::uniffi_reexport_scaffolding!(); + uniffi_namespaces_media::uniffi_reexport_scaffolding!(); } use std::sync::{ atomic::{AtomicU64, Ordering}, diff --git a/uniffi/dart/fixtures/namespaces/Cargo.toml b/uniffi/dart/fixtures/namespaces/Cargo.toml new file mode 100644 index 00000000..8ec1eeeb --- /dev/null +++ b/uniffi/dart/fixtures/namespaces/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "dart_namespaces" +version = "0.1.0" +edition = "2021" +publish = false + +# One library holding both crates of the namespaces fixture, which share their type names. +[lib] +name = "dart_namespaces" +crate-type = ["lib", "cdylib"] + +[dependencies] +uniffi = { workspace = true } +uniffi-fixture-namespaces-media = { path = "../../../fixtures/namespaces/media" } +uniffi-fixture-namespaces-net = { path = "../../../fixtures/namespaces/net" } + +[dev-dependencies] +uniffi-dart = { path = "../../", features = ["bindgen-tests"] } +uniffi = { workspace = true, features = ["bindgen-tests"] } +anyhow = "1" + +[features] +ffi-trace = [ + "uniffi/ffi-trace", + "uniffi-fixture-namespaces-media/ffi-trace", + "uniffi-fixture-namespaces-net/ffi-trace", +] diff --git a/uniffi/dart/fixtures/namespaces/src/lib.rs b/uniffi/dart/fixtures/namespaces/src/lib.rs new file mode 100644 index 00000000..452e9b61 --- /dev/null +++ b/uniffi/dart/fixtures/namespaces/src/lib.rs @@ -0,0 +1,3 @@ +// Link both crates' scaffolding into this library, as an application bundling them would. +uniffi_namespaces_media::uniffi_reexport_scaffolding!(); +uniffi_namespaces_net::uniffi_reexport_scaffolding!(); diff --git a/uniffi/dart/fixtures/namespaces/test/namespaces_test.dart b/uniffi/dart/fixtures/namespaces/test/namespaces_test.dart new file mode 100644 index 00000000..6bc337dd --- /dev/null +++ b/uniffi/dart/fixtures/namespaces/test/namespaces_test.dart @@ -0,0 +1,98 @@ +import 'dart:typed_data'; + +import 'package:test/test.dart'; +import '../media.dart' as media; +import '../net.dart' as net; + +net.Frame netFrame(List payload) => net.Frame(payload: Uint8List.fromList(payload)); + +net.GroupConsumer netGroup(int sequence, net.Priority priority, List> payloads) => + net.GroupConsumer( + sequence: sequence, + priority: priority, + frames: payloads.map(netFrame).toList(), + ); + +void main() { + test('an object wraps its namesake', () { + final inner = netGroup(3, net.Priority.low, [ + [1, 97], + [0, 98], + ]); + final group = media.GroupConsumer(group: inner); + expect(group.group(), isA()); + expect(group.group().sequence(), 3); + final priority = group.priority(); + expect(priority, isA()); + expect((priority as media.NetPriority).priority, net.Priority.low); + + final frame = group.readFrame(); + expect(frame, isA()); + expect(frame.raw.payload, [1, 97]); + expect(frame.keyframe, true); + expect(group.readFrame().keyframe, false); + + // Each layer's error, both named `GroupError`. + try { + group.readFrame(); + fail('expected media.NetGroupException'); + } on media.NetGroupException catch (e) { + expect(e.source, isA()); + expect((e.source as net.ClosedGroupException).code, 7); + } + expect(() => media.decode(raw: netFrame([])), throwsA(isA())); + }); + + test('async throws the net error', () async { + final group = media.GroupConsumer( + group: netGroup(1, net.Priority.high, [ + [1], + ]), + ); + expect(group.priority(), isA()); + + final frame = await group.nextFrame(); + expect(frame.raw.payload, [1]); + try { + await group.nextFrame(); + fail('expected net.ClosedGroupException'); + } on net.ClosedGroupException catch (e) { + expect(e.code, 7); + } + }); + + test('containers of both', () { + final frame = media.decode(raw: netFrame([0, 122])); + expect(media.encode(frame: frame).payload, [0, 122]); + + final frames = media.decodeAll(raw: [ + netFrame([0, 97]), + netFrame([]), + netFrame([1, 98]), + ]); + expect(frames.map((f) => f.keyframe).toList(), [false, true]); + final bounds = media.findBounds(frames: frames); + expect(bounds.keyframe!.raw.payload, [1, 98]); + expect(bounds.last!.payload, [1, 98]); + final empty = media.findBounds(frames: []); + expect(empty.keyframe, isNull); + expect(empty.last, isNull); + + final groups = media.wrapAll(groups: [ + netGroup(1, net.Priority.low, []), + netGroup(2, net.Priority.high, []), + ]); + expect(groups.groups.map((g) => g.group().sequence()).toList(), [1, 2]); + expect(groups.net, {1: net.Priority.low, 2: net.Priority.high}); + expect((groups.media[1] as media.NetPriority).priority, net.Priority.low); + expect(groups.media[2], isA()); + }); + + test('variants hold a data enum and its flat namesake', () { + final tracks = media.tracks(kind: net.VideoKind(640)); + expect(tracks.length, 2); + final kind = (tracks[0] as media.NetTrack).kind; + expect((kind as net.VideoKind).width, 640); + expect((tracks[1] as media.MediaTrack).kind, media.Kind.video); + }); +} diff --git a/uniffi/dart/fixtures/namespaces/tests/mod.rs b/uniffi/dart/fixtures/namespaces/tests/mod.rs new file mode 100644 index 00000000..e2f0892e --- /dev/null +++ b/uniffi/dart/fixtures/namespaces/tests/mod.rs @@ -0,0 +1,6 @@ +use anyhow::Result; + +#[test] +fn namespaces() -> Result<()> { + uniffi_dart::testing::run_library_test("dart_namespaces") +} diff --git a/uniffi/dart/src/gen/callback_interface.rs b/uniffi/dart/src/gen/callback_interface.rs index dc8fea4f..0341b749 100644 --- a/uniffi/dart/src/gen/callback_interface.rs +++ b/uniffi/dart/src/gen/callback_interface.rs @@ -2,6 +2,7 @@ use genco::prelude::*; use heck::ToUpperCamelCase; use uniffi_bindgen::interface::ffi::{FfiStruct, FfiType}; use uniffi_bindgen::interface::{AsType, Method, Type}; +use uniffi_bindgen::ComponentInterface; use crate::gen::oracle::{AsCodeType, DartCodeOracle}; use crate::gen::render::{AsRenderable, Renderable, TypeHelperRenderer}; @@ -11,21 +12,32 @@ use crate::gen::CodeType; pub struct CallbackInterfaceCodeType { name: String, self_type: Type, + namespace: Option, } impl CallbackInterfaceCodeType { - pub fn new(name: String, self_type: Type) -> Self { - Self { name, self_type } + pub fn new(name: String, self_type: Type, namespace: Option) -> Self { + Self { name, self_type, namespace } } } impl CodeType for CallbackInterfaceCodeType { fn type_label(&self) -> String { - super::DartCodeOracle::class_name(&self.name) + DartCodeOracle::qualify(self.namespace.as_deref(), DartCodeOracle::class_name(&self.name)) } fn canonical_name(&self) -> String { - format!("CallbackInterface{}", self.type_label()) + DartCodeOracle::qualify_canonical( + self.namespace.as_deref(), + format!("CallbackInterface{}", DartCodeOracle::class_name(&self.name)), + ) + } + + fn ffi_converter_name(&self) -> String { + DartCodeOracle::qualify( + self.namespace.as_deref(), + format!("FfiConverterCallbackInterface{}", DartCodeOracle::class_name(&self.name)), + ) } fn initialization_fn(&self) -> Option { @@ -36,12 +48,16 @@ impl CodeType for CallbackInterfaceCodeType { impl Renderable for CallbackInterfaceCodeType { fn render_type_helper(&self, type_helper: &dyn TypeHelperRenderer) -> dart::Tokens { type_helper.include_once_check(&self.canonical_name(), &self.self_type); + // Another crate's library declares its own helpers. + if self.namespace.is_some() { + return quote!(); + } let callback = type_helper.get_ci().get_callback_interface_definition(&self.name).unwrap(); // Generate all necessary components for the callback interface let interface = generate_callback_interface( callback.name(), - &callback.as_codetype().ffi_converter_name(), + &callback.as_codetype(type_helper.get_ci()).ffi_converter_name(), &callback.methods(), type_helper, None, @@ -187,7 +203,7 @@ pub fn generate_callback_interface( $async_support // We must define callback signatures - $(generate_callback_methods_signatures(cls_name, methods)) + $(generate_callback_methods_signatures(cls_name, methods, type_helper.get_ci())) }; tokens @@ -202,7 +218,8 @@ fn generate_callback_methods_definitions( .arguments() .iter() .map(|arg| { - let arg_type = arg.as_renderable().render_type(&arg.as_type(), type_helper); + let arg_type = + arg.as_renderable(type_helper.get_ci()).render_type(&arg.as_type(), type_helper); let arg_name = DartCodeOracle::var_name(arg.name()); quote!($arg_type $arg_name) @@ -211,13 +228,13 @@ fn generate_callback_methods_definitions( let ret_type = if method.is_async() { if let Some(ret) = method.return_type() { - let rendered = ret.as_renderable().render_type(ret, type_helper); + let rendered = ret.as_renderable(type_helper.get_ci()).render_type(ret, type_helper); quote!(Future<$rendered>) } else { quote!(Future) } } else if let Some(ret) = method.return_type() { - ret.as_renderable().render_type(ret, type_helper) + ret.as_renderable(type_helper.get_ci()).render_type(ret, type_helper) } else { quote!(void) }; @@ -227,7 +244,11 @@ fn generate_callback_methods_definitions( ) } -fn generate_callback_methods_signatures(callback_name: &str, methods: &[&Method]) -> dart::Tokens { +fn generate_callback_methods_signatures( + callback_name: &str, + methods: &[&Method], + ci: &ComponentInterface, +) -> dart::Tokens { let mut tokens = dart::Tokens::new(); for (method_index, method) in methods.iter().enumerate() { //let method_name = DartCodeOracle::fn_name(method.name()); @@ -239,13 +260,13 @@ fn generate_callback_methods_signatures(callback_name: &str, methods: &[&Method] let arg_native_types: Vec = method .arguments() .iter() - .map(|arg| DartCodeOracle::native_type_label(Some(&arg.as_type()))) + .map(|arg| DartCodeOracle::native_type_label(Some(&arg.as_type()), ci)) .collect(); let arg_dart_types: Vec = method .arguments() .iter() - .map(|arg| DartCodeOracle::native_dart_type_label(Some(&arg.as_type()))) + .map(|arg| DartCodeOracle::native_dart_type_label(Some(&arg.as_type()), ci)) .collect(); if method.is_async() { @@ -263,7 +284,7 @@ fn generate_callback_methods_signatures(callback_name: &str, methods: &[&Method] }); } else { let method_return_type = if let Some(ret) = method.return_type() { - DartCodeOracle::native_type_label(Some(ret)) + DartCodeOracle::native_type_label(Some(ret), ci) } else { quote!(Void) }; @@ -325,7 +346,7 @@ pub fn generate_callback_functions( .iter() .map(|arg| { let arg_name = DartCodeOracle::var_name(arg.name()); - DartCodeOracle::callback_param_type(&arg.as_type(), &arg_name) + DartCodeOracle::callback_param_type(&arg.as_type(), &arg_name, type_helper.get_ci()) }) .collect(); @@ -336,7 +357,7 @@ pub fn generate_callback_functions( .enumerate() .map(|(arg_idx, arg)| { let arg_name = DartCodeOracle::var_name(arg.name()); - DartCodeOracle::callback_arg_lift_indexed(&arg.as_type(), &arg_name, arg_idx) + DartCodeOracle::callback_arg_lift_indexed(&arg.as_type(), &arg_name, arg_idx, type_helper.get_ci()) }) .collect(); @@ -362,11 +383,11 @@ pub fn generate_callback_functions( let struct_tokens_alt = struct_tokens.clone(); if let Some(ret) = m.return_type() { - type_helper.include_once_check(&ret.as_codetype().canonical_name(), ret); + type_helper.include_once_check(&ret.as_codetype(type_helper.get_ci()).canonical_name(), ret); } let success_return = if let Some(ret) = m.return_type() { - let converter = ret.as_codetype().ffi_converter_name(); + let converter = ret.as_codetype(type_helper.get_ci()).ffi_converter_name(); quote!(resultStructPtr.ref.returnValue = $(&converter).lower(result);) } else { quote!() @@ -435,7 +456,7 @@ pub fn generate_callback_functions( } else { // Handle return value using the oracle let call_dart_method = if let Some(ret) = m.return_type() { - DartCodeOracle::callback_return_handling(ret, method_name, arg_names) + DartCodeOracle::callback_return_handling(ret, method_name, arg_names, type_helper.get_ci()) } else { // Handle void return types DartCodeOracle::callback_void_handling(method_name, arg_names) @@ -510,8 +531,9 @@ fn callback_error_handling( status: dart::Tokens, ) -> dart::Tokens { if let Some(error_type) = throws_type { - let error_type_label = error_type.as_renderable().render_type(error_type, type_helper); - let error_lower = error_type.as_codetype().ffi_converter_name(); + let error_type_label = + error_type.as_renderable(type_helper.get_ci()).render_type(error_type, type_helper); + let error_lower = error_type.as_codetype(type_helper.get_ci()).ffi_converter_name(); quote! { if (e is $error_type_label) { $(&status).code = CALL_ERROR; diff --git a/uniffi/dart/src/gen/compounds.rs b/uniffi/dart/src/gen/compounds.rs index 46ccaed7..2999e1d3 100644 --- a/uniffi/dart/src/gen/compounds.rs +++ b/uniffi/dart/src/gen/compounds.rs @@ -2,23 +2,26 @@ use genco::lang::dart; use genco::prelude::*; use paste::paste; use uniffi_bindgen::interface::Type; +use uniffi_bindgen::ComponentInterface; use super::oracle::{AsCodeType, DartCodeOracle}; use crate::gen::render::{AsRenderable, Renderable, TypeHelperRenderer}; use crate::gen::CodeType; macro_rules! impl_code_type_for_compound { - ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => { + ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => { paste! { #[derive(Debug)] pub struct $T { self_type: Type, inner: Type, + inner_code: Box, } impl $T { - pub fn new(self_type: Type, inner: Type) -> Self { - Self { self_type, inner } + pub fn new(self_type: Type, inner: Type, ci: &ComponentInterface) -> Self { + let inner_code = inner.as_codetype(ci); + Self { self_type, inner, inner_code } } fn inner(&self) -> &Type { &self.inner @@ -27,16 +30,16 @@ macro_rules! impl_code_type_for_compound { impl CodeType for $T { fn type_label(&self) -> String { - format!($type_label_pattern, DartCodeOracle::find(self.inner()).type_label()) + format!($type_label_pattern, self.inner_code.type_label()) } fn canonical_name(&self) -> String { - format!($canonical_name_pattern, DartCodeOracle::find(self.inner()).canonical_name()) + format!($canonical_name_pattern, self.inner_code.canonical_name()) } } } - } - } + }; +} macro_rules! impl_renderable_for_compound { ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => { @@ -44,7 +47,7 @@ macro_rules! impl_renderable_for_compound { impl Renderable for $T { fn render_type_helper(&self, type_helper: &dyn TypeHelperRenderer) -> dart::Tokens { type_helper.include_once_check(&self.ffi_converter_name(), &self.self_type); - let inner_codetype = DartCodeOracle::find(self.inner()); + let inner_codetype = &self.inner_code; let original_canonical = inner_codetype.canonical_name(); let canonical_with_exception = @@ -87,7 +90,7 @@ macro_rules! impl_renderable_for_compound { | Type::Int8 | Type::Int16 | Type::Int32 | Type::Int64 | Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64 | Type::Float32 | Type::Float64 => { - self.inner().as_renderable().render_type_helper(type_helper) + self.inner().as_renderable(type_helper.get_ci()).render_type_helper(type_helper) } _ => quote!() } @@ -155,7 +158,7 @@ macro_rules! impl_renderable_for_compound { if type_helper.include_once_check(&converter_name, &self.self_type) { return quote!(); } - let inner_codetype = self.inner().as_codetype(); + let inner_codetype = &self.inner_code; let original_canonical = inner_codetype.canonical_name(); let canonical_with_exception = @@ -242,11 +245,15 @@ pub struct MapCodeType { self_type: Type, key: Type, value: Type, + key_code: Box, + value_code: Box, } impl MapCodeType { - pub fn new(self_type: Type, key: Type, value: Type) -> Self { - Self { self_type, key, value } + pub fn new(self_type: Type, key: Type, value: Type, ci: &ComponentInterface) -> Self { + let key_code = key.as_codetype(ci); + let value_code = value.as_codetype(ci); + Self { self_type, key, value, key_code, value_code } } fn key(&self) -> &Type { @@ -260,16 +267,12 @@ impl MapCodeType { impl CodeType for MapCodeType { fn type_label(&self) -> String { - format!( - "Map<{}, {}>", - DartCodeOracle::find(self.key()).type_label(), - DartCodeOracle::find(self.value()).type_label() - ) + format!("Map<{}, {}>", self.key_code.type_label(), self.value_code.type_label()) } fn canonical_name(&self) -> String { - let key = DartCodeOracle::find(self.key()).canonical_name(); - let val = DartCodeOracle::find(self.value()).canonical_name(); + let key = self.key_code.canonical_name(); + let val = self.value_code.canonical_name(); format!("Map{}To{}", key, val) } } @@ -278,8 +281,8 @@ impl Renderable for MapCodeType { fn render_type_helper(&self, type_helper: &dyn TypeHelperRenderer) -> dart::Tokens { type_helper.include_once_check(&self.ffi_converter_name(), &self.self_type); - let key_codetype = DartCodeOracle::find(self.key()); - let val_codetype = DartCodeOracle::find(self.value()); + let key_codetype = &self.key_code; + let val_codetype = &self.value_code; type_helper.include_once_check(&key_codetype.canonical_name(), self.key()); type_helper.include_once_check(&val_codetype.canonical_name(), self.value()); diff --git a/uniffi/dart/src/gen/custom.rs b/uniffi/dart/src/gen/custom.rs index 5e9237e9..b148b243 100644 --- a/uniffi/dart/src/gen/custom.rs +++ b/uniffi/dart/src/gen/custom.rs @@ -10,17 +10,37 @@ pub struct CustomCodeType { name: String, module_path: String, builtin: Box, + namespace: Option, } impl CustomCodeType { - pub fn new(name: String, module_path: String, builtin: Box) -> Self { - CustomCodeType { name, module_path, builtin } + pub fn new( + name: String, + module_path: String, + builtin: Box, + namespace: Option, + ) -> Self { + CustomCodeType { name, module_path, builtin, namespace } } } impl CodeType for CustomCodeType { fn type_label(&self) -> String { - DartCodeOracle::class_name(&self.name) + DartCodeOracle::qualify(self.namespace.as_deref(), DartCodeOracle::class_name(&self.name)) + } + + fn canonical_name(&self) -> String { + DartCodeOracle::qualify_canonical( + self.namespace.as_deref(), + DartCodeOracle::class_name(&self.name), + ) + } + + fn ffi_converter_name(&self) -> String { + DartCodeOracle::qualify( + self.namespace.as_deref(), + format!("FfiConverter{}", DartCodeOracle::class_name(&self.name)), + ) } } @@ -37,11 +57,16 @@ impl AsType for CustomCodeType { impl Renderable for CustomCodeType { fn render_type_helper(&self, type_helper: &dyn TypeHelperRenderer) -> dart::Tokens { type_helper.include_once_check(&self.name, &self.as_type()); + // Another crate's library declares its own typedefs. + if self.namespace.is_some() { + return quote!(); + } let ffi_converter_name = &self.ffi_converter_name(); let type_name = &self.type_label(); - let builtin_ffi_converter_name = &(*self.builtin).as_codetype().ffi_converter_name(); - let builtin_name = DartCodeOracle::dart_type_label(Some(&*self.builtin)); + let ci = type_helper.get_ci(); + let builtin_ffi_converter_name = &(*self.builtin).as_codetype(ci).ffi_converter_name(); + let builtin_name = DartCodeOracle::dart_type_label(Some(&*self.builtin), ci); quote! { typedef $(type_name) = $(builtin_name); diff --git a/uniffi/dart/src/gen/defaults.rs b/uniffi/dart/src/gen/defaults.rs index 7b77fd16..0340b842 100644 --- a/uniffi/dart/src/gen/defaults.rs +++ b/uniffi/dart/src/gen/defaults.rs @@ -1,30 +1,40 @@ use genco::prelude::*; use uniffi_bindgen::interface::{Argument, AsType, DefaultValue, Type}; -use super::oracle::DartCodeOracle; +use uniffi_bindgen::ComponentInterface; + +use super::oracle::{AsCodeType, DartCodeOracle}; use super::primitives::render_interface_default_value; -fn enum_name_from_type(ty: &Type) -> Option { +fn enum_name_from_type(ty: &Type, ci: &ComponentInterface) -> Option { match ty { - Type::Enum { name, .. } => Some(DartCodeOracle::class_name(name)), - Type::Optional { inner_type } => enum_name_from_type(inner_type), + Type::Enum { .. } => Some(ty.as_codetype(ci).type_label()), + Type::Optional { inner_type } => enum_name_from_type(inner_type, ci), _ => None, } } -pub(crate) fn render_default_value(default_value: &DefaultValue, ty: &Type) -> Option { +pub(crate) fn render_default_value( + default_value: &DefaultValue, + ty: &Type, + ci: &ComponentInterface, +) -> Option { render_interface_default_value(default_value, ty, |ty, variant| { - let enum_name = enum_name_from_type(ty)?; + let enum_name = enum_name_from_type(ty, ci)?; let variant_name = DartCodeOracle::enum_variant_name(variant); Some(format!("{enum_name}.{variant_name}")) }) } -pub(crate) fn render_argument_param(arg: &Argument, type_tokens: dart::Tokens) -> dart::Tokens { +pub(crate) fn render_argument_param( + arg: &Argument, + type_tokens: dart::Tokens, + ci: &ComponentInterface, +) -> dart::Tokens { let name = DartCodeOracle::var_name(arg.name()); if let Some(default_value) = arg.default_value() { - if let Some(default_expr) = render_default_value(default_value, &arg.as_type()) { + if let Some(default_expr) = render_default_value(default_value, &arg.as_type(), ci) { quote!($type_tokens $name = $(default_expr)) } else { quote!(required $type_tokens $name) diff --git a/uniffi/dart/src/gen/enums.rs b/uniffi/dart/src/gen/enums.rs index 89149ea0..0ebcd176 100644 --- a/uniffi/dart/src/gen/enums.rs +++ b/uniffi/dart/src/gen/enums.rs @@ -1,6 +1,7 @@ use genco::prelude::*; use heck::ToLowerCamelCase; use uniffi_bindgen::interface::{AsType, Enum, Field, Literal, Type}; +use uniffi_bindgen::ComponentInterface; use super::oracle::{AsCodeType, DartCodeOracle}; use super::render::{AsRenderable, Renderable, TypeHelperRenderer}; @@ -9,21 +10,25 @@ use crate::gen::CodeType; #[derive(Debug)] pub struct EnumCodeType { id: String, + namespace: Option, } impl EnumCodeType { - pub fn new(id: String) -> Self { - Self { id } + pub fn new(id: String, namespace: Option) -> Self { + Self { id, namespace } } } impl CodeType for EnumCodeType { fn type_label(&self) -> String { - DartCodeOracle::class_name(&self.id) + DartCodeOracle::qualify(self.namespace.as_deref(), DartCodeOracle::class_name(&self.id)) } fn canonical_name(&self) -> String { - DartCodeOracle::class_name(&self.id) + DartCodeOracle::qualify_canonical( + self.namespace.as_deref(), + DartCodeOracle::class_name(&self.id), + ) } fn literal(&self, literal: &Literal) -> String { @@ -35,14 +40,18 @@ impl CodeType for EnumCodeType { } fn ffi_converter_name(&self) -> String { - format!("FfiConverter{}", DartCodeOracle::class_name(&self.id)) + DartCodeOracle::qualify( + self.namespace.as_deref(), + format!("FfiConverter{}", DartCodeOracle::class_name(&self.id)), + ) } } impl Renderable for EnumCodeType { fn render_type_helper(&self, type_helper: &dyn TypeHelperRenderer) -> dart::Tokens { let canonical = self.canonical_name(); - if type_helper.check(&canonical) { + // Another crate's library declares its own helpers. + if self.namespace.is_some() || type_helper.check(&canonical) { quote!() } else if let Some(enum_) = type_helper.get_enum(&self.id) { generate_enum(enum_, type_helper) @@ -54,9 +63,9 @@ impl Renderable for EnumCodeType { pub fn generate_enum(obj: &Enum, type_helper: &dyn TypeHelperRenderer) -> dart::Tokens { let dart_cls_name = &DartCodeOracle::class_name(obj.name()); - let ffi_converter_name = &obj.as_codetype().ffi_converter_name(); + let ffi_converter_name = &obj.as_codetype(type_helper.get_ci()).ffi_converter_name(); if obj.is_flat() { - let is_error_enum = type_helper.get_ci().is_name_used_as_error(obj.name()); + let is_error_enum = type_helper.get_ci().is_used_as_error(obj); let implements_exception = if is_error_enum { quote!( implements Exception) } else { quote!() }; @@ -137,21 +146,27 @@ pub fn generate_enum(obj: &Enum, type_helper: &dyn TypeHelperRenderer) -> dart:: fn field_type(field: &Field, type_helper: &dyn TypeHelperRenderer) -> String { field .as_type() - .as_renderable() + .as_renderable(type_helper.get_ci()) .render_type(&field.as_type(), type_helper) .to_string() .expect("Could not stringify type") } - fn field_ffi_converter_name(field: &Field) -> String { - field.as_type().as_codetype().ffi_converter_name() + fn field_ffi_converter_name(field: &Field, ci: &ComponentInterface) -> String { + field.as_type().as_codetype(ci).ffi_converter_name() } + // Looked up in the crate that defines the enum: a local enum may share its name. fn is_flat_enum(field: &Field, type_helper: &dyn TypeHelperRenderer) -> bool { - if let Type::Enum { name, .. } = &field.as_type() { - if let Some(enum_def) = type_helper.get_enum(name) { - return enum_def.is_flat(); - } - } - false + let ty = field.as_type(); + let Type::Enum { name, module_path } = &ty else { + return false; + }; + let ci = type_helper.get_ci(); + let defining = if ci.is_external(&ty) { + ci.find_component_interface(module_path) + } else { + Some(ci) + }; + defining.and_then(|ci| ci.get_enum_definition(name)).is_some_and(|e| e.is_flat()) } // A data-carrying variant's Dart class is named `{Variant}{Enum}`, which @@ -200,8 +215,8 @@ pub fn generate_enum(obj: &Enum, type_helper: &dyn TypeHelperRenderer) -> dart:: let display_to_string: Option = obj.uniffi_trait_methods().display_fmt.map(|fmt| { let ret = fmt.return_type().expect("Display returns a string"); - type_helper.include_once_check(&ret.as_codetype().canonical_name(), ret); - let lifter = ret.as_codetype().lift(); + type_helper.include_once_check(&ret.as_codetype(type_helper.get_ci()).canonical_name(), ret); + let lifter = ret.as_codetype(type_helper.get_ci()).lift(); quote! { @override String toString() { @@ -213,11 +228,14 @@ pub fn generate_enum(obj: &Enum, type_helper: &dyn TypeHelperRenderer) -> dart:: } }); let variant_to_string = - display_to_string.is_none() && type_helper.get_ci().is_name_used_as_error(obj.name()); + display_to_string.is_none() && type_helper.get_ci().is_used_as_error(obj); for (index, variant_obj) in obj.variants().iter().enumerate() { for f in variant_obj.fields() { - type_helper.include_once_check(&f.as_codetype().canonical_name(), &f.as_type()); + type_helper.include_once_check( + &f.as_codetype(type_helper.get_ci()).canonical_name(), + &f.as_type(), + ); } let variant_dart_cls_name = &variant_class_name(variant_obj.name()); @@ -249,12 +267,12 @@ pub fn generate_enum(obj: &Enum, type_helper: &dyn TypeHelperRenderer) -> dart:: // Handle flat enums specially - they serialize as int32 (4 bytes) quote!( final $(field_name(field, i))_int = buf.buffer.asByteData(new_offset).getInt32(0); - final $(field_name(field, i)) = $(field_ffi_converter_name(field)).lift(toRustBuffer(createUint8ListFromInt($(field_name(field, i))_int))); + final $(field_name(field, i)) = $(field_ffi_converter_name(field, type_helper.get_ci())).lift(toRustBuffer(createUint8ListFromInt($(field_name(field, i))_int))); new_offset += 4; ) } else { quote!( - final $(field_name(field, i))_lifted = $(field_ffi_converter_name(field)).read(Uint8List.view(buf.buffer, new_offset)); + final $(field_name(field, i))_lifted = $(field_ffi_converter_name(field, type_helper.get_ci())).read(Uint8List.view(buf.buffer, new_offset)); final $(field_name(field, i)) = $(field_name(field, i))_lifted.value; new_offset += $(field_name(field, i))_lifted.bytesRead; ) @@ -266,7 +284,7 @@ pub fn generate_enum(obj: &Enum, type_helper: &dyn TypeHelperRenderer) -> dart:: if is_flat_enum(field, type_helper) { quote!(4 + ) // Flat enums are 4 bytes (int32) } else { - quote!($(field_ffi_converter_name(field)).allocationSize($(field_name(field, i))) + ) + quote!($(field_ffi_converter_name(field, type_helper.get_ci())).allocationSize($(field_name(field, i))) + ) } }).collect(); @@ -275,14 +293,14 @@ pub fn generate_enum(obj: &Enum, type_helper: &dyn TypeHelperRenderer) -> dart:: if is_flat_enum(field, type_helper) { // Handle flat enums specially - lower to RustBuffer and extract int32 quote!( - final $(field_name(field, i))_buffer = $(field_ffi_converter_name(field)).lower($(field_name(field, i))); + final $(field_name(field, i))_buffer = $(field_ffi_converter_name(field, type_helper.get_ci())).lower($(field_name(field, i))); final $(field_name(field, i))_int = $(field_name(field, i))_buffer.asUint8List().buffer.asByteData().getInt32(0); buf.buffer.asByteData(new_offset).setInt32(0, $(field_name(field, i))_int); new_offset += 4; ) } else { quote!( - new_offset += $(field_ffi_converter_name(field)).write($(field_name(field, i)), Uint8List.view(buf.buffer, new_offset)); + new_offset += $(field_ffi_converter_name(field, type_helper.get_ci())).write($(field_name(field, i)), Uint8List.view(buf.buffer, new_offset)); ) } }).collect(); @@ -364,7 +382,7 @@ pub fn generate_enum(obj: &Enum, type_helper: &dyn TypeHelperRenderer) -> dart:: }); } - let is_error_enum = type_helper.get_ci().is_name_used_as_error(obj.name()); + let is_error_enum = type_helper.get_ci().is_used_as_error(obj); let implements_exception = if is_error_enum { quote!( implements Exception) } else { quote!() }; diff --git a/uniffi/dart/src/gen/functions.rs b/uniffi/dart/src/gen/functions.rs index 0abead9e..3b8fe3a3 100644 --- a/uniffi/dart/src/gen/functions.rs +++ b/uniffi/dart/src/gen/functions.rs @@ -1,5 +1,4 @@ use genco::prelude::*; -use heck::ToLowerCamelCase; use uniffi_bindgen::interface::{AsType, Function}; use super::defaults::render_argument_param; @@ -15,22 +14,24 @@ pub fn generate_function(func: &Function, type_helper: &dyn TypeHelperRenderer) quote!({$(for arg in &func.arguments() => $(render_argument_param( arg, - arg.as_renderable().render_type(&arg.as_type(), type_helper) + arg.as_renderable(type_helper.get_ci()).render_type(&arg.as_type(), type_helper), + type_helper.get_ci() )), )}) }; let (ret, lifter) = if let Some(ret) = func.return_type() { - (ret.as_renderable().render_type(ret, type_helper), quote!($(ret.as_codetype().lift()))) + ( + ret.as_renderable(type_helper.get_ci()).render_type(ret, type_helper), + quote!($(ret.as_codetype(type_helper.get_ci()).lift())), + ) } else { (quote!(void), quote!((_) {})) }; // Check if function can throw errors let error_handler = if let Some(error_type) = func.throws_type() { - let error_name = DartCodeOracle::class_name(error_type.name().unwrap_or("UnknownError")); - // Use the consistent Exception naming for error handlers - let handler_name = format!("{}ErrorHandler", error_name.to_lower_camel_case()); + let handler_name = DartCodeOracle::error_handler_name(error_type, type_helper.get_ci()); quote!($(handler_name)) } else { quote!(null) @@ -54,7 +55,7 @@ pub fn generate_function(func: &Function, type_helper: &dyn TypeHelperRenderer) UniffiRustFuture<$ret> $(DartCodeOracle::fn_name(func.name()))($args) { return uniffiRustCallAsync( () => $(func.ffi_func().name())( - $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) + $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg, type_helper.get_ci())),) ), $(DartCodeOracle::async_poll(func, type_helper.get_ci())), $(DartCodeOracle::async_complete(func, type_helper.get_ci())), @@ -70,7 +71,7 @@ pub fn generate_function(func: &Function, type_helper: &dyn TypeHelperRenderer) $ret $(DartCodeOracle::fn_name(func.name()))($args) { return rustCall((status) { $(func.ffi_func().name())( - $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) status + $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg, type_helper.get_ci())),) status ); }, $error_handler); } @@ -80,7 +81,7 @@ pub fn generate_function(func: &Function, type_helper: &dyn TypeHelperRenderer) $ret $(DartCodeOracle::fn_name(func.name()))($args) { return rustCallWithLifter( (status) => $(func.ffi_func().name())( - $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) status + $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg, type_helper.get_ci())),) status ), $lifter, $error_handler diff --git a/uniffi/dart/src/gen/mod.rs b/uniffi/dart/src/gen/mod.rs index 9ca038dd..236d9da8 100644 --- a/uniffi/dart/src/gen/mod.rs +++ b/uniffi/dart/src/gen/mod.rs @@ -276,7 +276,7 @@ impl BindingGenerator for DartBindingGenerator { } // The shared runtime goes out once for the whole generation, ahead of the - // per-component files that import it. The first component supplies the + // per-component files that import it. One component supplies the // `rustbuffer_*` symbols and asset id for all of them: only library mode // generates more than one component, every component there links into one // cdylib, and UniFFI gives each the same cdylib name. If a configuration @@ -298,7 +298,10 @@ impl BindingGenerator for DartBindingGenerator { } } - if let Some(Component { ci, config, .. }) = components.first() { + // Components arrive in no particular order; take the runtime from the same one every run. + if let Some(Component { ci, config, .. }) = + components.iter().min_by_key(|c| c.ci.crate_name()) + { let asset_id = full_asset_id(config); let runtime = quote! { $("// ignore_for_file: unused_import")$['\n'] diff --git a/uniffi/dart/src/gen/objects.rs b/uniffi/dart/src/gen/objects.rs index d331b918..82da4906 100644 --- a/uniffi/dart/src/gen/objects.rs +++ b/uniffi/dart/src/gen/objects.rs @@ -20,21 +20,25 @@ use crate::gen::CodeType; pub struct ObjectCodeType { id: String, imp: ObjectImpl, + namespace: Option, } impl ObjectCodeType { - pub fn new(id: String, imp: ObjectImpl) -> Self { - Self { id, imp } + pub fn new(id: String, imp: ObjectImpl, namespace: Option) -> Self { + Self { id, imp, namespace } } } impl CodeType for ObjectCodeType { fn type_label(&self) -> String { - DartCodeOracle::class_name(&self.id) + DartCodeOracle::qualify(self.namespace.as_deref(), DartCodeOracle::class_name(&self.id)) } fn canonical_name(&self) -> String { - self.type_label() + DartCodeOracle::qualify_canonical( + self.namespace.as_deref(), + DartCodeOracle::class_name(&self.id), + ) } fn literal(&self, _literal: &Literal) -> String { @@ -42,12 +46,13 @@ impl CodeType for ObjectCodeType { } fn ffi_converter_name(&self) -> String { - match self.imp { + let class_name = DartCodeOracle::class_name(&self.id); + let name = match self.imp { // The converter lives in its own class rather than on the object itself, so // that a Rust method named `read`/`write`/`lower` cannot collide with the // converter's statics (Dart rejects a static and an instance member sharing // a name). Matches how records, enums and callback interfaces are emitted. - ObjectImpl::Struct => format!("FfiConverter{}", self.canonical_name()), + ObjectImpl::Struct => format!("FfiConverter{class_name}"), // Foreign-implementable traits (with_foreign / callback_interface) are // lowered through a callback-interface FfiConverter. Mirrors // uniffi's `ObjectImpl::has_callback_interface()` (Both | ForeignOnly). @@ -55,16 +60,16 @@ impl CodeType for ObjectCodeType { format!("FfiConverterCallbackInterface{}", self.id) } // A pure Rust-only trait object uses its own sibling FfiConverter class. - ObjectImpl::Trait(TraitKind::RustOnly) => { - format!("FfiConverter{}", self.canonical_name()) - } - } + ObjectImpl::Trait(TraitKind::RustOnly) => format!("FfiConverter{class_name}"), + }; + DartCodeOracle::qualify(self.namespace.as_deref(), name) } } impl Renderable for ObjectCodeType { fn render_type_helper(&self, type_helper: &dyn TypeHelperRenderer) -> dart::Tokens { - if type_helper.check(&self.id) { + // Another crate's library declares its own helpers. + if self.namespace.is_some() || type_helper.check(&self.id) { quote!() } else if let Some(obj) = type_helper.get_object(&self.id) { generate_object(obj, type_helper) @@ -79,7 +84,7 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da if obj.has_callback_interface() { let interface = generate_callback_interface( obj.name(), - &obj.as_codetype().ffi_converter_name(), + &obj.as_codetype(type_helper.get_ci()).ffi_converter_name(), &obj.methods(), type_helper, Some(&format!("_{}Impl", DartCodeOracle::class_name(obj.name()))), @@ -113,7 +118,7 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da } let cls_name = &DartCodeOracle::class_name(obj.name()); - let ffi_converter_name = &obj.as_codetype().ffi_converter_name(); + let ffi_converter_name = &obj.as_codetype(type_helper.get_ci()).ffi_converter_name(); let interface_name = DartCodeOracle::object_interface_name(type_helper.get_ci(), obj); let interface_definition = generate_object_interface(obj, &interface_name, type_helper); let finalizer_cls_name = &format!("{cls_name}Finalizer"); @@ -140,10 +145,7 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da // Check if function can throw errors let error_handler = if let Some(error_type) = constructor.throws_type() { - let error_name = - DartCodeOracle::class_name(error_type.name().unwrap_or("UnknownError")); - // Use the consistent Exception naming for error handlers - let handler_name = format!("{}ErrorHandler", error_name.to_lower_camel_case()); + let handler_name = DartCodeOracle::error_handler_name(error_type, type_helper.get_ci()); quote!($(handler_name)) } else { quote!(null) @@ -155,18 +157,22 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da quote!({$(for arg in constructor.arguments() => $(render_argument_param( arg, - quote!($(DartCodeOracle::dart_type_label(Some(&arg.as_type())))) + quote!($(DartCodeOracle::dart_type_label(Some(&arg.as_type()), type_helper.get_ci()))), + type_helper.get_ci() )), )}) }; let ffi_call_args = quote!($(for arg in constructor.arguments() => - $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) + $(DartCodeOracle::lower_arg_with_callback_handling(arg, type_helper.get_ci())),) ); // Ensure argument types are included for arg in constructor.arguments() { - type_helper.include_once_check(&arg.as_codetype().canonical_name(), &arg.as_type()); + type_helper.include_once_check( + &arg.as_codetype(type_helper.get_ci()).canonical_name(), + &arg.as_type(), + ); } if constructor.is_async() { @@ -201,7 +207,7 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da } // For interface objects that are used as error types, generate error handlers - let is_error_interface = type_helper.get_ci().is_name_used_as_error(obj.name()); + let is_error_interface = type_helper.get_ci().is_used_as_error(obj); let error_handler_class = if is_error_interface { // Generate error handlers for specific error interfaces @@ -406,21 +412,24 @@ fn generate_callback_trait_rust_method( .arguments() .iter() .map(|arg| { - let arg_type = arg.as_renderable().render_type(&arg.as_type(), type_helper); + let arg_type = + arg.as_renderable(type_helper.get_ci()).render_type(&arg.as_type(), type_helper); let arg_name = DartCodeOracle::var_name(arg.name()); quote!($arg_type $arg_name) }) .collect::>(); let (ret, lifter) = if let Some(ret) = method.return_type() { - (ret.as_renderable().render_type(ret, type_helper), quote!($(ret.as_codetype().lift()))) + ( + ret.as_renderable(type_helper.get_ci()).render_type(ret, type_helper), + quote!($(ret.as_codetype(type_helper.get_ci()).lift())), + ) } else { (quote!(void), quote!((_) {})) }; let error_handler = if let Some(error_type) = method.throws_type() { - let error_name = DartCodeOracle::class_name(error_type.name().unwrap_or("UnknownError")); - let handler_name = format!("{}ErrorHandler", error_name.to_lower_camel_case()); + let handler_name = DartCodeOracle::error_handler_name(error_type, type_helper.get_ci()); quote!($(handler_name)) } else { quote!(null) @@ -444,7 +453,7 @@ fn generate_callback_trait_rust_method( return uniffiRustCallAsync( () => $(method.ffi_func().name())( uniffiClonePointer(), - $(for arg in &method.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) + $(for arg in &method.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg, type_helper.get_ci())),) ), $(DartCodeOracle::async_poll(method, type_helper.get_ci())), $(DartCodeOracle::async_complete(method, type_helper.get_ci())), @@ -462,7 +471,7 @@ fn generate_callback_trait_rust_method( return rustCall((status) { $(method.ffi_func().name())( uniffiClonePointer(), - $(for arg in &method.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) + $(for arg in &method.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg, type_helper.get_ci())),) status ); }, $error_handler); @@ -475,7 +484,7 @@ fn generate_callback_trait_rust_method( return rustCallWithLifter( (status) => $(method.ffi_func().name())( uniffiClonePointer(), - $(for arg in &method.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) + $(for arg in &method.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg, type_helper.get_ci())),) status ), $lifter, @@ -495,22 +504,24 @@ pub fn generate_method(func: &Method, type_helper: &dyn TypeHelperRenderer) -> d quote!({$(for arg in &func.arguments() => $(render_argument_param( arg, - arg.as_renderable().render_type(&arg.as_type(), type_helper) + arg.as_renderable(type_helper.get_ci()).render_type(&arg.as_type(), type_helper), + type_helper.get_ci() )), )}) }; let (ret, lifter) = if let Some(ret) = func.return_type() { - (ret.as_renderable().render_type(ret, type_helper), quote!($(ret.as_codetype().lift()))) + ( + ret.as_renderable(type_helper.get_ci()).render_type(ret, type_helper), + quote!($(ret.as_codetype(type_helper.get_ci()).lift())), + ) } else { (quote!(void), quote!((_) {})) }; // Check if function can throw errors let error_handler = if let Some(error_type) = func.throws_type() { - let error_name = DartCodeOracle::class_name(error_type.name().unwrap_or("UnknownError")); - // Use the consistent Exception naming for error handlers - let handler_name = format!("{}ErrorHandler", error_name.to_lower_camel_case()); + let handler_name = DartCodeOracle::error_handler_name(error_type, type_helper.get_ci()); quote!($(handler_name)) } else { quote!(null) @@ -534,7 +545,7 @@ pub fn generate_method(func: &Method, type_helper: &dyn TypeHelperRenderer) -> d return uniffiRustCallAsync( () => $(func.ffi_func().name())( uniffiClonePointer(), - $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) + $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg, type_helper.get_ci())),) ), $(DartCodeOracle::async_poll(func, type_helper.get_ci())), $(DartCodeOracle::async_complete(func, type_helper.get_ci())), @@ -552,7 +563,7 @@ pub fn generate_method(func: &Method, type_helper: &dyn TypeHelperRenderer) -> d return rustCall((status) { $(func.ffi_func().name())( uniffiClonePointer(), - $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) status + $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg, type_helper.get_ci())),) status ); }, $error_handler); } @@ -563,7 +574,7 @@ pub fn generate_method(func: &Method, type_helper: &dyn TypeHelperRenderer) -> d return rustCallWithLifter( (status) => $(func.ffi_func().name())( uniffiClonePointer(), - $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) status + $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg, type_helper.get_ci())),) status ), $lifter, $error_handler @@ -659,8 +670,7 @@ fn trait_method_call( let ffi_name = method.ffi_func().name(); let error_handler = if let Some(error_type) = method.throws_type() { - let error_name = DartCodeOracle::class_name(error_type.name().unwrap_or("UnknownError")); - let handler_name = format!("{}ErrorHandler", error_name.to_lower_camel_case()); + let handler_name = DartCodeOracle::error_handler_name(error_type, type_helper.get_ci()); quote!($(handler_name)) } else { quote!(null) @@ -668,13 +678,21 @@ fn trait_method_call( let mut lowered_args = Vec::new(); for (arg, expr) in method.arguments().into_iter().zip(arg_exprs.iter()) { - type_helper.include_once_check(&arg.as_codetype().canonical_name(), &arg.as_type()); - lowered_args.push(DartCodeOracle::type_lower_fn(&arg.as_type(), expr.clone())); + type_helper.include_once_check( + &arg.as_codetype(type_helper.get_ci()).canonical_name(), + &arg.as_type(), + ); + lowered_args.push(DartCodeOracle::type_lower_fn( + &arg.as_type(), + expr.clone(), + type_helper.get_ci(), + )); } if let Some(ret) = method.return_type() { - type_helper.include_once_check(&ret.as_codetype().canonical_name(), ret); - let lifter = quote!($(ret.as_codetype().lift())); + type_helper + .include_once_check(&ret.as_codetype(type_helper.get_ci()).canonical_name(), ret); + let lifter = quote!($(ret.as_codetype(type_helper.get_ci()).lift())); quote!( rustCallWithLifter( (status) => $ffi_name( @@ -703,7 +721,7 @@ fn generate_trait_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> type_helper.include_once_check(obj.name(), &obj.as_type()); let cls_name = &DartCodeOracle::class_name(obj.name()); - let ffi_converter_name = &obj.as_codetype().ffi_converter_name(); + let ffi_converter_name = &obj.as_codetype(type_helper.get_ci()).ffi_converter_name(); let impl_name = format!("_{cls_name}Impl"); let finalizer_field = format!("_{cls_name}ImplFinalizer"); @@ -840,7 +858,8 @@ fn generate_interface_method( quote!({$(for arg in method.arguments() => $(render_argument_param( arg, - arg.as_renderable().render_type(&arg.as_type(), type_helper) + arg.as_renderable(type_helper.get_ci()).render_type(&arg.as_type(), type_helper), + type_helper.get_ci() )), )}) }; @@ -857,7 +876,7 @@ fn method_return_type_tokens( type_helper: &dyn TypeHelperRenderer, ) -> dart::Tokens { let base = if let Some(ret) = method.return_type() { - ret.as_renderable().render_type(ret, type_helper) + ret.as_renderable(type_helper.get_ci()).render_type(ret, type_helper) } else { quote!(void) }; diff --git a/uniffi/dart/src/gen/oracle.rs b/uniffi/dart/src/gen/oracle.rs index 5c2f52f1..1485fd8a 100644 --- a/uniffi/dart/src/gen/oracle.rs +++ b/uniffi/dart/src/gen/oracle.rs @@ -13,8 +13,46 @@ use crate::gen::{primitives, CodeType}; pub struct DartCodeOracle; impl DartCodeOracle { - pub fn find(type_: &Type) -> Box { - type_.clone().as_type().as_codetype() + /// The namespace of the crate that defines `ty`, if it is not `ci`'s crate. + pub fn external_namespace(ty: &Type, ci: &ComponentInterface) -> Option { + ci.is_external(ty).then(|| { + ci.namespace_for_type(ty).expect("external type should have a namespace").to_string() + }) + } + + /// The prefix the library of `namespace` is imported under. + /// + /// Another crate's library is always imported under a prefix, so a local type of the same + /// name cannot shadow its types. The bare namespace would itself be shadowed by a member of + /// the same name, such as a record field `net` holding `net` types; generated members are + /// lowerCamelCase, so they never contain the underscore. + pub fn import_prefix(namespace: &str) -> String { + format!("uniffi_{namespace}") + } + + /// Refers to `name` through the library of `namespace` that declares it, if it is not local. + pub fn qualify(namespace: Option<&str>, name: String) -> String { + match namespace { + Some(namespace) => format!("{}.{name}", Self::import_prefix(namespace)), + None => name, + } + } + + /// The canonical name of a type from the library of `namespace`, unique within this one. + pub fn qualify_canonical(namespace: Option<&str>, canonical: String) -> String { + match namespace { + Some(namespace) => format!("{}{canonical}", namespace.to_upper_camel_case()), + None => canonical, + } + } + + /// The handler that lifts `error_type` when a call throws it. + pub fn error_handler_name(error_type: &Type, ci: &ComponentInterface) -> String { + let error_name = Self::class_name(error_type.name().unwrap_or("UnknownError")); + Self::qualify( + Self::external_namespace(error_type, ci).as_deref(), + format!("{}ErrorHandler", error_name.to_lower_camel_case()), + ) } /// Sanitize a Dart identifier, appending an underscore if it's a reserved keyword. @@ -195,7 +233,7 @@ impl DartCodeOracle { // } // } - pub fn type_lower_fn(ty: &Type, inner: dart::Tokens) -> dart::Tokens { + pub fn type_lower_fn(ty: &Type, inner: dart::Tokens, ci: &ComponentInterface) -> dart::Tokens { match ty { Type::Int8 | Type::Int16 @@ -205,7 +243,7 @@ impl DartCodeOracle { | Type::UInt16 | Type::UInt32 | Type::UInt64 => { - quote!($(ty.as_codetype().ffi_converter_name()).lower($inner)) + quote!($(ty.as_codetype(ci).ffi_converter_name()).lower($inner)) } Type::Float32 | Type::Float64 => inner, Type::Boolean @@ -215,9 +253,9 @@ impl DartCodeOracle { | Type::Enum { .. } | Type::Optional { .. } | Type::Record { .. } => { - quote!($(ty.as_codetype().ffi_converter_name()).lower($inner)) + quote!($(ty.as_codetype(ci).ffi_converter_name()).lower($inner)) } - _ => quote!($(ty.as_codetype().ffi_converter_name()).lower($inner)), // Fallback implementation + _ => quote!($(ty.as_codetype(ci).ffi_converter_name()).lower($inner)), // Fallback implementation } } @@ -246,7 +284,7 @@ impl DartCodeOracle { } /// Get the idiomatic Dart rendering of a class name based on `Type`. - pub fn dart_type_label(type_: Option<&Type>) -> dart::Tokens { + pub fn dart_type_label(type_: Option<&Type>, ci: &ComponentInterface) -> dart::Tokens { if let Some(ret_type) = type_ { match ret_type { Type::UInt8 @@ -264,35 +302,25 @@ impl DartCodeOracle { Type::Timestamp => quote!(DateTime), Type::Duration => quote!(Duration), // Reference types - Type::Object { name, .. } => { - let class_name = &DartCodeOracle::class_name(name); - quote!($class_name) + Type::Object { .. } + | Type::Enum { .. } + | Type::Record { .. } + | Type::Custom { .. } => { + quote!($(ret_type.as_codetype(ci).type_label())) } Type::Optional { inner_type } => { - let inner = DartCodeOracle::dart_type_label(Some(inner_type)); + let inner = DartCodeOracle::dart_type_label(Some(inner_type), ci); quote!($inner?) } Type::Sequence { inner_type } => { - let inner = DartCodeOracle::dart_type_label(Some(inner_type)); + let inner = DartCodeOracle::dart_type_label(Some(inner_type), ci); quote!(List<$inner>) } Type::Map { key_type, value_type, .. } => { - let key = DartCodeOracle::dart_type_label(Some(key_type)); - let value = DartCodeOracle::dart_type_label(Some(value_type)); + let key = DartCodeOracle::dart_type_label(Some(key_type), ci); + let value = DartCodeOracle::dart_type_label(Some(value_type), ci); quote!(Map<$key, $value>) } - Type::Enum { name, .. } => { - let enum_name = &DartCodeOracle::class_name(name); - quote!($enum_name) - } - Type::Record { name, .. } => { - let rec_name = &DartCodeOracle::class_name(name); - quote!($rec_name) - } - Type::Custom { name, .. } => { - let type_name = &DartCodeOracle::class_name(name); - quote!($type_name) - } _ => quote!(dynamic), } } else { @@ -301,7 +329,10 @@ impl DartCodeOracle { } /// Get the native Dart FFI type rendering based on `Type`. - pub fn native_type_label(native_ret_type: Option<&Type>) -> dart::Tokens { + pub fn native_type_label( + native_ret_type: Option<&Type>, + ci: &ComponentInterface, + ) -> dart::Tokens { if let Some(ret_type) = native_ret_type { match ret_type { Type::UInt8 => quote!(Uint8), @@ -328,10 +359,7 @@ impl DartCodeOracle { Type::Object { .. } => quote!(Pointer), Type::Enum { .. } => quote!(Int32), Type::Record { .. } => quote!(RustBuffer), - Type::Custom { name, .. } => { - let class_name = &DartCodeOracle::class_name(name); - quote!($class_name) - } + Type::Custom { .. } => quote!($(ret_type.as_codetype(ci).type_label())), _ => quote!(Pointer), } } else { @@ -340,7 +368,10 @@ impl DartCodeOracle { } /// Get the native Dart FFI type rendering based on `Type`. - pub fn native_dart_type_label(native_ret_type: Option<&Type>) -> dart::Tokens { + pub fn native_dart_type_label( + native_ret_type: Option<&Type>, + ci: &ComponentInterface, + ) -> dart::Tokens { if let Some(ret_type) = native_ret_type { match ret_type { Type::UInt8 @@ -367,10 +398,7 @@ impl DartCodeOracle { Type::Object { .. } => quote!(Pointer), Type::Enum { .. } => quote!(int), Type::Record { .. } => quote!(RustBuffer), - Type::Custom { name, .. } => { - let type_name = &DartCodeOracle::class_name(name); - quote!($type_name) - } + Type::Custom { .. } => quote!($(ret_type.as_codetype(ci).type_label())), _ => quote!(dynamic), } } else { @@ -379,8 +407,12 @@ impl DartCodeOracle { } // Method to get the appropriate callback parameter type - pub fn callback_param_type(arg_type: &Type, arg_name: &str) -> dart::Tokens { - let type_label = DartCodeOracle::native_dart_type_label(Some(arg_type)); + pub fn callback_param_type( + arg_type: &Type, + arg_name: &str, + ci: &ComponentInterface, + ) -> dart::Tokens { + let type_label = DartCodeOracle::native_dart_type_label(Some(arg_type), ci); quote!($type_label $arg_name) } @@ -389,6 +421,7 @@ impl DartCodeOracle { ret_type: &Type, method_name: &str, args: Vec, + ci: &ComponentInterface, ) -> dart::Tokens { match ret_type { Type::Boolean => { @@ -407,7 +440,7 @@ impl DartCodeOracle { | Type::Int32 | Type::Int64 => { // For primitive integer return values - let lowered = ret_type.as_codetype().ffi_converter_name(); + let lowered = ret_type.as_codetype(ci).ffi_converter_name(); quote!( final result = obj.$method_name($(for arg in &args => $arg,)); outReturn.value = $lowered.lower(result); @@ -423,7 +456,7 @@ impl DartCodeOracle { Type::Optional { .. } => { // For optional return values, use the Optional FfiConverter which already // handles the discriminant byte encoding correctly. - let ffi_converter = ret_type.as_codetype().ffi_converter_name(); + let ffi_converter = ret_type.as_codetype(ci).ffi_converter_name(); quote!( final result = obj.$method_name($(for arg in &args => $arg,)); if (result == null) { @@ -443,7 +476,7 @@ impl DartCodeOracle { ) } Type::Object { .. } => { - let lowered = ret_type.as_codetype().ffi_converter_name(); + let lowered = ret_type.as_codetype(ci).ffi_converter_name(); quote!( final result = obj.$method_name($(for arg in &args => $arg,)); outReturn.value = $lowered.lower(result); @@ -458,7 +491,7 @@ impl DartCodeOracle { ) } else { // For other sequence types - let lowered = ret_type.as_codetype().ffi_converter_name(); + let lowered = ret_type.as_codetype(ci).ffi_converter_name(); quote!( final result = obj.$method_name($(for arg in &args => $arg,)); outReturn.ref = $lowered.lower(result); @@ -467,7 +500,7 @@ impl DartCodeOracle { } _ => { // For other return types - let lowered = ret_type.as_codetype().ffi_converter_name(); + let lowered = ret_type.as_codetype(ci).ffi_converter_name(); quote!( final result = obj.$method_name($(for arg in &args => $arg,)); outReturn.ref = $lowered.lower(result); @@ -512,12 +545,13 @@ impl DartCodeOracle { arg_type: &Type, arg_name: &str, arg_idx: usize, + ci: &ComponentInterface, ) -> dart::Tokens { // Use index-based variable names to avoid conflicts if let Type::Boolean = arg_type { quote!(final bool_arg$(arg_idx) = $arg_name == 1;) } else if let Type::Enum { .. } = arg_type { - let converter = arg_type.as_codetype().ffi_converter_name(); + let converter = arg_type.as_codetype(ci).ffi_converter_name(); quote!(final arg$(arg_idx) = $converter.read(createUint8ListFromInt($arg_name)).value;) } else if let Type::Bytes = arg_type { quote!(final arg$(arg_idx) = FfiConverterUint8List.lift($arg_name);) @@ -527,18 +561,18 @@ impl DartCodeOracle { if let Type::String = **inner_type { quote!(final arg$(arg_idx) = FfiConverterOptionalString.lift($arg_name);) } else { - let converter = arg_type.as_codetype().ffi_converter_name(); + let converter = arg_type.as_codetype(ci).ffi_converter_name(); quote!(final arg$(arg_idx) = $converter.lift($arg_name);) } } else if let Type::Sequence { inner_type } = arg_type { if let Type::Int32 = **inner_type { quote!(final arg$(arg_idx) = FfiConverterSequenceInt32.lift($arg_name);) } else { - let converter = arg_type.as_codetype().ffi_converter_name(); + let converter = arg_type.as_codetype(ci).ffi_converter_name(); quote!(final arg$(arg_idx) = $converter.lift($arg_name);) } } else { - let converter = arg_type.as_codetype().ffi_converter_name(); + let converter = arg_type.as_codetype(ci).ffi_converter_name(); quote!(final arg$(arg_idx) = $converter.lift($arg_name);) } } @@ -553,7 +587,10 @@ impl DartCodeOracle { } /// Lower argument with special handling for callback traits - pub fn lower_arg_with_callback_handling(arg: &Argument) -> dart::Tokens { + pub fn lower_arg_with_callback_handling( + arg: &Argument, + ci: &ComponentInterface, + ) -> dart::Tokens { // Borrowed `&[u8]` / `[ByRef] bytes` arguments take the `ForeignBytes` // FFI path, not the owned `RustBuffer` path. uniffi 0.32 selects // `FfiType::ForeignBytes` for these (see `Argument::is_borrowed_bytes`), @@ -566,7 +603,8 @@ impl DartCodeOracle { if arg.is_borrowed_bytes() { return quote!(lowerForeignBytes($(Self::var_name(arg.name())))); } - let base_lower = Self::type_lower_fn(&arg.as_type(), quote!($(Self::var_name(arg.name())))); + let base_lower = + Self::type_lower_fn(&arg.as_type(), quote!($(Self::var_name(arg.name()))), ci); match arg.as_type() { Type::Object { imp: ObjectImpl::Trait(TraitKind::Both | TraitKind::ForeignOnly), @@ -670,11 +708,13 @@ pub static RESERVED_IDENTIFIERS: [&str; 63] = [ ]; pub trait AsCodeType { - fn as_codetype(&self) -> Box; + /// The code type of this type as `ci`'s library sees it. + fn as_codetype(&self, ci: &ComponentInterface) -> Box; } impl AsCodeType for T { - fn as_codetype(&self) -> Box { + fn as_codetype(&self, ci: &ComponentInterface) -> Box { + let namespace = DartCodeOracle::external_namespace(&self.as_type(), ci); match self.as_type() { Type::UInt8 => Box::new(primitives::UInt8CodeType), Type::Int8 => Box::new(primitives::Int8CodeType), @@ -690,23 +730,25 @@ impl AsCodeType for T { Type::String => Box::new(primitives::StringCodeType), Type::Duration => Box::new(primitives::DurationCodeType), Type::Bytes => Box::new(primitives::BytesCodeType), - Type::Object { name, imp, .. } => Box::new(objects::ObjectCodeType::new(name, imp)), + Type::Object { name, imp, .. } => { + Box::new(objects::ObjectCodeType::new(name, imp, namespace)) + } Type::Optional { inner_type } => { - Box::new(compounds::OptionalCodeType::new(self.as_type(), *inner_type)) + Box::new(compounds::OptionalCodeType::new(self.as_type(), *inner_type, ci)) } Type::Sequence { inner_type } => { - Box::new(compounds::SequenceCodeType::new(self.as_type(), *inner_type)) + Box::new(compounds::SequenceCodeType::new(self.as_type(), *inner_type, ci)) } Type::Map { key_type, value_type, .. } => { - Box::new(compounds::MapCodeType::new(self.as_type(), *key_type, *value_type)) - } - Type::Enum { name, .. } => Box::new(enums::EnumCodeType::new(name)), - Type::Record { name, .. } => Box::new(records::RecordCodeType::new(name)), - Type::CallbackInterface { name, .. } => { - Box::new(callback_interface::CallbackInterfaceCodeType::new(name, self.as_type())) + Box::new(compounds::MapCodeType::new(self.as_type(), *key_type, *value_type, ci)) } + Type::Enum { name, .. } => Box::new(enums::EnumCodeType::new(name, namespace)), + Type::Record { name, .. } => Box::new(records::RecordCodeType::new(name, namespace)), + Type::CallbackInterface { name, .. } => Box::new( + callback_interface::CallbackInterfaceCodeType::new(name, self.as_type(), namespace), + ), Type::Custom { name, module_path, builtin } => { - Box::new(custom::CustomCodeType::new(name, module_path, builtin)) + Box::new(custom::CustomCodeType::new(name, module_path, builtin, namespace)) } _ => todo!("As Type for Type::{:?}", self.as_type()), } diff --git a/uniffi/dart/src/gen/records.rs b/uniffi/dart/src/gen/records.rs index a43a4c91..e81e021a 100644 --- a/uniffi/dart/src/gen/records.rs +++ b/uniffi/dart/src/gen/records.rs @@ -10,24 +10,26 @@ use crate::gen::CodeType; #[derive(Debug)] pub struct RecordCodeType { id: String, - // module_path: String, + namespace: Option, } impl RecordCodeType { - pub fn new(id: String, //module_path: String - ) -> Self { - Self { id, // module_path - } + pub fn new(id: String, namespace: Option) -> Self { + Self { id, namespace } } } impl CodeType for RecordCodeType { fn type_label(&self) -> String { - DartCodeOracle::class_name(&self.id) + DartCodeOracle::qualify(self.namespace.as_deref(), DartCodeOracle::class_name(&self.id)) } fn canonical_name(&self) -> String { - self.id.to_string() + DartCodeOracle::qualify_canonical(self.namespace.as_deref(), self.id.to_string()) + } + + fn ffi_converter_name(&self) -> String { + DartCodeOracle::qualify(self.namespace.as_deref(), format!("FfiConverter{}", self.id)) } fn literal(&self, _literal: &PipelineLiteral) -> String { @@ -37,7 +39,8 @@ impl CodeType for RecordCodeType { impl Renderable for RecordCodeType { fn render_type_helper(&self, type_helper: &dyn TypeHelperRenderer) -> dart::Tokens { - if type_helper.check(&self.id) { + // Another crate's library declares its own helpers. + if self.namespace.is_some() || type_helper.check(&self.id) { quote!() } else if let Some(record_) = type_helper.get_record(&self.id) { generate_record(record_, type_helper) @@ -49,14 +52,16 @@ impl Renderable for RecordCodeType { pub fn generate_record(obj: &Record, type_helper: &dyn TypeHelperRenderer) -> dart::Tokens { let cls_name = &DartCodeOracle::class_name(obj.name()); - let ffi_conv_name = &obj.as_codetype().ffi_converter_name(); + let ffi_conv_name = &obj.as_codetype(type_helper.get_ci()).ffi_converter_name(); let constructor_params: Vec = obj .fields() .iter() .map(|field| { let name = DartCodeOracle::var_name(field.name()); if let Some(default_value) = field.default_value() { - if let Some(default_expr) = render_default_value(default_value, &field.as_type()) { + if let Some(default_expr) = + render_default_value(default_value, &field.as_type(), type_helper.get_ci()) + { quote!(this.$name = $(default_expr)) } else { // Fallback to required when a default is present but cannot be rendered safely. @@ -77,11 +82,14 @@ pub fn generate_record(obj: &Record, type_helper: &dyn TypeHelperRenderer) -> da }; for f in obj.fields() { - type_helper.include_once_check(&f.as_codetype().canonical_name(), &f.as_type()); + type_helper.include_once_check( + &f.as_codetype(type_helper.get_ci()).canonical_name(), + &f.as_type(), + ); } quote! { class $cls_name { - $(for f in obj.fields() => final $(generate_type(&f.as_type())) $(DartCodeOracle::var_name(f.name()));) + $(for f in obj.fields() => final $(generate_type(&f.as_type(), type_helper.get_ci())) $(DartCodeOracle::var_name(f.name()));) $constructor } @@ -95,7 +103,7 @@ pub fn generate_record(obj: &Record, type_helper: &dyn TypeHelperRenderer) -> da int new_offset = buf.offsetInBytes; $(for f in obj.fields() => - final $(DartCodeOracle::var_name(f.name()))_lifted = $(f.as_type().as_codetype().ffi_converter_name()).read(Uint8List.view(buf.buffer, new_offset)); + final $(DartCodeOracle::var_name(f.name()))_lifted = $(f.as_type().as_codetype(type_helper.get_ci()).ffi_converter_name()).read(Uint8List.view(buf.buffer, new_offset)); final $(DartCodeOracle::var_name(f.name())) = $(DartCodeOracle::var_name(f.name()))_lifted.value; new_offset += $(DartCodeOracle::var_name(f.name()))_lifted.bytesRead; ) @@ -105,7 +113,7 @@ pub fn generate_record(obj: &Record, type_helper: &dyn TypeHelperRenderer) -> da } static RustBuffer lower( $cls_name value) { - final total_length = $(for f in obj.fields() => $(f.as_type().as_codetype().ffi_converter_name()).allocationSize(value.$(DartCodeOracle::var_name(f.name()))) + ) 0; + final total_length = $(for f in obj.fields() => $(f.as_type().as_codetype(type_helper.get_ci()).ffi_converter_name()).allocationSize(value.$(DartCodeOracle::var_name(f.name()))) + ) 0; final buf = Uint8List(total_length); write(value, buf); return toRustBuffer(buf); @@ -115,13 +123,13 @@ pub fn generate_record(obj: &Record, type_helper: &dyn TypeHelperRenderer) -> da int new_offset = buf.offsetInBytes; $(for f in obj.fields() => - new_offset += $(f.as_type().as_codetype().ffi_converter_name()).write(value.$(DartCodeOracle::var_name(f.name())), Uint8List.view(buf.buffer, new_offset)); + new_offset += $(f.as_type().as_codetype(type_helper.get_ci()).ffi_converter_name()).write(value.$(DartCodeOracle::var_name(f.name())), Uint8List.view(buf.buffer, new_offset)); ) return new_offset - buf.offsetInBytes; } static int allocationSize($cls_name value) { - return $(for f in obj.fields() => $(f.as_type().as_codetype().ffi_converter_name()).allocationSize(value.$(DartCodeOracle::var_name(f.name()))) + ) 0; + return $(for f in obj.fields() => $(f.as_type().as_codetype(type_helper.get_ci()).ffi_converter_name()).allocationSize(value.$(DartCodeOracle::var_name(f.name()))) + ) 0; } } } diff --git a/uniffi/dart/src/gen/render/mod.rs b/uniffi/dart/src/gen/render/mod.rs index c03b09c6..f5242d4d 100644 --- a/uniffi/dart/src/gen/render/mod.rs +++ b/uniffi/dart/src/gen/render/mod.rs @@ -25,8 +25,6 @@ pub trait TypeHelperRenderer { pub trait Renderable { fn render_type(&self, ty: &Type, type_helper: &dyn TypeHelperRenderer) -> dart::Tokens { - use super::oracle::DartCodeOracle; - let type_name = match ty { Type::UInt8 | Type::Int8 @@ -40,7 +38,13 @@ pub trait Renderable { Type::String => quote!(String), Type::Boolean => quote!(bool), Type::Bytes => quote!(Uint8List), - Type::Object { name, .. } => quote!($(DartCodeOracle::class_name(name))), + Type::Object { .. } + | Type::Enum { .. } + | Type::Record { .. } + | Type::Custom { .. } + | Type::CallbackInterface { .. } => { + quote!($(ty.as_codetype(type_helper.get_ci()).type_label())) + } Type::Optional { inner_type } => quote!($(&self.render_type(inner_type, type_helper))?), Type::Sequence { inner_type } => { quote!(List<$(&self.render_type(inner_type, type_helper))>) @@ -48,15 +52,11 @@ pub trait Renderable { Type::Map { key_type, value_type } => { quote!(Map<$(&self.render_type(key_type, type_helper)), $(&self.render_type(value_type, type_helper))>) } - Type::Enum { name, .. } => quote!($(DartCodeOracle::class_name(name))), - Type::Record { name, .. } => quote!($(DartCodeOracle::class_name(name))), - Type::Custom { name, .. } => quote!($(DartCodeOracle::class_name(name))), Type::Duration => quote!(Duration), - Type::CallbackInterface { name, .. } => quote!($(DartCodeOracle::class_name(name))), _ => todo!("Type::{:?}", ty), }; - type_helper.include_once_check(&ty.as_codetype().canonical_name(), ty); + type_helper.include_once_check(&ty.as_codetype(type_helper.get_ci()).canonical_name(), ty); type_name } @@ -65,11 +65,13 @@ pub trait Renderable { } pub trait AsRenderable { - fn as_renderable(&self) -> Box; + /// The renderer of this type as `ci`'s library sees it. + fn as_renderable(&self, ci: &ComponentInterface) -> Box; } impl AsRenderable for T { - fn as_renderable(&self) -> Box { + fn as_renderable(&self, ci: &ComponentInterface) -> Box { + let namespace = super::oracle::DartCodeOracle::external_namespace(&self.as_type(), ci); match self.as_type() { Type::UInt8 => Box::new(primitives::UInt8CodeType), Type::Int8 => Box::new(primitives::Int8CodeType), @@ -85,24 +87,26 @@ impl AsRenderable for T { Type::String => Box::new(primitives::StringCodeType), Type::Duration => Box::new(primitives::DurationCodeType), Type::Bytes => Box::new(primitives::BytesCodeType), - Type::Object { name, imp, .. } => Box::new(objects::ObjectCodeType::new(name, imp)), + Type::Object { name, imp, .. } => { + Box::new(objects::ObjectCodeType::new(name, imp, namespace)) + } Type::Optional { inner_type } => { - Box::new(compounds::OptionalCodeType::new(self.as_type(), *inner_type)) + Box::new(compounds::OptionalCodeType::new(self.as_type(), *inner_type, ci)) } Type::Sequence { inner_type } => { - Box::new(compounds::SequenceCodeType::new(self.as_type(), *inner_type)) + Box::new(compounds::SequenceCodeType::new(self.as_type(), *inner_type, ci)) } Type::Map { key_type, value_type } => { - Box::new(compounds::MapCodeType::new(self.as_type(), *key_type, *value_type)) + Box::new(compounds::MapCodeType::new(self.as_type(), *key_type, *value_type, ci)) } - Type::Enum { name, .. } => Box::new(enums::EnumCodeType::new(name)), - Type::Record { name, .. } => Box::new(records::RecordCodeType::new(name)), + Type::Enum { name, .. } => Box::new(enums::EnumCodeType::new(name, namespace)), + Type::Record { name, .. } => Box::new(records::RecordCodeType::new(name, namespace)), Type::Custom { name, module_path, builtin } => { - Box::new(custom::CustomCodeType::new(name, module_path, builtin)) - } - Type::CallbackInterface { name, .. } => { - Box::new(callback_interface::CallbackInterfaceCodeType::new(name, self.as_type())) + Box::new(custom::CustomCodeType::new(name, module_path, builtin, namespace)) } + Type::CallbackInterface { name, .. } => Box::new( + callback_interface::CallbackInterfaceCodeType::new(name, self.as_type(), namespace), + ), _ => todo!("Renderable for Type::{:?}", self.as_type()), } } diff --git a/uniffi/dart/src/gen/types.rs b/uniffi/dart/src/gen/types.rs index 5a72d519..4ee8366a 100644 --- a/uniffi/dart/src/gen/types.rs +++ b/uniffi/dart/src/gen/types.rs @@ -37,7 +37,7 @@ impl<'a> TypeHelpersRenderer<'a> { } fn register_type_helper(&self, ty: &Type) { - let canonical_name = ty.as_codetype().canonical_name(); + let canonical_name = ty.as_codetype(self.ci).canonical_name(); self.include_once_check(&canonical_name, ty); let exception_safe_name = DartCodeOracle::exception_safe_name(&canonical_name); @@ -144,7 +144,8 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { $( for obj in self.ci.object_definitions() => $(objects::generate_object(obj, self))) }; - // Render all unique imports, sorted alphabetically + // Render all unique imports, sorted alphabetically. Each library is imported under a + // prefix, which every reference to its types goes through. let modules_to_import = self .ci .iter_external_types() @@ -154,7 +155,7 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { .collect::>(); let imports: dart::Tokens = quote!( $( for imp in modules_to_import { - $(format!("import \"{}.dart\"", imp)); + $(format!("import \"{imp}.dart\" as {}", DartCodeOracle::import_prefix(imp))); }) ); @@ -174,7 +175,7 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { // would collide with the re-exported one. let helpers_definitions = quote! { $(for (_, ty) in self.get_include_names().iter().filter(|(_, ty)| !matches!(ty, Type::String)) => - $(ty.as_renderable().render_type_helper(self)) ) + $(ty.as_renderable(self.ci).render_type_helper(self)) ) }; let types_helper_code = quote! { @@ -202,7 +203,7 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { } } -pub fn generate_type(ty: &Type) -> dart::Tokens { +pub fn generate_type(ty: &Type, ci: &ComponentInterface) -> dart::Tokens { match ty { Type::UInt8 | Type::UInt32 @@ -215,17 +216,16 @@ pub fn generate_type(ty: &Type) -> dart::Tokens { Type::Float32 | Type::Float64 => quote!(double), Type::String => quote!(String), Type::Bytes => quote!(Uint8List), - Type::Object { name, .. } => quote!($(DartCodeOracle::class_name(name))), + Type::Object { .. } | Type::Enum { .. } | Type::Record { .. } | Type::Custom { .. } => { + quote!($(ty.as_codetype(ci).type_label())) + } Type::Boolean => quote!(bool), - Type::Optional { inner_type } => quote!($(generate_type(inner_type))?), - Type::Sequence { inner_type } => quote!(List<$(generate_type(inner_type))>), + Type::Optional { inner_type } => quote!($(generate_type(inner_type, ci))?), + Type::Sequence { inner_type } => quote!(List<$(generate_type(inner_type, ci))>), Type::Map { key_type, value_type } => { - quote!(Map<$(generate_type(key_type)), $(generate_type(value_type))>) + quote!(Map<$(generate_type(key_type, ci)), $(generate_type(value_type, ci))>) } - Type::Enum { name, .. } => quote!($(DartCodeOracle::class_name(name))), Type::Duration => quote!(Duration), - Type::Record { name, .. } => quote!($(DartCodeOracle::class_name(name))), - Type::Custom { name, .. } => quote!($(DartCodeOracle::class_name(name))), _ => todo!("Type::{:?}", ty), } } diff --git a/uniffi/dart/src/testing.rs b/uniffi/dart/src/testing.rs index 7d0a2937..7bd758e8 100644 --- a/uniffi/dart/src/testing.rs +++ b/uniffi/dart/src/testing.rs @@ -78,6 +78,13 @@ pub fn run_test(fixture: &str, udl_path: &str, config_path: Option<&str>) -> Res run_test_with_config(fixture, udl_path, config_path, &TestConfig::from_env()) } +/// Run a test in library mode, over every crate whose scaffolding `fixture`'s cdylib holds +/// +/// Env overrides are the same as `run_test`'s. +pub fn run_library_test(fixture: &str) -> Result<()> { + run_test_impl(fixture, None, None, &TestConfig::from_env()) +} + /// Run a test with explicit configuration pub fn run_test_with_config( fixture: &str, @@ -85,7 +92,7 @@ pub fn run_test_with_config( config_path: Option<&str>, test_config: &TestConfig, ) -> Result<()> { - run_test_impl(fixture, udl_path, config_path, test_config) + run_test_impl(fixture, Some(udl_path), config_path, test_config) } /// Run a test with an explicit output directory (convenience wrapper) @@ -99,13 +106,13 @@ pub fn run_test_with_output_dir( custom_output_dir: custom_output_dir.map(|p| p.to_owned()), ..Default::default() }; - run_test_impl(fixture, udl_path, config_path, &config) + run_test_impl(fixture, Some(udl_path), config_path, &config) } -/// Test execution (core implementation) +/// Test execution (core implementation), in library mode without a UDL file fn run_test_impl( fixture: &str, - udl_path: &str, + udl_path: Option<&str>, config_path: Option<&str>, test_config: &TestConfig, ) -> Result<()> { @@ -139,7 +146,10 @@ fn run_test_impl( out_dir }; - let udl_path = Utf8Path::new(".").canonicalize_utf8()?.join(udl_path); + let udl_path = match udl_path { + Some(udl_path) => Some(Utf8Path::new(".").canonicalize_utf8()?.join(udl_path)), + None => None, + }; let config_path = if let Some(path) = config_path { Some(Utf8Path::new(".").canonicalize_utf8()?.join(path)) } else { @@ -250,13 +260,14 @@ void main(List args) async {{ )?; build_hook.flush()?; gen::generate_dart_bindings( - &udl_path, + // Library mode reads no UDL file. + udl_path.as_deref().unwrap_or(Utf8Path::new("")), config_path.as_deref(), Some(&out_dir), &test_helper.cdylib_path()?, - false, // library_mode - None, // crate_name filter (library mode only) - true, // try_format + udl_path.is_none(), // library_mode + None, // crate_name filter (library mode only) + true, // try_format )?; // Copy fixture test files to output directory diff --git a/uniffi/fixtures/namespaces/media/Cargo.toml b/uniffi/fixtures/namespaces/media/Cargo.toml new file mode 100644 index 00000000..51ce38ca --- /dev/null +++ b/uniffi/fixtures/namespaces/media/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "uniffi-fixture-namespaces-media" +edition = "2021" +version = "0.1.0" +license = "MPL-2.0" +publish = false + +[lib] +crate-type = ["lib", "cdylib"] +name = "uniffi_namespaces_media" + +[dependencies] +thiserror = "2" +uniffi = { workspace = true } +uniffi-fixture-namespaces-net = { path = "../net" } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } + +[features] +ffi-trace = ["uniffi/ffi-trace", "uniffi-fixture-namespaces-net/ffi-trace"] diff --git a/uniffi/fixtures/namespaces/media/src/lib.rs b/uniffi/fixtures/namespaces/media/src/lib.rs new file mode 100644 index 00000000..fb73797b --- /dev/null +++ b/uniffi/fixtures/namespaces/media/src/lib.rs @@ -0,0 +1,165 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! The upper of two layers: each type here shares its name with one in `net`, and holds or +//! refers to it. + +use std::{collections::HashMap, sync::Arc}; + +use uniffi_namespaces_net as net; + +uniffi::setup_scaffolding!("media"); + +#[derive(Clone, Debug, PartialEq, uniffi::Record)] +pub struct Frame { + pub raw: net::Frame, + pub keyframe: bool, +} + +#[derive(Clone, Debug, PartialEq, uniffi::Enum)] +pub enum Priority { + Net { priority: net::Priority }, + Realtime, +} + +#[derive(Debug, thiserror::Error, uniffi::Error)] +pub enum GroupError { + #[error("empty frame")] + Empty, + #[error("network: {source}")] + Net { source: net::GroupError }, +} + +#[derive(Debug, uniffi::Object)] +pub struct GroupConsumer { + group: Arc, +} + +#[uniffi::export] +impl GroupConsumer { + #[uniffi::constructor] + pub fn new(group: Arc) -> Arc { + Arc::new(Self { group }) + } + + pub fn group(&self) -> Arc { + self.group.clone() + } + + pub fn priority(&self) -> Priority { + match self.group.priority() { + net::Priority::High => Priority::Realtime, + priority => Priority::Net { priority }, + } + } + + /// Reads and decodes the next frame. + pub fn read_frame(&self) -> Result { + let raw = self + .group + .read_frame() + .map_err(|source| GroupError::Net { source })?; + decode(raw) + } + + /// Reads the next frame, throwing the `net` error as is. + pub async fn next_frame(&self) -> Result { + let raw = self.group.read_frame()?; + Ok(Frame { + keyframe: raw.payload.first() == Some(&1), + raw, + }) + } +} + +/// Wraps a `net` frame, whose first byte marks a keyframe. +#[uniffi::export] +pub fn decode(raw: net::Frame) -> Result { + match raw.payload.first() { + None => Err(GroupError::Empty), + Some(first) => Ok(Frame { + keyframe: *first == 1, + raw, + }), + } +} + +/// Unwraps a `media` frame into the `net` frame it holds. +#[uniffi::export] +pub fn encode(frame: Frame) -> net::Frame { + frame.raw +} + +// Each of these holds a `net` type and its `media` namesake in the same container, so the +// helpers generated for both containers live side by side. + +/// Decodes every `net` frame, skipping empty ones. +#[uniffi::export] +pub fn decode_all(raw: Vec) -> Vec { + raw.into_iter().filter_map(|raw| decode(raw).ok()).collect() +} + +/// The first keyframe, and the `net` frame of the last frame. +#[uniffi::export] +pub fn find_bounds(frames: Vec) -> Bounds { + Bounds { + keyframe: frames.iter().find(|frame| frame.keyframe).cloned(), + last: frames.last().map(|frame| frame.raw.clone()), + } +} + +#[derive(Clone, Debug, PartialEq, uniffi::Record)] +pub struct Bounds { + pub keyframe: Option, + pub last: Option, +} + +/// Wraps each `net` group, keyed by its sequence, with the priorities both layers give it. +#[uniffi::export] +pub fn wrap_all(groups: Vec>) -> Groups { + let mut wrapped = Groups { + groups: Vec::new(), + net: HashMap::new(), + media: HashMap::new(), + }; + for group in groups { + let sequence = group.sequence(); + wrapped.net.insert(sequence, group.priority()); + let group = GroupConsumer::new(group); + wrapped.media.insert(sequence, group.priority()); + wrapped.groups.push(group); + } + wrapped +} + +#[derive(uniffi::Record)] +pub struct Groups { + pub groups: Vec>, + pub net: HashMap, + pub media: HashMap, +} + +/// The flat counterpart of `net`'s data-carrying `Kind`. +#[derive(Clone, Copy, Debug, PartialEq, uniffi::Enum)] +pub enum Kind { + Video, + Audio, +} + +#[derive(Clone, Debug, PartialEq, uniffi::Enum)] +pub enum Track { + Net { kind: net::Kind }, + Media { kind: Kind }, +} + +/// A track for each layer's `Kind`, so variants hold both the data-carrying one and its flat +/// namesake. +#[uniffi::export] +pub fn tracks(kind: net::Kind) -> Vec { + let media = match kind { + net::Kind::Video { .. } => Kind::Video, + net::Kind::Audio => Kind::Audio, + }; + vec![Track::Net { kind }, Track::Media { kind: media }] +} diff --git a/uniffi/fixtures/namespaces/media/tests/bindings/test_namespaces.kts b/uniffi/fixtures/namespaces/media/tests/bindings/test_namespaces.kts new file mode 100644 index 00000000..93892062 --- /dev/null +++ b/uniffi/fixtures/namespaces/media/tests/bindings/test_namespaces.kts @@ -0,0 +1,71 @@ +/* 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/. */ + +import kotlinx.coroutines.runBlocking +import uniffi.media.* + +fun netGroup(sequence: ULong, priority: uniffi.net.Priority, vararg payloads: ByteArray) = + uniffi.net.GroupConsumer(sequence, priority, payloads.map { uniffi.net.Frame(it) }) + +fun bytes(vararg values: Int) = ByteArray(values.size) { values[it].toByte() } + +// The object wraps its namesake. +val inner = netGroup(3UL, uniffi.net.Priority.LOW, bytes(1, 97), bytes(0, 98)) +val group = GroupConsumer(inner) +assert(group.group().sequence() == 3UL) +assert(group.priority() == Priority.Net(uniffi.net.Priority.LOW)) + +val frame: Frame = group.readFrame() +assert(frame.keyframe) +assert(frame.raw.payload.contentEquals(bytes(1, 97))) +assert(!group.readFrame().keyframe) + +// Each layer's error, both named `GroupError`. +try { + group.readFrame() + throw RuntimeException("should have thrown") +} catch (e: GroupException.Net) { + val source = e.source + assert(source is uniffi.net.GroupException.Closed && source.code == 7U) +} +try { + decode(uniffi.net.Frame(bytes())) + throw RuntimeException("should have thrown") +} catch (e: GroupException.Empty) { +} + +// The async method throws the `net` error as is. +val realtime = GroupConsumer(netGroup(1UL, uniffi.net.Priority.HIGH, bytes(1))) +assert(realtime.priority() == Priority.Realtime) +runBlocking { + assert(realtime.nextFrame().raw.payload.contentEquals(bytes(1))) + try { + realtime.nextFrame() + throw RuntimeException("should have thrown") + } catch (e: uniffi.net.GroupException.Closed) { + assert(e.code == 7U) + } +} + +// Containers of both. +assert(encode(decode(uniffi.net.Frame(bytes(0, 122)))).payload.contentEquals(bytes(0, 122))) + +val frames: List = decodeAll(listOf( + uniffi.net.Frame(bytes(0, 97)), + uniffi.net.Frame(bytes()), + uniffi.net.Frame(bytes(1, 98)), +)) +assert(frames.map { it.keyframe } == listOf(false, true)) +val b = findBounds(frames) +assert(b.keyframe!!.raw.payload.contentEquals(frames[1].raw.payload)) +assert(b.last!!.payload.contentEquals(bytes(1, 98))) +assert(findBounds(listOf()) == Bounds(null, null)) + +val groups = wrapAll(listOf( + netGroup(1UL, uniffi.net.Priority.LOW), + netGroup(2UL, uniffi.net.Priority.HIGH), +)) +assert(groups.groups.map { it.group().sequence() } == listOf(1UL, 2UL)) +assert(groups.net == mapOf(1UL to uniffi.net.Priority.LOW, 2UL to uniffi.net.Priority.HIGH)) +assert(groups.media == mapOf(1UL to Priority.Net(uniffi.net.Priority.LOW), 2UL to Priority.Realtime)) diff --git a/uniffi/fixtures/namespaces/media/tests/bindings/test_namespaces.py b/uniffi/fixtures/namespaces/media/tests/bindings/test_namespaces.py new file mode 100644 index 00000000..4b060727 --- /dev/null +++ b/uniffi/fixtures/namespaces/media/tests/bindings/test_namespaces.py @@ -0,0 +1,75 @@ +# 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/. */ + +import asyncio +import unittest + +import media +import net + + +def net_group(sequence, priority, *payloads): + return net.GroupConsumer(sequence, priority, [net.Frame(payload=p) for p in payloads]) + + +class TestNamespaces(unittest.TestCase): + def test_object_wraps_its_namesake(self): + inner = net_group(3, net.Priority.LOW, b"\x01a", b"\x00b") + group = media.GroupConsumer(inner) + self.assertIsInstance(group.group(), net.GroupConsumer) + self.assertEqual(group.group().sequence(), 3) + self.assertEqual(group.priority(), media.Priority.NET(priority=net.Priority.LOW)) + + frame = group.read_frame() + self.assertIsInstance(frame, media.Frame) + self.assertEqual(frame, media.Frame(raw=net.Frame(payload=b"\x01a"), keyframe=True)) + self.assertEqual(group.read_frame().keyframe, False) + + # Each layer's error, both named `GroupError`. + with self.assertRaises(media.GroupError.Net) as caught: + group.read_frame() + self.assertIsInstance(caught.exception.source, net.GroupError.Closed) + self.assertEqual(caught.exception.source.code, 7) + with self.assertRaises(media.GroupError.Empty): + media.decode(net.Frame(payload=b"")) + + def test_async_throws_the_net_error(self): + group = media.GroupConsumer(net_group(1, net.Priority.HIGH, b"\x01")) + self.assertEqual(group.priority(), media.Priority.REALTIME()) + + async def run(): + frame = await group.next_frame() + self.assertEqual(frame.raw.payload, b"\x01") + with self.assertRaises(net.GroupError.Closed) as caught: + await group.next_frame() + self.assertEqual(caught.exception.code, 7) + + asyncio.run(run()) + + def test_containers_of_both(self): + frame = media.decode(net.Frame(payload=b"\x00z")) + self.assertEqual(media.encode(frame), net.Frame(payload=b"\x00z")) + + frames = media.decode_all([net.Frame(payload=b"\x00a"), net.Frame(payload=b""), net.Frame(payload=b"\x01b")]) + self.assertEqual([f.keyframe for f in frames], [False, True]) + bounds = media.find_bounds(frames) + self.assertEqual(bounds.keyframe, frames[1]) + self.assertEqual(bounds.last, net.Frame(payload=b"\x01b")) + self.assertEqual(media.find_bounds([]), media.Bounds(keyframe=None, last=None)) + + groups = media.wrap_all([net_group(1, net.Priority.LOW), net_group(2, net.Priority.HIGH)]) + self.assertEqual([g.group().sequence() for g in groups.groups], [1, 2]) + self.assertEqual(groups.net, {1: net.Priority.LOW, 2: net.Priority.HIGH}) + self.assertEqual(groups.media, {1: media.Priority.NET(priority=net.Priority.LOW), 2: media.Priority.REALTIME()}) + + def test_variants_hold_a_data_enum_and_its_flat_namesake(self): + kind = net.Kind.VIDEO(width=640) + self.assertEqual( + media.tracks(kind), + [media.Track.NET(kind=kind), media.Track.MEDIA(kind=media.Kind.VIDEO)], + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/uniffi/fixtures/namespaces/media/tests/bindings/test_namespaces.swift b/uniffi/fixtures/namespaces/media/tests/bindings/test_namespaces.swift new file mode 100644 index 00000000..9b980a21 --- /dev/null +++ b/uniffi/fixtures/namespaces/media/tests/bindings/test_namespaces.swift @@ -0,0 +1,76 @@ +/* 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/. */ + +// Each crate is its own module, since both define `GroupConsumer`, `Frame`, `Priority`, and +// `GroupError`. +import Foundation +import media +import net + +func netGroup(_ sequence: UInt64, _ priority: net.Priority, _ payloads: UInt8...) -> net.GroupConsumer { + net.GroupConsumer( + sequence: sequence, + priority: priority, + frames: payloads.map { net.Frame(payload: Data([$0])) } + ) +} + +// The object wraps its namesake. +let group = media.GroupConsumer(group: netGroup(3, .low, 1, 0)) +assert(group.group().sequence() == 3) +assert(group.priority() == media.Priority.net(priority: .low)) + +let frame = try group.readFrame() +assert(frame == media.Frame(raw: net.Frame(payload: Data([1])), keyframe: true)) +let second = try group.readFrame() +assert(second.keyframe == false) + +// Each layer's error, both named `GroupError`. +do { + _ = try group.readFrame() + fatalError("should have thrown") +} catch media.GroupError.Net(let source) { + assert(source == net.GroupError.Closed(code: 7)) +} +do { + _ = try media.decode(raw: net.Frame(payload: Data())) + fatalError("should have thrown") +} catch media.GroupError.Empty { +} + +// The async method throws the `net` error as is. +let counter = DispatchGroup() +counter.enter() +Task { + let group = media.GroupConsumer(group: netGroup(1, .high, 1)) + assert(group.priority() == media.Priority.realtime) + let frame = try await group.nextFrame() + assert(frame.raw.payload == Data([1])) + do { + _ = try await group.nextFrame() + fatalError("should have thrown") + } catch net.GroupError.Closed(let code) { + assert(code == 7) + } + counter.leave() +} +counter.wait() + +// Containers of both. +let decoded = try media.decode(raw: net.Frame(payload: Data([0, 122]))) +assert(media.encode(frame: decoded) == net.Frame(payload: Data([0, 122]))) + +let frames = media.decodeAll(raw: [ + net.Frame(payload: Data([0])), net.Frame(payload: Data()), net.Frame(payload: Data([1])), +]) +assert(frames.map { $0.keyframe } == [false, true]) +let bounds = media.findBounds(frames: frames) +assert(bounds.keyframe == frames[1]) +assert(bounds.last == net.Frame(payload: Data([1]))) +assert(media.findBounds(frames: []) == media.Bounds(keyframe: nil, last: nil)) + +let groups = media.wrapAll(groups: [netGroup(1, .low), netGroup(2, .high)]) +assert(groups.groups.map { $0.group().sequence() } == [1, 2]) +assert(groups.net == [1: net.Priority.low, 2: net.Priority.high]) +assert(groups.media == [1: media.Priority.net(priority: .low), 2: media.Priority.realtime]) diff --git a/uniffi/fixtures/namespaces/media/tests/test_generated_bindings.rs b/uniffi/fixtures/namespaces/media/tests/test_generated_bindings.rs new file mode 100644 index 00000000..5feb32e9 --- /dev/null +++ b/uniffi/fixtures/namespaces/media/tests/test_generated_bindings.rs @@ -0,0 +1,5 @@ +uniffi::build_foreign_language_testcases!( + "tests/bindings/test_namespaces.kts", + "tests/bindings/test_namespaces.py", + "tests/bindings/test_namespaces.swift", +); diff --git a/uniffi/fixtures/namespaces/media/uniffi.toml b/uniffi/fixtures/namespaces/media/uniffi.toml new file mode 100644 index 00000000..b964636b --- /dev/null +++ b/uniffi/fixtures/namespaces/media/uniffi.toml @@ -0,0 +1,3 @@ +[bindings.python.external_packages] +# The test loads each crate's module from the top level, rather than from one package. +net = "" diff --git a/uniffi/fixtures/namespaces/net/Cargo.toml b/uniffi/fixtures/namespaces/net/Cargo.toml new file mode 100644 index 00000000..7ac6b966 --- /dev/null +++ b/uniffi/fixtures/namespaces/net/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "uniffi-fixture-namespaces-net" +edition = "2021" +version = "0.1.0" +license = "MPL-2.0" +publish = false + +[lib] +crate-type = ["lib", "cdylib"] +name = "uniffi_namespaces_net" + +[dependencies] +thiserror = "2" +uniffi = { workspace = true } + +[features] +ffi-trace = ["uniffi/ffi-trace"] diff --git a/uniffi/fixtures/namespaces/net/src/lib.rs b/uniffi/fixtures/namespaces/net/src/lib.rs new file mode 100644 index 00000000..3bc6661a --- /dev/null +++ b/uniffi/fixtures/namespaces/net/src/lib.rs @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! The lower of two layers whose types share their names with the upper one, `media`. + +use std::sync::{Arc, Mutex}; + +uniffi::setup_scaffolding!("net"); + +#[derive(Clone, Debug, PartialEq, uniffi::Record)] +pub struct Frame { + pub payload: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq, uniffi::Enum)] +pub enum Priority { + Low, + High, +} + +#[derive(Clone, Debug, PartialEq, uniffi::Enum)] +pub enum Kind { + Video { width: u32 }, + Audio, +} + +#[derive(Debug, thiserror::Error, uniffi::Error)] +pub enum GroupError { + #[error("group closed with code {code}")] + Closed { code: u32 }, +} + +#[derive(Debug, uniffi::Object)] +pub struct GroupConsumer { + sequence: u64, + priority: Priority, + frames: Mutex>, +} + +#[uniffi::export] +impl GroupConsumer { + #[uniffi::constructor] + pub fn new(sequence: u64, priority: Priority, frames: Vec) -> Arc { + Arc::new(Self { + sequence, + priority, + frames: Mutex::new(frames), + }) + } + + pub fn sequence(&self) -> u64 { + self.sequence + } + + pub fn priority(&self) -> Priority { + self.priority + } + + /// Takes the next frame, or fails with code 7 once there are none left. + pub fn read_frame(&self) -> Result { + let mut frames = self.frames.lock().unwrap(); + if frames.is_empty() { + return Err(GroupError::Closed { code: 7 }); + } + Ok(frames.remove(0)) + } +} diff --git a/uniffi/go/bindgen/src/gen_go/enum_.rs b/uniffi/go/bindgen/src/gen_go/enum_.rs index 5da1c170..5d3c7117 100644 --- a/uniffi/go/bindgen/src/gen_go/enum_.rs +++ b/uniffi/go/bindgen/src/gen_go/enum_.rs @@ -2,25 +2,28 @@ * 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 uniffi_bindgen::{interface::Literal, ComponentInterface}; +use uniffi_bindgen::{ + interface::{Literal, Type}, + ComponentInterface, +}; use super::{filters::oracle, CodeType}; #[derive(Debug)] pub struct EnumCodeType { - name: String, + type_: Type, } impl EnumCodeType { - pub fn new(name: String) -> Self { - Self { name } + pub fn new(type_: Type) -> Self { + Self { type_ } } } impl CodeType for EnumCodeType { fn type_label(&self, ci: &ComponentInterface) -> String { let name = self.canonical_name(); - if ci.is_name_used_as_error(&self.name) { + if ci.is_used_as_error(&self.type_) { format!("*{name}") } else { name @@ -28,7 +31,7 @@ impl CodeType for EnumCodeType { } fn canonical_name(&self) -> String { - oracle().class_name(&self.name) + oracle().class_name(self.type_.name().expect("an enum is named")) } fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { diff --git a/uniffi/go/bindgen/src/gen_go/external.rs b/uniffi/go/bindgen/src/gen_go/external.rs index 76b1c9cf..9d548ebc 100644 --- a/uniffi/go/bindgen/src/gen_go/external.rs +++ b/uniffi/go/bindgen/src/gen_go/external.rs @@ -7,6 +7,8 @@ use uniffi_bindgen::{ ComponentInterface, }; +use heck::ToUpperCamelCase; + use super::{filters::oracle, CodeType}; #[derive(Debug)] @@ -49,8 +51,12 @@ impl ExternalCodeType { } } + fn class_name(&self) -> String { + oracle().class_name(&self.name) + } + fn rendered_type_label(&self) -> String { - let label = format!("{}.{}", self.namespace, self.canonical_name()); + let label = format!("{}.{}", self.namespace, self.class_name()); match self.kind { ExternalKind::Value | ExternalKind::Interface if self.is_error => format!("*{}", label), ExternalKind::Value | ExternalKind::Interface => label, @@ -58,11 +64,12 @@ impl ExternalCodeType { } } + /// The stem of the helpers the defining package names after this type. fn helper_stem(&self) -> String { if self.custom_builtin.is_some() { - format!("Type{}", self.canonical_name()) + format!("Type{}", self.class_name()) } else { - self.canonical_name() + self.class_name() } } @@ -134,8 +141,14 @@ impl CodeType for ExternalCodeType { self.rendered_type_label() } + // Compound helpers are generated in the referencing package, which may define a type of the + // same name; the namespace keeps their helpers apart. fn canonical_name(&self) -> String { - oracle().class_name(&self.name) + format!( + "{}{}", + self.namespace.to_upper_camel_case(), + self.helper_stem() + ) } fn ffi_converter_name(&self) -> String { diff --git a/uniffi/go/bindgen/src/gen_go/mod.rs b/uniffi/go/bindgen/src/gen_go/mod.rs index 566936ad..1e2797c1 100644 --- a/uniffi/go/bindgen/src/gen_go/mod.rs +++ b/uniffi/go/bindgen/src/gen_go/mod.rs @@ -375,7 +375,7 @@ impl GoCodeOracle { Type::CallbackInterface { .. } => ExternalKind::Interface, _ => ExternalKind::Value, }; - let is_error = matches!(type_, Type::Enum { .. }) && ci.is_name_used_as_error(&name); + let is_error = matches!(type_, Type::Enum { .. }) && ci.is_used_as_error(&type_); let custom_builtin = match &type_ { Type::Custom { builtin, .. } if !matches!( @@ -433,7 +433,7 @@ impl GoCodeOracle { Type::Timestamp => Box::new(miscellany::TimestampCodeType), Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), - Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)), + Type::Enum { .. } => Box::new(enum_::EnumCodeType::new(type_)), Type::CallbackInterface { name, .. } => { Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) } @@ -649,10 +649,13 @@ impl<'a> TypeRenderer<'a> { } pub fn field_type_name(&self, field: &Field, ci: &ComponentInterface) -> String { - let name = oracle().find(&field.as_type(), ci).type_label(ci); - match self.ci.is_name_used_as_error(&name) { - true => format!("*{name}"), - false => name.to_string(), + let type_ = field.as_type(); + let name = oracle().find(&type_, ci).type_label(ci); + // An error enum's label, and an external error's, is already a pointer. + if !ci.is_external(&type_) && !name.starts_with('*') && ci.is_used_as_error(&type_) { + format!("*{name}") + } else { + name } } } diff --git a/uniffi/go/bindgen/templates/ObjectTemplate.go b/uniffi/go/bindgen/templates/ObjectTemplate.go index d03658e3..81bda41e 100644 --- a/uniffi/go/bindgen/templates/ObjectTemplate.go +++ b/uniffi/go/bindgen/templates/ObjectTemplate.go @@ -125,7 +125,7 @@ var {{ ffi_converter_instance }} = {{ ffi_converter_name }}{ } -{% if ci.is_name_used_as_error(name) %} +{% if ci.is_used_as_error(type_) %} func (_self {{impl_name}}) Error() string { {%- let has_display = obj|has_display %} {%- if has_display %} diff --git a/uniffi/go/bindgen/templates/Types.go b/uniffi/go/bindgen/templates/Types.go index 6c45eaeb..8d05e96f 100644 --- a/uniffi/go/bindgen/templates/Types.go +++ b/uniffi/go/bindgen/templates/Types.go @@ -59,7 +59,7 @@ {% include "DurationHelper.go" %} {%- when Type::Enum { name, module_path } %} -{%- if ci.is_name_used_as_error(name) %} +{%- if ci.is_used_as_error(type_) %} {{- self.error(type_)? }} {%- else %} {{- self.enum_(type_)? }} diff --git a/uniffi/go/binding_tests/namespaces_test.go b/uniffi/go/binding_tests/namespaces_test.go new file mode 100644 index 00000000..466fef16 --- /dev/null +++ b/uniffi/go/binding_tests/namespaces_test.go @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package binding_tests + +import ( + "context" + "testing" + + "github.com/NordSecurity/uniffi-bindgen-go/binding_tests/generated/media" + "github.com/NordSecurity/uniffi-bindgen-go/binding_tests/generated/net" + + "github.com/stretchr/testify/assert" +) + +// Every `media` type shares its name with the `net` type it holds or refers to. + +func netGroup(sequence uint64, priority net.Priority, payloads ...[]byte) *net.GroupConsumer { + frames := []net.Frame{} + for _, payload := range payloads { + frames = append(frames, net.Frame{Payload: payload}) + } + return net.NewGroupConsumer(sequence, priority, frames) +} + +func TestNamespacesObjectWrapsItsNamesake(t *testing.T) { + group := media.NewGroupConsumer(netGroup(3, net.PriorityLow, []byte{1, 'a'}, []byte{0, 'b'})) + assert.Equal(t, uint64(3), group.Group().Sequence()) + assert.Equal(t, media.Priority(media.PriorityNet{Priority: net.PriorityLow}), group.Priority()) + + frame, err := group.ReadFrame() + assert.NoError(t, err) + assert.Equal(t, media.Frame{Raw: net.Frame{Payload: []byte{1, 'a'}}, Keyframe: true}, frame) + frame, err = group.ReadFrame() + assert.NoError(t, err) + assert.False(t, frame.Keyframe) + + // Each layer's error, both named `GroupError`. + _, err = group.ReadFrame() + var mediaErr *media.GroupErrorNet + assert.ErrorAs(t, err, &mediaErr) + var netErr *net.GroupErrorClosed + assert.ErrorAs(t, mediaErr.Source, &netErr) + assert.Equal(t, uint32(7), netErr.Code) + + _, err = media.Decode(net.Frame{Payload: []byte{}}) + assert.ErrorIs(t, err, media.ErrGroupErrorEmpty) +} + +func TestNamespacesAsyncThrowsTheNetError(t *testing.T) { + group := media.NewGroupConsumer(netGroup(1, net.PriorityHigh, []byte{1})) + assert.Equal(t, media.Priority(media.PriorityRealtime{}), group.Priority()) + + frame, err := group.NextFrame(context.Background()) + assert.NoError(t, err) + assert.Equal(t, []byte{1}, frame.Raw.Payload) + + _, err = group.NextFrame(context.Background()) + var netErr *net.GroupErrorClosed + assert.ErrorAs(t, err, &netErr) + assert.Equal(t, uint32(7), netErr.Code) +} + +func TestNamespacesContainersOfBoth(t *testing.T) { + frame, err := media.Decode(net.Frame{Payload: []byte{0, 'z'}}) + assert.NoError(t, err) + assert.Equal(t, net.Frame{Payload: []byte{0, 'z'}}, media.Encode(frame)) + + frames := media.DecodeAll([]net.Frame{{Payload: []byte{0, 'a'}}, {Payload: []byte{}}, {Payload: []byte{1, 'b'}}}) + assert.Equal(t, 2, len(frames)) + assert.False(t, frames[0].Keyframe) + assert.True(t, frames[1].Keyframe) + + bounds := media.FindBounds(frames) + assert.Equal(t, &frames[1], bounds.Keyframe) + assert.Equal(t, &net.Frame{Payload: []byte{1, 'b'}}, bounds.Last) + assert.Equal(t, media.Bounds{}, media.FindBounds([]media.Frame{})) + + groups := media.WrapAll([]*net.GroupConsumer{netGroup(1, net.PriorityLow), netGroup(2, net.PriorityHigh)}) + assert.Equal(t, 2, len(groups.Groups)) + assert.Equal(t, uint64(1), groups.Groups[0].Group().Sequence()) + assert.Equal(t, uint64(2), groups.Groups[1].Group().Sequence()) + assert.Equal(t, map[uint64]net.Priority{1: net.PriorityLow, 2: net.PriorityHigh}, groups.Net) + assert.Equal(t, map[uint64]media.Priority{ + 1: media.PriorityNet{Priority: net.PriorityLow}, + 2: media.PriorityRealtime{}, + }, groups.Media) +} diff --git a/uniffi/go/fixtures/Cargo.toml b/uniffi/go/fixtures/Cargo.toml index 828f04b3..4e7bf7cc 100644 --- a/uniffi/go/fixtures/Cargo.toml +++ b/uniffi/go/fixtures/Cargo.toml @@ -38,6 +38,8 @@ uniffi-fixture-ext-types-lib-one = { path = "../../fixtures/ext-types/uniffi-one uniffi-fixture-ext-types-proc-macro = { path = "../../fixtures/ext-types/proc-macro-lib"} uniffi-fixture-large-enum = { package = "large-enum", path = "../../fixtures/large-enum"} uniffi-fixture-large-error = { package = "large-error", path = "../../fixtures/large-error"} +uniffi-fixture-namespaces-media = { path = "../../fixtures/namespaces/media"} +uniffi-fixture-namespaces-net = { path = "../../fixtures/namespaces/net"} uniffi-fixture-proc-macro = { path = "../../fixtures/proc-macro"} uniffi-fixture-simple-fns = { path = "../../fixtures/simple-fns"} uniffi-fixture-simple-iface = { path = "../../fixtures/simple-iface"} diff --git a/uniffi/go/fixtures/src/lib.rs b/uniffi/go/fixtures/src/lib.rs index d5804d4b..2f2d0137 100644 --- a/uniffi/go/fixtures/src/lib.rs +++ b/uniffi/go/fixtures/src/lib.rs @@ -25,6 +25,8 @@ mod uniffi_fixtures { uniffi_one::uniffi_reexport_scaffolding!(); uniffi_fixture_large_enum::uniffi_reexport_scaffolding!(); + uniffi_namespaces_media::uniffi_reexport_scaffolding!(); + uniffi_namespaces_net::uniffi_reexport_scaffolding!(); uniffi_go_futures::uniffi_reexport_scaffolding!(); uniffi_proc_macro::uniffi_reexport_scaffolding!(); diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs index ae4bffc9..408aad8f 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs @@ -2,30 +2,35 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use super::CodeType; -use crate::ComponentInterface; +use super::{CodeType, KotlinCi}; +use crate::interface::Type; #[derive(Debug)] pub struct CallbackInterfaceCodeType { - id: String, + ty: Type, } impl CallbackInterfaceCodeType { - pub fn new(id: String) -> Self { - Self { id } + pub fn new(ty: Type) -> Self { + Self { ty } } } impl CodeType for CallbackInterfaceCodeType { - fn type_label(&self, ci: &ComponentInterface) -> String { - super::KotlinCodeOracle.class_name(ci, &self.id) + fn type_label(&self, ci: &KotlinCi<'_>) -> String { + ci.named_type_label(&self.ty) } - fn canonical_name(&self) -> String { - format!("Type{}", self.id) + fn canonical_name(&self, ci: &KotlinCi<'_>) -> String { + ci.named_canonical_name(&self.ty) + } + + fn ffi_converter_name(&self, ci: &KotlinCi<'_>) -> String { + ci.named_ffi_converter_name(&self.ty) } fn initialization_fn(&self) -> Option { - Some(format!("uniffiCallbackInterface{}.register", self.id)) + let name = self.ty.name().expect("a named type"); + Some(format!("uniffiCallbackInterface{name}.register")) } } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs index 8e9876d3..6470786f 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs @@ -2,11 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use super::{AsCodeType, CodeType}; +use super::{AsCodeType, CodeType, KotlinCi}; use crate::{ bail, interface::{DefaultValue, Literal, Type}, - ComponentInterface, Result, + Result, }; #[derive(Debug)] @@ -24,21 +24,23 @@ impl OptionalCodeType { } impl CodeType for OptionalCodeType { - fn type_label(&self, ci: &ComponentInterface) -> String { + fn type_label(&self, ci: &KotlinCi<'_>) -> String { format!( "{}?", super::KotlinCodeOracle.find(self.inner()).type_label(ci) ) } - fn canonical_name(&self) -> String { + fn canonical_name(&self, ci: &KotlinCi<'_>) -> String { format!( "Optional{}", - super::KotlinCodeOracle.find(self.inner()).canonical_name() + super::KotlinCodeOracle + .find(self.inner()) + .canonical_name(ci) ) } - fn default(&self, default: &DefaultValue, ci: &ComponentInterface) -> Result { + fn default(&self, default: &DefaultValue, ci: &KotlinCi<'_>) -> Result { match default { DefaultValue::Default | DefaultValue::Literal(Literal::None) => Ok("null".into()), DefaultValue::Literal(Literal::Some { inner }) => { @@ -64,21 +66,23 @@ impl SequenceCodeType { } impl CodeType for SequenceCodeType { - fn type_label(&self, ci: &ComponentInterface) -> String { + fn type_label(&self, ci: &KotlinCi<'_>) -> String { format!( "List<{}>", super::KotlinCodeOracle.find(self.inner()).type_label(ci) ) } - fn canonical_name(&self) -> String { + fn canonical_name(&self, ci: &KotlinCi<'_>) -> String { format!( "Sequence{}", - super::KotlinCodeOracle.find(self.inner()).canonical_name() + super::KotlinCodeOracle + .find(self.inner()) + .canonical_name(ci) ) } - fn default(&self, default: &DefaultValue, _ci: &ComponentInterface) -> Result { + fn default(&self, default: &DefaultValue, _ci: &KotlinCi<'_>) -> Result { match default { DefaultValue::Default | DefaultValue::Literal(Literal::EmptySequence) => { Ok("listOf()".into()) @@ -109,7 +113,7 @@ impl MapCodeType { } impl CodeType for MapCodeType { - fn type_label(&self, ci: &ComponentInterface) -> String { + fn type_label(&self, ci: &KotlinCi<'_>) -> String { format!( "Map<{}, {}>", super::KotlinCodeOracle.find(self.key()).type_label(ci), @@ -117,15 +121,15 @@ impl CodeType for MapCodeType { ) } - fn canonical_name(&self) -> String { + fn canonical_name(&self, ci: &KotlinCi<'_>) -> String { format!( "Map{}{}", - self.key().as_codetype().canonical_name(), - self.value().as_codetype().canonical_name(), + self.key().as_codetype().canonical_name(ci), + self.value().as_codetype().canonical_name(ci), ) } - fn default(&self, default: &DefaultValue, _ci: &ComponentInterface) -> Result { + fn default(&self, default: &DefaultValue, _ci: &KotlinCi<'_>) -> Result { match default { DefaultValue::Default | DefaultValue::Literal(Literal::EmptyMap) => { Ok("mapOf()".into()) @@ -151,21 +155,23 @@ impl SetCodeType { } impl CodeType for SetCodeType { - fn type_label(&self, ci: &ComponentInterface) -> String { + fn type_label(&self, ci: &KotlinCi<'_>) -> String { format!( "Set<{}>", super::KotlinCodeOracle.find(self.inner()).type_label(ci) ) } - fn canonical_name(&self) -> String { + fn canonical_name(&self, ci: &KotlinCi<'_>) -> String { format!( "Set{}", - super::KotlinCodeOracle.find(self.inner()).canonical_name() + super::KotlinCodeOracle + .find(self.inner()) + .canonical_name(ci) ) } - fn default(&self, default: &DefaultValue, _ci: &ComponentInterface) -> Result { + fn default(&self, default: &DefaultValue, _ci: &KotlinCi<'_>) -> Result { match default { DefaultValue::Default | DefaultValue::Literal(Literal::EmptySequence) => { Ok("setOf()".into()) diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs index add9a1e8..2292299e 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs @@ -2,32 +2,39 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use super::CodeType; -use crate::{interface::DefaultValue, ComponentInterface}; +use super::{AsCodeType, CodeType, KotlinCi}; +use crate::interface::{DefaultValue, Type}; #[derive(Debug)] pub struct CustomCodeType { - name: String, - builtin: Box, + ty: Type, } impl CustomCodeType { - pub fn new(name: String, builtin: Box) -> Self { - CustomCodeType { name, builtin } + pub fn new(ty: Type) -> Self { + CustomCodeType { ty } } } impl CodeType for CustomCodeType { - fn type_label(&self, ci: &ComponentInterface) -> String { - super::KotlinCodeOracle.class_name(ci, &self.name) + fn type_label(&self, ci: &KotlinCi<'_>) -> String { + ci.named_type_label(&self.ty) } - fn canonical_name(&self) -> String { - format!("Type{}", self.name) + fn canonical_name(&self, ci: &KotlinCi<'_>) -> String { + ci.named_canonical_name(&self.ty) } - fn default(&self, default: &DefaultValue, ci: &ComponentInterface) -> anyhow::Result { - self.builtin + fn ffi_converter_name(&self, ci: &KotlinCi<'_>) -> String { + ci.named_ffi_converter_name(&self.ty) + } + + fn default(&self, default: &DefaultValue, ci: &KotlinCi<'_>) -> anyhow::Result { + let Type::Custom { builtin, .. } = &self.ty else { + unreachable!("a custom type") + }; + builtin + .as_codetype() .default(default, ci) .map_err(|_e| anyhow::anyhow!("Unsupported default value for {}", self.type_label(ci))) } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs index c9fe0558..86d33b0a 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs @@ -2,34 +2,38 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use super::CodeType; +use super::{CodeType, KotlinCi}; use crate::{ bail, - interface::{DefaultValue, Literal}, - ComponentInterface, Result, + interface::{DefaultValue, Literal, Type}, + Result, }; #[derive(Debug)] pub struct EnumCodeType { - id: String, + ty: Type, } impl EnumCodeType { - pub fn new(id: String) -> Self { - Self { id } + pub fn new(ty: Type) -> Self { + Self { ty } } } impl CodeType for EnumCodeType { - fn type_label(&self, ci: &ComponentInterface) -> String { - super::KotlinCodeOracle.class_name(ci, &self.id) + fn type_label(&self, ci: &KotlinCi<'_>) -> String { + ci.named_type_label(&self.ty) } - fn canonical_name(&self) -> String { - format!("Type{}", self.id) + fn canonical_name(&self, ci: &KotlinCi<'_>) -> String { + ci.named_canonical_name(&self.ty) } - fn default(&self, default: &DefaultValue, ci: &ComponentInterface) -> Result { + fn ffi_converter_name(&self, ci: &KotlinCi<'_>) -> String { + ci.named_ffi_converter_name(&self.ty) + } + + fn default(&self, default: &DefaultValue, ci: &KotlinCi<'_>) -> Result { if let DefaultValue::Literal(Literal::Enum(v, _)) = default { Ok(format!( "{}.{}", diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs index 135db343..919c17a9 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs @@ -2,8 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use super::CodeType; -use crate::ComponentInterface; +use super::{CodeType, KotlinCi}; macro_rules! impl_code_type_for_miscellany { ($T:ident, $class_name:literal, $canonical_name:literal) => { @@ -11,11 +10,11 @@ macro_rules! impl_code_type_for_miscellany { pub struct $T; impl CodeType for $T { - fn type_label(&self, _ci: &ComponentInterface) -> String { + fn type_label(&self, _ci: &KotlinCi<'_>) -> String { $class_name.into() } - fn canonical_name(&self) -> String { + fn canonical_name(&self, _ci: &KotlinCi<'_>) -> String { $canonical_name.into() } } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index bd64ced5..06e958e5 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -6,6 +6,7 @@ use std::borrow::Borrow; use std::cell::RefCell; use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Debug; +use std::ops::Deref; use askama::Template; use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; @@ -29,17 +30,17 @@ mod variant; trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in /// method signatures and property declarations. - fn type_label(&self, ci: &ComponentInterface) -> String; + fn type_label(&self, ci: &KotlinCi<'_>) -> String; /// A representation of this type label that can be used as part of another /// identifier. e.g. `read_foo()`, or `FooInternals`. /// /// This is especially useful when creating specialized objects or methods to deal /// with this type only. - fn canonical_name(&self) -> String; + fn canonical_name(&self, ci: &KotlinCi<'_>) -> String; // default for named types is to assume a ctor exists. - fn default(&self, default: &DefaultValue, ci: &ComponentInterface) -> Result { + fn default(&self, default: &DefaultValue, ci: &KotlinCi<'_>) -> Result { match default { DefaultValue::Default => Ok(format!("{}()", self.type_label(ci))), DefaultValue::Literal(_) => bail!("Literals for named types are not supported"), @@ -54,8 +55,8 @@ trait CodeType: Debug { /// This is the newer way of handling these methods and replaces the lower, write, lift, and /// read CodeType methods. Currently only used by Kotlin, but the plan is to move other /// backends to using this. - fn ffi_converter_name(&self) -> String { - format!("FfiConverter{}", self.canonical_name()) + fn ffi_converter_name(&self, ci: &KotlinCi<'_>) -> String { + format!("FfiConverter{}", self.canonical_name(ci)) } /// Function to run at startup @@ -64,6 +65,80 @@ trait CodeType: Debug { } } +/// The component being rendered, with the config that names the package of every crate it uses. +/// +/// Another crate's types are always spelled with their package: a bare-name import would be +/// shadowed by a local type of the same name. +#[derive(Clone, Copy)] +pub struct KotlinCi<'a> { + ci: &'a ComponentInterface, + config: &'a Config, +} + +impl<'a> KotlinCi<'a> { + fn new(ci: &'a ComponentInterface, config: &'a Config) -> Self { + Self { ci, config } + } + + /// The package of the crate a module path belongs to. + fn package_for_module_path(&self, module_path: &str) -> String { + let namespace = self.ci.namespace_for_module_path(module_path).ok(); + self.config.external_package_name(module_path, namespace) + } + + /// The package of another crate's type, or `None` for a local type. + fn external_package(&self, ty: &Type) -> Option { + if !self.ci.is_external(ty) { + return None; + } + ty.module_path().map(|p| self.package_for_module_path(p)) + } + + /// The label of a named type. + fn named_type_label(&self, ty: &Type) -> String { + let class_name = KotlinCodeOracle.type_class_name(self.ci, ty); + match self.external_package(ty) { + Some(package) => format!("{package}.{class_name}"), + None => class_name, + } + } + + /// The canonical name of a named type, which starts with its namespace when it's another + /// crate's so the helpers for containers of it don't collide with a local namesake's. + fn named_canonical_name(&self, ty: &Type) -> String { + let name = ty.name().expect("a named type"); + if self.ci.is_external(ty) { + // Outside library mode, an external crate's namespace may be unknown. + let namespace = self + .ci + .namespace_for_type(ty) + .ok() + .or(ty.crate_name()) + .expect("an external type has a crate"); + format!("{}Type{name}", namespace.to_upper_camel_case()) + } else { + format!("Type{name}") + } + } + + /// The FfiConverter of a named type, which another crate's bindings define. + fn named_ffi_converter_name(&self, ty: &Type) -> String { + let name = ty.name().expect("a named type"); + match self.external_package(ty) { + Some(package) => format!("{package}.FfiConverterType{name}"), + None => format!("FfiConverterType{name}"), + } + } +} + +impl Deref for KotlinCi<'_> { + type Target = ComponentInterface; + + fn deref(&self) -> &ComponentInterface { + self.ci + } +} + // config options to customize the generated Kotlin. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Config { @@ -223,7 +298,7 @@ impl Config { // Generate kotlin bindings for the given ComponentInterface, as a string. pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result { ensure_flat_enum_trait_methods_supported(ci)?; - KotlinWrapper::new(config.clone(), ci) + KotlinWrapper::new(config, ci) .context("failed to create a binding generator")? .render() .context("failed to render kotlin bindings") @@ -231,7 +306,7 @@ pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result Result<()> { for enum_def in ci.enum_definitions() { - if ci.is_name_used_as_error(enum_def.name()) || !enum_def.is_flat() { + if ci.is_used_as_error(enum_def) || !enum_def.is_flat() { continue; } @@ -254,8 +329,6 @@ fn ensure_flat_enum_trait_methods_supported(ci: &ComponentInterface) -> Result<( pub enum ImportRequirement { /// The name we are importing. Import { name: String }, - /// Import the name with the specified local name. - ImportAs { name: String, as_name: String }, } impl ImportRequirement { @@ -263,9 +336,6 @@ impl ImportRequirement { fn render(&self) -> String { match &self { ImportRequirement::Import { name } => format!("import {name}"), - ImportRequirement::ImportAs { name, as_name } => { - format!("import {name} as {as_name}") - } } } } @@ -278,7 +348,7 @@ impl ImportRequirement { #[template(syntax = "kt", escape = "none", path = "Types.kt")] pub struct TypeRenderer<'a> { config: &'a Config, - ci: &'a ComponentInterface, + ci: KotlinCi<'a>, // Track imports added with the `add_import()` macro imports: RefCell>, } @@ -287,17 +357,11 @@ impl<'a> TypeRenderer<'a> { fn new(config: &'a Config, ci: &'a ComponentInterface) -> Self { Self { config, - ci, + ci: KotlinCi::new(ci, config), imports: RefCell::new(BTreeSet::new()), } } - // Get the package name for an external type - fn external_type_package_name(&self, module_path: &str, namespace: &str) -> String { - self.config - .external_package_name(module_path, Some(namespace)) - } - // The following methods are used by the `Types.kt` macros. // Helper to add an import statement @@ -312,42 +376,31 @@ impl<'a> TypeRenderer<'a> { }); "" } - - // Like add_import, but arranges for `import name as as_name` - fn add_import_as(&self, name: &str, as_name: &str) -> &str { - self.imports - .borrow_mut() - .insert(ImportRequirement::ImportAs { - name: name.to_owned(), - as_name: as_name.to_owned(), - }); - "" - } } #[derive(Template)] #[template(syntax = "kt", escape = "none", path = "wrapper.kt")] pub struct KotlinWrapper<'a> { - config: Config, - ci: &'a ComponentInterface, + config: &'a Config, + ci: KotlinCi<'a>, type_helper_code: String, type_imports: BTreeSet, } impl<'a> KotlinWrapper<'a> { - pub fn new(config: Config, ci: &'a ComponentInterface) -> Result { - let type_renderer = TypeRenderer::new(&config, ci); + pub fn new(config: &'a Config, ci: &'a ComponentInterface) -> Result { + let type_renderer = TypeRenderer::new(config, ci); let type_helper_code = type_renderer.render()?; let type_imports = type_renderer.imports.into_inner(); Ok(Self { config, - ci, + ci: KotlinCi::new(ci, config), type_helper_code, type_imports, }) } - pub fn initialization_fns(&self, ci: &ComponentInterface) -> Vec { + pub fn initialization_fns(&self) -> Vec { let init_fns = self .ci .iter_local_types() @@ -363,10 +416,7 @@ impl<'a> KotlinWrapper<'a> { .iter_external_types() .filter_map(|ty| ty.crate_name()) .map(|crate_name| { - let namespace = ci.namespace_for_module_path(crate_name).unwrap(); - let package_name = self - .config - .external_package_name(crate_name, Some(namespace)); + let package_name = self.ci.package_for_module_path(crate_name); format!("{package_name}.uniffiEnsureInitialized()") }) // Collect into a btree set to de-dup and order @@ -390,7 +440,7 @@ impl<'a> KotlinWrapper<'a> { /// This all impacts what types `FfiConverter.lower()` inputs. If it's a "foreign trait" /// `lower` must lower anything that implements the interface (ie, a kotlin implementation). /// If not, then lower only lowers the concrete class (ie, our simple instance with the pointer). -fn object_interface_name(ci: &ComponentInterface, obj: &Object) -> String { +fn object_interface_name(ci: &KotlinCi<'_>, obj: &Object) -> String { let class_name = KotlinCodeOracle.class_name(ci, obj.name()); if obj.has_callback_interface() { class_name @@ -401,7 +451,7 @@ fn object_interface_name(ci: &ComponentInterface, obj: &Object) -> String { // *sigh* - same thing for a trait, which might be either Object or CallbackInterface. // (we should either fold it into object or kill it!) -fn trait_interface_name(ci: &ComponentInterface, trait_ty: &Type) -> Result { +fn trait_interface_name(ci: &KotlinCi<'_>, trait_ty: &Type) -> Result { let Some(crate_name) = trait_ty.crate_name() else { bail!("Invalid trait_type: {trait_ty:?}"); }; @@ -423,15 +473,19 @@ fn trait_interface_name(ci: &ComponentInterface, trait_ty: &Type) -> Result format!("{package}.{interface_name}"), + None => interface_name, + }) } // The name of the object exposing a Rust implementation. -fn object_impl_name(ci: &ComponentInterface, obj: &Object) -> String { +fn object_impl_name(ci: &KotlinCi<'_>, obj: &Object) -> String { let class_name = KotlinCodeOracle.class_name(ci, obj.name()); if obj.has_callback_interface() { format!("{class_name}Impl") @@ -449,10 +503,24 @@ impl KotlinCodeOracle { } /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc). + /// + /// `nm` names a local item; see `type_class_name` for a type of any crate. fn class_name(&self, ci: &ComponentInterface, nm: &str) -> String { - let name = nm.to_string().to_upper_camel_case(); + match ci.get_type(nm) { + Some(ty) => self.type_class_name(ci, &ty), + None => nm.to_string().to_upper_camel_case(), + } + } + + /// The unqualified class name of a named type, which ends in `Exception` for an error. + fn type_class_name(&self, ci: &ComponentInterface, ty: &Type) -> String { + let name = ty + .name() + .expect("a named type") + .to_string() + .to_upper_camel_case(); // fixup errors. - if ci.is_name_used_as_error(nm) { + if ci.is_used_as_error(ty) { self.convert_error_suffix(&name) } else { name @@ -496,7 +564,7 @@ impl KotlinCodeOracle { format!("Uniffi{}", nm.to_upper_camel_case()) } - fn ffi_type_label_by_value(&self, ffi_type: &FfiType, ci: &ComponentInterface) -> String { + fn ffi_type_label_by_value(&self, ffi_type: &FfiType, ci: &KotlinCi<'_>) -> String { match ffi_type { FfiType::RustBuffer(_) => format!("{}.ByValue", self.ffi_type_label(ffi_type, ci)), FfiType::Struct(name) => format!("{}.UniffiByValue", self.ffi_struct_name(name)), @@ -507,11 +575,7 @@ impl KotlinCodeOracle { /// Kotlin/JNA direct mapping can mis-handle unsigned 8/16-bit direct return values /// on some runtimes, so widen the raw carrier to Int and let the generated /// converters lift it back into the public UByte/UShort API types. - fn ffi_type_label_for_direct_return( - &self, - ffi_type: &FfiType, - ci: &ComponentInterface, - ) -> String { + fn ffi_type_label_for_direct_return(&self, ffi_type: &FfiType, ci: &KotlinCi<'_>) -> String { match ffi_type { FfiType::UInt8 | FfiType::UInt16 => "Int".to_string(), _ => self.ffi_type_label_by_value(ffi_type, ci), @@ -522,7 +586,7 @@ impl KotlinCodeOracle { /// /// The main requirement here is that all types must have default values or else the struct /// won't work in some JNA contexts. - fn ffi_type_label_for_ffi_struct(&self, ffi_type: &FfiType, ci: &ComponentInterface) -> String { + fn ffi_type_label_for_ffi_struct(&self, ffi_type: &FfiType, ci: &KotlinCi<'_>) -> String { match ffi_type { // Make callbacks function pointers nullable. This matches the semantics of a C // function pointer better and allows for `null` as a default value. @@ -552,7 +616,7 @@ impl KotlinCodeOracle { } } - fn ffi_type_label_by_reference(&self, ffi_type: &FfiType, ci: &ComponentInterface) -> String { + fn ffi_type_label_by_reference(&self, ffi_type: &FfiType, ci: &KotlinCi<'_>) -> String { match ffi_type { FfiType::Int8 | FfiType::UInt8 @@ -571,7 +635,7 @@ impl KotlinCodeOracle { } } - fn ffi_type_label(&self, ffi_type: &FfiType, ci: &ComponentInterface) -> String { + fn ffi_type_label(&self, ffi_type: &FfiType, ci: &KotlinCi<'_>) -> String { match ffi_type { // Note that unsigned integers in Kotlin are currently experimental, but java.nio.ByteBuffer does not // support them yet. Thus, we use the signed variants to represent both signed and unsigned @@ -583,9 +647,13 @@ impl KotlinCodeOracle { FfiType::Float32 => "Float".to_string(), FfiType::Float64 => "Double".to_string(), FfiType::Handle => "Long".to_string(), + // Another crate's buffers are its own JNA structure. FfiType::RustBuffer(maybe_external) => match maybe_external { Some(external_meta) if external_meta.crate_name() != ci.crate_name() => { - format!("RustBuffer{}", external_meta.name) + format!( + "{}.RustBuffer", + ci.package_for_module_path(&external_meta.module_path) + ) } _ => "RustBuffer".to_string(), }, @@ -632,11 +700,11 @@ impl AsCodeType for T { Type::Timestamp => Box::new(miscellany::TimestampCodeType), Type::Duration => Box::new(miscellany::DurationCodeType), - Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)), - Type::Object { name, imp, .. } => Box::new(object::ObjectCodeType::new(name, imp)), - Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)), - Type::CallbackInterface { name, .. } => { - Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) + ty @ Type::Enum { .. } => Box::new(enum_::EnumCodeType::new(ty)), + ty @ Type::Object { .. } => Box::new(object::ObjectCodeType::new(ty)), + ty @ Type::Record { .. } => Box::new(record::RecordCodeType::new(ty)), + ty @ Type::CallbackInterface { .. } => { + Box::new(callback_interface::CallbackInterfaceCodeType::new(ty)) } Type::Optional { inner_type } => { Box::new(compounds::OptionalCodeType::new(*inner_type)) @@ -649,9 +717,7 @@ impl AsCodeType for T { value_type, } => Box::new(compounds::MapCodeType::new(*key_type, *value_type)), Type::Set { inner_type } => Box::new(compounds::SetCodeType::new(*inner_type)), - Type::Custom { name, builtin, .. } => { - Box::new(custom::CustomCodeType::new(name, builtin.as_codetype())) - } + ty @ Type::Custom { .. } => Box::new(custom::CustomCodeType::new(ty)), Type::Box { inner_type } => inner_type.as_codetype(), } } @@ -665,7 +731,7 @@ mod filters { pub(super) fn type_name( as_ct: &impl AsCodeType, _: &dyn askama::Values, - ci: &ComponentInterface, + ci: &KotlinCi<'filter>, ) -> Result { Ok(as_ct.as_codetype().type_label(ci)) } @@ -674,30 +740,30 @@ mod filters { pub(super) fn canonical_name( as_ct: &impl AsCodeType, _: &dyn askama::Values, + ci: &KotlinCi<'filter>, ) -> Result { - Ok(as_ct.as_codetype().canonical_name()) + Ok(as_ct.as_codetype().canonical_name(ci)) } #[askama::filter_fn] pub(super) fn qualified_type_name( as_type: &T, _: &dyn askama::Values, - ci: &ComponentInterface, - config: &Config, + ci: &KotlinCi<'filter>, ) -> Result where T: AsCodeType + AsType, { - fully_qualified_type_label(&as_type.as_type(), ci, config) - .map_err(|err| to_askama_error(&err)) + Ok(fully_qualified_type_label(&as_type.as_type(), ci)) } #[askama::filter_fn] pub(super) fn ffi_converter_name( as_ct: &impl AsCodeType, _: &dyn askama::Values, + ci: &KotlinCi<'filter>, ) -> Result { - Ok(as_ct.as_codetype().ffi_converter_name()) + Ok(as_ct.as_codetype().ffi_converter_name(ci)) } #[askama::filter_fn] @@ -712,10 +778,11 @@ mod filters { pub(super) fn lower_fn( as_ct: &impl AsCodeType, _: &dyn askama::Values, + ci: &KotlinCi<'filter>, ) -> Result { Ok(format!( "{}.lower", - as_ct.as_codetype().ffi_converter_name() + as_ct.as_codetype().ffi_converter_name(ci) )) } @@ -723,10 +790,11 @@ mod filters { pub(super) fn allocation_size_fn( as_ct: &impl AsCodeType, _: &dyn askama::Values, + ci: &KotlinCi<'filter>, ) -> Result { Ok(format!( "{}.allocationSize", - as_ct.as_codetype().ffi_converter_name() + as_ct.as_codetype().ffi_converter_name(ci) )) } @@ -734,10 +802,11 @@ mod filters { pub(super) fn write_fn( as_ct: &impl AsCodeType, _: &dyn askama::Values, + ci: &KotlinCi<'filter>, ) -> Result { Ok(format!( "{}.write", - as_ct.as_codetype().ffi_converter_name() + as_ct.as_codetype().ffi_converter_name(ci) )) } @@ -745,69 +814,58 @@ mod filters { pub(super) fn lift_fn( as_ct: &impl AsCodeType, _: &dyn askama::Values, + ci: &KotlinCi<'filter>, ) -> Result { - Ok(format!("{}.lift", as_ct.as_codetype().ffi_converter_name())) + Ok(format!( + "{}.lift", + as_ct.as_codetype().ffi_converter_name(ci) + )) } #[askama::filter_fn] pub(super) fn read_fn( as_ct: &impl AsCodeType, _: &dyn askama::Values, + ci: &KotlinCi<'filter>, ) -> Result { - Ok(format!("{}.read", as_ct.as_codetype().ffi_converter_name())) + Ok(format!( + "{}.read", + as_ct.as_codetype().ffi_converter_name(ci) + )) } - fn fully_qualified_type_label( - ty: &Type, - ci: &ComponentInterface, - config: &Config, - ) -> Result { + fn fully_qualified_type_label(ty: &Type, ci: &KotlinCi<'_>) -> String { match ty { - Type::Optional { inner_type } => Ok(format!( - "{}?", - fully_qualified_type_label(inner_type, ci, config)? - )), - Type::Sequence { inner_type } => Ok(format!( - "List<{}>", - fully_qualified_type_label(inner_type, ci, config)? - )), + Type::Optional { inner_type } => { + format!("{}?", fully_qualified_type_label(inner_type, ci)) + } + Type::Sequence { inner_type } => { + format!("List<{}>", fully_qualified_type_label(inner_type, ci)) + } Type::Map { key_type, value_type, - } => Ok(format!( + } => format!( "Map<{}, {}>", - fully_qualified_type_label(key_type, ci, config)?, - fully_qualified_type_label(value_type, ci, config)? - )), - Type::Set { inner_type } => Ok(format!( - "Set<{}>", - fully_qualified_type_label(inner_type, ci, config)? - )), + fully_qualified_type_label(key_type, ci), + fully_qualified_type_label(value_type, ci) + ), + Type::Set { inner_type } => { + format!("Set<{}>", fully_qualified_type_label(inner_type, ci)) + } Type::Enum { .. } | Type::Record { .. } | Type::Object { .. } | Type::CallbackInterface { .. } - | Type::Custom { .. } => { - let class_name = ty - .name() - .map(|nm| KotlinCodeOracle.class_name(ci, nm)) - .ok_or_else(|| anyhow::anyhow!("type {:?} has no name", ty))?; - let package_name = package_for_type(ty, ci, config)?; - Ok(format!("{package_name}.{class_name}")) - } - _ => Ok(KotlinCodeOracle.find(ty).type_label(ci)), - } - } - - fn package_for_type(ty: &Type, ci: &ComponentInterface, config: &Config) -> Result { - if ci.is_external(ty) { - let module_path = ty - .module_path() - .ok_or_else(|| anyhow::anyhow!("external type {:?} missing module path", ty))?; - let namespace = ci.namespace_for_module_path(module_path)?; - Ok(config.external_package_name(module_path, Some(namespace))) - } else { - Ok(config.package_name()) + | Type::Custom { .. } => match ci.is_external(ty) { + true => ci.named_type_label(ty), + false => format!( + "{}.{}", + ci.config.package_name(), + KotlinCodeOracle.type_class_name(ci, ty) + ), + }, + _ => KotlinCodeOracle.find(ty).type_label(ci), } } @@ -816,7 +874,7 @@ mod filters { default: &DefaultValue, _: &dyn askama::Values, as_ct: &T, - ci: &ComponentInterface, + ci: &KotlinCi<'filter>, ) -> Result { as_ct .as_codetype() @@ -864,7 +922,7 @@ mod filters { pub fn ffi_type_name_by_value( type_: &FfiType, _: &dyn askama::Values, - ci: &ComponentInterface, + ci: &KotlinCi<'filter>, ) -> Result { Ok(KotlinCodeOracle.ffi_type_label_by_value(type_, ci)) } @@ -873,7 +931,7 @@ mod filters { pub fn ffi_type_name_for_direct_return( type_: &FfiType, _: &dyn askama::Values, - ci: &ComponentInterface, + ci: &KotlinCi<'filter>, ) -> Result { Ok(KotlinCodeOracle.ffi_type_label_for_direct_return(type_, ci)) } @@ -882,7 +940,7 @@ mod filters { pub fn ffi_type_name_for_ffi_struct( type_: &FfiType, _: &dyn askama::Values, - ci: &ComponentInterface, + ci: &KotlinCi<'filter>, ) -> Result { Ok(KotlinCodeOracle.ffi_type_label_for_ffi_struct(type_, ci)) } @@ -900,7 +958,7 @@ mod filters { pub fn class_name>( nm: S, _: &dyn askama::Values, - ci: &ComponentInterface, + ci: &KotlinCi<'filter>, ) -> Result { Ok(KotlinCodeOracle.class_name(ci, nm.as_ref())) } @@ -935,7 +993,7 @@ mod filters { pub(super) fn lower_type_name_for_arg( arg: &Argument, _: &dyn askama::Values, - ci: &ComponentInterface, + ci: &KotlinCi<'filter>, ) -> Result { if arg.is_borrowed_bytes() { Ok("java.nio.ByteBuffer".to_string()) @@ -951,11 +1009,15 @@ mod filters { pub(super) fn lower_fn_for_arg( arg: &Argument, _: &dyn askama::Values, + ci: &KotlinCi<'filter>, ) -> Result { if arg.is_borrowed_bytes() { Ok("FfiConverterByRefBytes.lower".to_string()) } else { - Ok(format!("{}.lower", arg.as_codetype().ffi_converter_name())) + Ok(format!( + "{}.lower", + arg.as_codetype().ffi_converter_name(ci) + )) } } @@ -996,7 +1058,7 @@ mod filters { pub fn async_poll( callable: impl Callable, _: &dyn askama::Values, - ci: &ComponentInterface, + ci: &KotlinCi<'filter>, ) -> Result { let ffi_func = callable.ffi_rust_future_poll(ci); Ok(format!( @@ -1008,7 +1070,7 @@ mod filters { pub fn async_complete( callable: impl Callable, _: &dyn askama::Values, - ci: &ComponentInterface, + ci: &KotlinCi<'filter>, ) -> Result { let ffi_func = callable.ffi_rust_future_complete(ci); let call = format!("UniffiLib.{ffi_func}(future, continuation)"); @@ -1018,9 +1080,9 @@ mod filters { FfiType::RustBuffer(Some(external_meta)) if external_meta.crate_name() != ci.crate_name() => { - let ExternalFfiMetadata { name, .. } = external_meta; - let suffix = KotlinCodeOracle.class_name(ci, &name); - format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity.toULong(), it.len.toULong(), it.data) }}") + let ExternalFfiMetadata { module_path, .. } = external_meta; + let package = ci.package_for_module_path(&module_path); + format!("{call}.let {{ {package}.RustBuffer.create(it.capacity.toULong(), it.len.toULong(), it.data) }}") } _ => call, }, @@ -1033,7 +1095,7 @@ mod filters { pub fn async_free( callable: impl Callable, _: &dyn askama::Values, - ci: &ComponentInterface, + ci: &KotlinCi<'filter>, ) -> Result { let ffi_func = callable.ffi_rust_future_free(ci); Ok(format!("{{ future -> UniffiLib.{ffi_func}(future) }}")) diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs index 5a4305d1..5a7087c2 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs @@ -2,33 +2,38 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use super::CodeType; -use crate::{interface::ObjectImpl, ComponentInterface}; +use super::{CodeType, KotlinCi}; +use crate::interface::Type; #[derive(Debug)] pub struct ObjectCodeType { - name: String, - imp: ObjectImpl, + ty: Type, } impl ObjectCodeType { - pub fn new(name: String, imp: ObjectImpl) -> Self { - Self { name, imp } + pub fn new(ty: Type) -> Self { + Self { ty } } } impl CodeType for ObjectCodeType { - fn type_label(&self, ci: &ComponentInterface) -> String { - super::KotlinCodeOracle.class_name(ci, &self.name) + fn type_label(&self, ci: &KotlinCi<'_>) -> String { + ci.named_type_label(&self.ty) } - fn canonical_name(&self) -> String { - format!("Type{}", self.name) + fn canonical_name(&self, ci: &KotlinCi<'_>) -> String { + ci.named_canonical_name(&self.ty) + } + + fn ffi_converter_name(&self, ci: &KotlinCi<'_>) -> String { + ci.named_ffi_converter_name(&self.ty) } fn initialization_fn(&self) -> Option { - self.imp - .has_callback_interface() - .then(|| format!("uniffiCallbackInterface{}.register", self.name)) + let Type::Object { name, imp, .. } = &self.ty else { + unreachable!("an object type") + }; + imp.has_callback_interface() + .then(|| format!("uniffiCallbackInterface{name}.register")) } } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs index c2aebd49..30dcdc7c 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs @@ -2,14 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use super::CodeType; +use super::{CodeType, KotlinCi}; use crate::{ bail, - interface::{ComponentInterface, DefaultValue, Literal, Radix, Type}, + interface::{DefaultValue, Literal, Radix, Type}, Result, }; -fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> Result { +fn render_literal(literal: &Literal) -> Result { fn typed_number(type_: &Type, num_str: String) -> Result { let unwrapped_type = match type_ { Type::Optional { inner_type } => inner_type, @@ -60,18 +60,18 @@ macro_rules! impl_code_type_for_primitive { pub struct $T; impl CodeType for $T { - fn type_label(&self, _ci: &ComponentInterface) -> String { + fn type_label(&self, _ci: &KotlinCi<'_>) -> String { format!("kotlin.{}", $class_name) } - fn canonical_name(&self) -> String { + fn canonical_name(&self, _ci: &KotlinCi<'_>) -> String { $class_name.into() } - fn default(&self, default: &DefaultValue, ci: &ComponentInterface) -> Result { + fn default(&self, default: &DefaultValue, _ci: &KotlinCi<'_>) -> Result { match default { DefaultValue::Default => Ok($def.into()), - DefaultValue::Literal(literal) => render_literal(&literal, ci), + DefaultValue::Literal(literal) => render_literal(&literal), } } } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs index 17781c22..34e519e7 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs @@ -2,26 +2,30 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use super::CodeType; -use crate::ComponentInterface; +use super::{CodeType, KotlinCi}; +use crate::interface::Type; #[derive(Debug)] pub struct RecordCodeType { - id: String, + ty: Type, } impl RecordCodeType { - pub fn new(id: String) -> Self { - Self { id } + pub fn new(ty: Type) -> Self { + Self { ty } } } impl CodeType for RecordCodeType { - fn type_label(&self, ci: &ComponentInterface) -> String { - super::KotlinCodeOracle.class_name(ci, &self.id) + fn type_label(&self, ci: &KotlinCi<'_>) -> String { + ci.named_type_label(&self.ty) } - fn canonical_name(&self) -> String { - format!("Type{}", self.id) + fn canonical_name(&self, ci: &KotlinCi<'_>) -> String { + ci.named_canonical_name(&self.ty) + } + + fn ffi_converter_name(&self, ci: &KotlinCi<'_>) -> String { + ci.named_ffi_converter_name(&self.ty) } } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs index c7673882..88cfc1d8 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs @@ -2,8 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use super::{AsCodeType, CodeType, KotlinCodeOracle}; -use crate::interface::{ComponentInterface, Variant}; +use super::{AsCodeType, CodeType, KotlinCi, KotlinCodeOracle}; +use crate::interface::Variant; #[derive(Debug)] pub(super) struct VariantCodeType { @@ -11,11 +11,11 @@ pub(super) struct VariantCodeType { } impl CodeType for VariantCodeType { - fn type_label(&self, ci: &ComponentInterface) -> String { + fn type_label(&self, ci: &KotlinCi<'_>) -> String { KotlinCodeOracle.class_name(ci, self.v.name()) } - fn canonical_name(&self) -> String { + fn canonical_name(&self, _ci: &KotlinCi<'_>) -> String { self.v.name().to_string() } } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt index 324a4181..f58662f2 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt @@ -67,7 +67,7 @@ internal inline fun uniffiTraitInterfaceCallAsync( handleError( UniffiRustCallStatus.create( UNIFFI_CALL_UNEXPECTED_ERROR, - {{ Type::String.borrow()|lower_fn }}(e.toString()), + {{ Type::String.borrow()|lower_fn(ci) }}(e.toString()), ) ) return@coroutineBlock @@ -104,7 +104,7 @@ internal inline fun uniffiTraitInterfaceCallAsyncWithEr handleError( UniffiRustCallStatus.create( UNIFFI_CALL_UNEXPECTED_ERROR, - {{ Type::String.borrow()|lower_fn }}(e.toString()), + {{ Type::String.borrow()|lower_fn(ci) }}(e.toString()), ) ) } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt index e8ced2fd..afed6913 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt @@ -19,7 +19,7 @@ internal object {{ trait_impl }} { val makeCall = {% if meth.is_async() %}suspend {% endif %}{ -> uniffiObj.{{ meth.name()|fn_name() }}( {%- for arg in meth.arguments() %} - {{ arg|lift_fn }}({{ arg.name()|var_name }}), + {{ arg|lift_fn(ci) }}({{ arg.name()|var_name }}), {%- endfor %} ) } @@ -27,7 +27,7 @@ internal object {{ trait_impl }} { {%- match meth.return_type() %} {%- when Some(return_type) %} - val writeReturn = { value: {{ return_type|type_name(ci) }} -> uniffiOutReturn.setValue({{ return_type|lower_fn }}(value)) } + val writeReturn = { value: {{ return_type|type_name(ci) }} -> uniffiOutReturn.setValue({{ return_type|lower_fn(ci) }}(value)) } {%- when None %} val writeReturn = { _: Unit -> Unit } {%- endmatch %} @@ -40,7 +40,7 @@ internal object {{ trait_impl }} { uniffiCallStatus, makeCall, writeReturn, - { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) } + { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn(ci) }}(e) } ) {%- endmatch %} @@ -48,7 +48,7 @@ internal object {{ trait_impl }} { val uniffiHandleSuccess = { {% if meth.return_type().is_some() %}returnValue{% else %}_{% endif %}: {% match meth.return_type() %}{%- when Some(return_type) %}{{ return_type|type_name(ci) }}{%- when None %}Unit{% endmatch %} -> val uniffiResult = {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}.UniffiByValue( {%- if let Some(return_type) = meth.return_type() %} - {{ return_type|lower_fn }}(returnValue), + {{ return_type|lower_fn(ci) }}(returnValue), {%- endif %} UniffiRustCallStatus.ByValue() ) @@ -80,7 +80,7 @@ internal object {{ trait_impl }} { makeCall, uniffiHandleSuccess, uniffiHandleError, - { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) }, + { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn(ci) }}(e) }, uniffiOutDroppedCallback ) {%- endmatch %} diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt index 57575781..4de84663 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt @@ -4,7 +4,7 @@ {#- Define the type using typealiases to the builtin #} {%- call kt::docstring_value(docstring, 0) %}{% endcall %} public typealias {{ type_name }} = {{ builtin|type_name(ci) }} -public typealias {{ ffi_converter_name }} = {{ builtin|ffi_converter_name }} +public typealias {{ ffi_converter_name }} = {{ builtin|ffi_converter_name(ci) }} {%- when Some(config) %} @@ -31,28 +31,28 @@ public typealias {{ type_name }} = {{ concrete_type_name }} */ public object {{ ffi_converter_name }}: FfiConverter<{{ type_name }}, {{ ffi_type_name }}> { override fun lift(value: {{ ffi_type_name }}): {{ type_name }} { - val builtinValue = {{ builtin|lift_fn }}(value) + val builtinValue = {{ builtin|lift_fn(ci) }}(value) return {{ config.lift("builtinValue") }} } override fun lower(value: {{ type_name }}): {{ ffi_type_name }} { val builtinValue = {{ config.lower("value") }} - return {{ builtin|lower_fn }}(builtinValue) + return {{ builtin|lower_fn(ci) }}(builtinValue) } override fun read(buf: ByteBuffer): {{ type_name }} { - val builtinValue = {{ builtin|read_fn }}(buf) + val builtinValue = {{ builtin|read_fn(ci) }}(buf) return {{ config.lift("builtinValue") }} } override fun allocationSize(value: {{ type_name }}): ULong { val builtinValue = {{ config.lower("value") }} - return {{ builtin|allocation_size_fn }}(builtinValue) + return {{ builtin|allocation_size_fn(ci) }}(builtinValue) } override fun write(value: {{ type_name }}, buf: ByteBuffer) { val builtinValue = {{ config.lower("value") }} - {{ builtin|write_fn }}(builtinValue, buf) + {{ builtin|write_fn(ci) }}(builtinValue, buf) } } {%- endmatch %} diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt index 287a5b43..eee20bc7 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt @@ -46,7 +46,7 @@ enum class {{ type_name }}(val value: {{ variant_discr_type|type_name(ci) }}) { /** * @suppress */ -public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { +public object {{ e|ffi_converter_name(ci) }}: FfiConverterRustBuffer<{{ type_name }}> { override fun read(buf: ByteBuffer) = try { {% if config.use_enum_entries() %} {{ type_name }}.entries[buf.getInt() - 1] @@ -80,7 +80,7 @@ sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% e data class {{ variant|type_name(ci) }}( {%- for field in variant.fields() -%} {%- call kt::docstring(field, 8) %}{% endcall %} - val {% call kt::field_name(field, loop.index) %}{% endcall %}: {{ field|qualified_type_name(ci, config) }} + val {% call kt::field_name(field, loop.index) %}{% endcall %}: {{ field|qualified_type_name(ci) }} {%- if let Some(default) = field.default_value() %} = {{ default|render_default(field, ci) }} {% endif %} {%- if loop.last %}{% else %}, {% endif %} {%- endfor -%} @@ -128,13 +128,13 @@ sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% e /** * @suppress */ -public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}>{ +public object {{ e|ffi_converter_name(ci) }} : FfiConverterRustBuffer<{{ type_name }}>{ override fun read(buf: ByteBuffer): {{ type_name }} { return when(buf.getInt()) { {%- for variant in e.variants() %} {{ loop.index }} -> {{ type_name }}.{{ variant|type_name(ci) }}{% if variant.has_fields() %}( {% for field in variant.fields() -%} - {{ field|read_fn }}(buf), + {{ field|read_fn(ci) }}(buf), {% endfor -%} ){%- endif -%} {%- endfor %} @@ -149,7 +149,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } ( 4UL {%- for field in variant.fields() %} - + {{ field|allocation_size_fn }}(value.{%- call kt::field_name(field, loop.index) %}{% endcall -%}) + + {{ field|allocation_size_fn(ci) }}(value.{%- call kt::field_name(field, loop.index) %}{% endcall -%}) {%- endfor %} ) } @@ -162,7 +162,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } is {{ type_name }}.{{ variant|type_name(ci) }} -> { buf.putInt({{ loop.index }}) {%- for field in variant.fields() %} - {{ field|write_fn }}(value.{%- call kt::field_name(field, loop.index) %}{% endcall -%}, buf) + {{ field|write_fn(ci) }}(value.{%- call kt::field_name(field, loop.index) %}{% endcall -%}, buf) {%- endfor %} Unit } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt index 57aec8e5..d164638e 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt @@ -1,6 +1,6 @@ {%- let type_name = type_|type_name(ci) %} -{%- let ffi_converter_name = type_|ffi_converter_name %} -{%- let canonical_type_name = type_|canonical_name %} +{%- let ffi_converter_name = type_|ffi_converter_name(ci) %} +{%- let canonical_type_name = type_|canonical_name(ci) %} {% if e.is_flat() %} {%- call kt::docstring(e, 0) %}{% endcall %} @@ -71,12 +71,12 @@ sealed class {{ type_name }}: kotlin.Exception(){% if contains_object_references /** * @suppress */ -public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}> { +public object {{ e|ffi_converter_name(ci) }} : FfiConverterRustBuffer<{{ type_name }}> { override fun read(buf: ByteBuffer): {{ type_name }} { {% if e.is_flat() %} return when(buf.getInt()) { {%- for variant in e.variants() %} - {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant_name }}({{ Type::String.borrow()|read_fn }}(buf)) + {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant_name }}({{ Type::String.borrow()|read_fn(ci) }}(buf)) {%- endfor %} else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } @@ -86,7 +86,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } {%- for variant in e.variants() %} {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant_name }}({% if variant.has_fields() %} {% for field in variant.fields() -%} - {{ field|read_fn }}(buf), + {{ field|read_fn(ci) }}(buf), {% endfor -%} {%- endif -%}) {%- endfor %} @@ -105,7 +105,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } // Add the size for the Int that specifies the variant plus the size needed for all fields 4UL {%- for field in variant.fields() %} - + {{ field|allocation_size_fn }}(value.{% call kt::field_name(field, loop.index) %}{% endcall %}) + + {{ field|allocation_size_fn(ci) }}(value.{% call kt::field_name(field, loop.index) %}{% endcall %}) {%- endfor %} ) {%- endfor %} @@ -119,7 +119,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } is {{ type_name }}.{{ variant|error_variant_name }} -> { buf.putInt({{ loop.index }}) {%- for field in variant.fields() %} - {{ field|write_fn }}(value.{% call kt::field_name(field, loop.index) %}{% endcall %}, buf) + {{ field|write_fn(ci) }}(value.{% call kt::field_name(field, loop.index) %}{% endcall %}, buf) {%- endfor %} Unit } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt index 3203d70d..53525163 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt @@ -1,21 +1,11 @@ -{%- let namespace = ci.namespace_for_module_path(module_path)? %} -{%- let package_name=self.external_type_package_name(module_path, namespace) %} -{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name|class_name(ci)) %} -{%- let fully_qualified_ffi_converter_name = "{}.FfiConverterType{}"|format(package_name, name) %} -{%- let fully_qualified_rustbuffer_name = "{}.RustBuffer"|format(package_name) %} -{%- let local_rustbuffer_name = "RustBuffer{}"|format(name) %} +{%- if ci.is_used_as_error(type_) %} +{%- let type_name = type_|type_name(ci) %} +{%- let package_name = ci.package_for_module_path(module_path) %} -{{- self.add_import(fully_qualified_type_name) }} -{{- self.add_import(fully_qualified_ffi_converter_name) }} -{{ self.add_import_as(fully_qualified_rustbuffer_name, local_rustbuffer_name) }} - -{%- if ci.is_name_used_as_error(name) %} -{%- let class_name = name|class_name(ci) %} - -object {{ class_name }}ExternalErrorHandler : UniffiRustCallStatusErrorHandler<{{ class_name }}> { - override fun lift(error_buf: RustBuffer.ByValue): {{ class_name }} = - {{ fully_qualified_type_name }}.ErrorHandler.lift( - {{ local_rustbuffer_name }}.ByValue().apply { +object {{ type_|canonical_name(ci) }}ExternalErrorHandler : UniffiRustCallStatusErrorHandler<{{ type_name }}> { + override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = + {{ type_name }}.ErrorHandler.lift( + {{ package_name }}.RustBuffer.ByValue().apply { capacity = error_buf.capacity len = error_buf.len data = error_buf.data diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt index 61863376..1fc5297c 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt @@ -68,7 +68,7 @@ private fun uniffiCheckCallStatus(errorHandler: UniffiRustC // with the message. but if that code panics, then it just sends back // an empty buffer. if (status.error_buf.len > 0) { - throw InternalException({{ Type::String.borrow()|lift_fn }}(status.error_buf)) + throw InternalException({{ Type::String.borrow()|lift_fn(ci) }}(status.error_buf)) } else { throw InternalException("Rust panic") } @@ -104,7 +104,7 @@ internal inline fun uniffiTraitInterfaceCall( } catch(e: kotlin.Exception) { val err = try { e.stackTraceToString() } catch(_: Throwable) { "" } callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR - callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(err) + callStatus.error_buf = {{ Type::String.borrow()|lower_fn(ci) }}(err) } } @@ -123,7 +123,7 @@ internal inline fun uniffiTraitInterfaceCallWithError( } else { val err = try { e.stackTraceToString() } catch(_: Throwable) { "" } callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR - callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(err) + callStatus.error_buf = {{ Type::String.borrow()|lower_fn(ci) }}(err) } } } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt index 459c932a..1ed85b20 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt @@ -9,8 +9,8 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer(len) { repeat(len) { - val k = {{ key_type|read_fn }}(buf) - val v = {{ value_type|read_fn }}(buf) + val k = {{ key_type|read_fn(ci) }}(buf) + val v = {{ value_type|read_fn(ci) }}(buf) this[k] = v } } @@ -19,8 +19,8 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer): ULong { val spaceForMapSize = 4UL val spaceForChildren = value.map { (k, v) -> - {{ key_type|allocation_size_fn }}(k) + - {{ value_type|allocation_size_fn }}(v) + {{ key_type|allocation_size_fn(ci) }}(k) + + {{ value_type|allocation_size_fn(ci) }}(v) }.sum() return spaceForMapSize + spaceForChildren } @@ -31,8 +31,8 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer - {{ key_type|write_fn }}(k, buf) - {{ value_type|write_fn }}(v, buf) + {{ key_type|write_fn(ci) }}(k, buf) + {{ value_type|write_fn(ci) }}(v, buf) } } } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt index 75e628c8..0ccbba81 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt @@ -98,7 +98,7 @@ internal object UniffiLib { init { Native.register(UniffiLib::class.java, findLibraryName(componentName = "{{ ci.namespace() }}")) - {% for fn_item in self.initialization_fns(ci) -%} + {% for fn_item in self.initialization_fns() -%} {{ fn_item }} {% endfor %} } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt index 7be1645e..66a80636 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt @@ -100,8 +100,8 @@ {%- let methods = obj.methods() %} {%- let uniffi_trait_methods = obj.uniffi_trait_methods() %} {%- let interface_docstring = obj.docstring() %} -{%- let is_error = ci.is_name_used_as_error(name) %} -{%- let ffi_converter_name = obj|ffi_converter_name %} +{%- let is_error = ci.is_used_as_error(obj) %} +{%- let ffi_converter_name = obj|ffi_converter_name(ci) %} {%- include "Interface.kt" %} diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt index aa46ab30..4a589764 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt @@ -8,14 +8,14 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_nam if (buf.get().toInt() == 0) { return null } - return {{ inner_type|read_fn }}(buf) + return {{ inner_type|read_fn(ci) }}(buf) } override fun allocationSize(value: {{ inner_type_name }}?): ULong { if (value == null) { return 1UL } else { - return 1UL + {{ inner_type|allocation_size_fn }}(value) + return 1UL + {{ inner_type|allocation_size_fn(ci) }}(value) } } @@ -24,7 +24,7 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_nam buf.put(0) } else { buf.put(1) - {{ inner_type|write_fn }}(value, buf) + {{ inner_type|write_fn(ci) }}(value, buf) } } } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt index 1307587b..42439e06 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt @@ -48,12 +48,12 @@ class {{ type_name }} { /** * @suppress */ -public object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { +public object {{ rec|ffi_converter_name(ci) }}: FfiConverterRustBuffer<{{ type_name }}> { override fun read(buf: ByteBuffer): {{ type_name }} { {%- if rec.has_fields() %} return {{ type_name }}( {%- for field in rec.fields() %} - {{ field|read_fn }}(buf), + {{ field|read_fn(ci) }}(buf), {%- endfor %} ) {%- else %} @@ -63,13 +63,13 @@ public object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name override fun allocationSize(value: {{ type_name }}) = {%- if rec.has_fields() %} ( {%- for field in rec.fields() %} - {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif %} + {{ field|allocation_size_fn(ci) }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif %} {%- endfor %} ) {%- else %} 0UL {%- endif %} override fun write(value: {{ type_name }}, buf: ByteBuffer) { {%- for field in rec.fields() %} - {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {{ field|write_fn(ci) }}(value.{{ field.name()|var_name }}, buf) {%- endfor %} } } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt index dab8f22d..7ec0b69e 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt @@ -7,20 +7,20 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer { val len = buf.getInt() return List<{{ inner_type_name }}>(len) { - {{ inner_type|read_fn }}(buf) + {{ inner_type|read_fn(ci) }}(buf) } } override fun allocationSize(value: List<{{ inner_type_name }}>): ULong { val sizeForLength = 4UL - val sizeForItems = value.map { {{ inner_type|allocation_size_fn }}(it) }.sum() + val sizeForItems = value.map { {{ inner_type|allocation_size_fn(ci) }}(it) }.sum() return sizeForLength + sizeForItems } override fun write(value: List<{{ inner_type_name }}>, buf: ByteBuffer) { buf.putInt(value.size) value.iterator().forEach { - {{ inner_type|write_fn }}(it, buf) + {{ inner_type|write_fn(ci) }}(it, buf) } } } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/SetTemplate.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/SetTemplate.kt index 4eb2e327..221364d1 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/SetTemplate.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/SetTemplate.kt @@ -8,21 +8,21 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer(len) { repeat(len) { - add({{ inner_type|read_fn }}(buf)) + add({{ inner_type|read_fn(ci) }}(buf)) } } } override fun allocationSize(value: Set<{{ inner_type_name }}>): ULong { val spaceForLength = 4UL - val spaceForItems = value.map { {{ inner_type|allocation_size_fn }}(it) }.sum() + val spaceForItems = value.map { {{ inner_type|allocation_size_fn(ci) }}(it) }.sum() return spaceForLength + spaceForItems } override fun write(value: Set<{{ inner_type_name }}>, buf: ByteBuffer) { buf.putInt(value.size) value.iterator().forEach { - {{ inner_type|write_fn }}(it, buf) + {{ inner_type|write_fn(ci) }}(it, buf) } } } diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt index 408225ca..b13e945b 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt @@ -86,8 +86,8 @@ object NoHandle {%- for type_ in ci.iter_local_types() %} {%- let type_name = type_|type_name(ci) %} -{%- let ffi_converter_name = type_|ffi_converter_name %} -{%- let canonical_type_name = type_|canonical_name %} +{%- let ffi_converter_name = type_|ffi_converter_name(ci) %} +{%- let canonical_type_name = type_|canonical_name(ci) %} {%- let contains_object_references = ci.item_contains_object_references(type_) %} {# @@ -142,7 +142,7 @@ object NoHandle {%- when Type::Enum { name, module_path } %} {%- let e = ci.get_enum_definition(name).unwrap() %} -{%- if !ci.is_name_used_as_error(name) %} +{%- if !ci.is_used_as_error(type_) %} {% include "EnumTemplate.kt" %} {%- else %} {% include "ErrorTemplate.kt" %} diff --git a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt index c657d62d..515bd2d8 100644 --- a/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt +++ b/uniffi/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt @@ -19,7 +19,7 @@ {%- match func.throws_type() %} {%- when Some(e) %} {%- if ci.is_external(e) %} - uniffiRustCallWithError({{ e|type_name(ci) }}ExternalErrorHandler) + uniffiRustCallWithError({{ e|canonical_name(ci) }}ExternalErrorHandler) {%- else %} uniffiRustCallWithError({{ e|type_name(ci) }}) {%- endif %} @@ -31,7 +31,7 @@ {%- when Some(Type::Object { .. }) %} it, {%- when Some(t) %} - {{- t|lower_fn }}(this), + {{- t|lower_fn(ci) }}(this), {%- when None %} {% endmatch %} {% call arg_list_lowered(func) %}{% endcall -%} @@ -60,7 +60,7 @@ ){%- match callable.return_type() -%} {%- when Some(return_type) -%} : {{ return_type|type_name(ci) }} { - return {{ return_type|lift_fn }}({% call to_ffi_call(callable) %}{% endcall %}) + return {{ return_type|lift_fn(ci) }}({% call to_ffi_call(callable) %}{% endcall %}) } {%- when None %} = {% call to_ffi_call(callable) %}{% endcall %} @@ -81,7 +81,7 @@ }, {%- when Some(t) %} UniffiLib.{{ callable.ffi_func().name() }}( - {{- t|lower_fn }}(this), + {{- t|lower_fn(ci) }}(this), {% call arg_list_lowered(callable) %}{% endcall %} ), {%- else %} @@ -93,7 +93,7 @@ // lift function {%- match callable.return_type() %} {%- when Some(return_type) %} - { {{ return_type|lift_fn }}(it) }, + { {{ return_type|lift_fn(ci) }}(it) }, {%- when None %} { }, {% endmatch %} @@ -101,7 +101,7 @@ {%- match callable.throws_type() %} {%- when Some(e) %} {%- if ci.is_external(e) %} - {{ e|type_name(ci) }}ExternalErrorHandler, + {{ e|canonical_name(ci) }}ExternalErrorHandler, {%- else %} {{ e|type_name(ci) }}.ErrorHandler, {%- endif %} @@ -113,7 +113,7 @@ {%- macro arg_list_lowered(func) %} {%- for arg in func.arguments() %} - {{ arg|lower_fn_for_arg }}({{ arg.name()|var_name }}), + {{ arg|lower_fn_for_arg(ci) }}({{ arg.name()|var_name }}), {%- endfor %} {%- endmacro -%} @@ -190,26 +190,26 @@ v{{- field_num -}} {%- if let Some(fmt) = uniffi_trait_methods.display_fmt.or(uniffi_trait_methods.debug_fmt.clone()) %} // The local Rust `Display`/`Debug` implementation. override fun toString(): String { - return {{ fmt.return_type().unwrap()|lift_fn }}({% call to_ffi_call(fmt) %}{% endcall %}) + return {{ fmt.return_type().unwrap()|lift_fn(ci) }}({% call to_ffi_call(fmt) %}{% endcall %}) } {%- endif %} {%- if let Some(eq) = uniffi_trait_methods.eq_eq %} // The local Rust `Eq` implementation - only `eq` is used. override fun equals(other: Any?): Boolean { if (other !is {{ eq.object_name()|class_name(ci) }}) return false - return {{ eq.return_type().unwrap()|lift_fn }}({% call to_ffi_call(eq) %}{% endcall %}) + return {{ eq.return_type().unwrap()|lift_fn(ci) }}({% call to_ffi_call(eq) %}{% endcall %}) } {%- endif %} {%- if let Some(hash) = uniffi_trait_methods.hash_hash %} // The local Rust `Hash` implementation override fun hashCode(): Int { - return {{ hash.return_type().unwrap()|lift_fn }}({%- call to_ffi_call(hash) %}{% endcall %}).toInt() + return {{ hash.return_type().unwrap()|lift_fn(ci) }}({%- call to_ffi_call(hash) %}{% endcall %}).toInt() } {%- endif %} {%- if let Some(cmp) = uniffi_trait_methods.ord_cmp %} // The local Rust `Ord` implementation override fun compareTo(other: {{ cmp.object_name()|class_name(ci) }}): Int { - return {{ cmp.return_type().unwrap()|lift_fn }}({%- call to_ffi_call(cmp) %}{% endcall %}).toInt() + return {{ cmp.return_type().unwrap()|lift_fn(ci) }}({%- call to_ffi_call(cmp) %}{% endcall %}).toInt() } {%- endif %} {%- endmacro %} diff --git a/uniffi/uniffi_bindgen/src/bindings/python/pipeline/types.rs b/uniffi/uniffi_bindgen/src/bindings/python/pipeline/types.rs index 182a63a2..2feeb4b3 100644 --- a/uniffi/uniffi_bindgen/src/bindings/python/pipeline/types.rs +++ b/uniffi/uniffi_bindgen/src/bindings/python/pipeline/types.rs @@ -113,7 +113,12 @@ pub fn type_name(ty: &Type, context: &Context) -> Result { /// body (needed for Python < 3.10 which evaluates annotations eagerly). pub fn type_annotation(ty: &Type, context: &Context) -> Result { match ty { - Type::Enum { name, .. } | Type::Record { name, .. } if context.is_recursive(name) => { + Type::Enum { + namespace, name, .. + } + | Type::Record { + namespace, name, .. + } if context.module_namespace()? == namespace && context.is_recursive(name) => { Ok(format!("\"{}\"", type_name(ty, context)?)) } Type::Optional { inner_type } => Ok(format!( diff --git a/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs b/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs index b62129c2..b300e970 100644 --- a/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs +++ b/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs @@ -5,39 +5,34 @@ use super::CodeType; use crate::{ bail, - interface::{DefaultValue, Literal, Type}, + interface::{DefaultValue, Literal}, Result, }; #[derive(Debug)] pub struct OptionalCodeType { - inner: Type, + inner: Box, } impl OptionalCodeType { - pub fn new(inner: Type) -> Self { + pub fn new(inner: Box) -> Self { Self { inner } } } impl CodeType for OptionalCodeType { fn type_label(&self) -> String { - format!("{}?", super::SwiftCodeOracle.find(&self.inner).type_label()) + format!("{}?", self.inner.type_label()) } fn canonical_name(&self) -> String { - format!( - "Option{}", - super::SwiftCodeOracle.find(&self.inner).canonical_name() - ) + format!("Option{}", self.inner.canonical_name()) } fn default(&self, default: &DefaultValue) -> Result { match default { DefaultValue::Default | DefaultValue::Literal(Literal::None) => Ok("nil".into()), - DefaultValue::Literal(Literal::Some { inner }) => { - super::SwiftCodeOracle.find(&self.inner).default(inner) - } + DefaultValue::Literal(Literal::Some { inner }) => self.inner.default(inner), _ => bail!("Invalid literal for Optional type: {default:?}"), } } @@ -45,28 +40,22 @@ impl CodeType for OptionalCodeType { #[derive(Debug)] pub struct SequenceCodeType { - inner: Type, + inner: Box, } impl SequenceCodeType { - pub fn new(inner: Type) -> Self { + pub fn new(inner: Box) -> Self { Self { inner } } } impl CodeType for SequenceCodeType { fn type_label(&self) -> String { - format!( - "[{}]", - super::SwiftCodeOracle.find(&self.inner).type_label() - ) + format!("[{}]", self.inner.type_label()) } fn canonical_name(&self) -> String { - format!( - "Sequence{}", - super::SwiftCodeOracle.find(&self.inner).canonical_name() - ) + format!("Sequence{}", self.inner.canonical_name()) } fn default(&self, default: &DefaultValue) -> Result { @@ -81,30 +70,26 @@ impl CodeType for SequenceCodeType { #[derive(Debug)] pub struct MapCodeType { - key: Type, - value: Type, + key: Box, + value: Box, } impl MapCodeType { - pub fn new(key: Type, value: Type) -> Self { + pub fn new(key: Box, value: Box) -> Self { Self { key, value } } } impl CodeType for MapCodeType { fn type_label(&self) -> String { - format!( - "[{}: {}]", - super::SwiftCodeOracle.find(&self.key).type_label(), - super::SwiftCodeOracle.find(&self.value).type_label() - ) + format!("[{}: {}]", self.key.type_label(), self.value.type_label()) } fn canonical_name(&self) -> String { format!( "Dictionary{}{}", - super::SwiftCodeOracle.find(&self.key).canonical_name(), - super::SwiftCodeOracle.find(&self.value).canonical_name() + self.key.canonical_name(), + self.value.canonical_name() ) } @@ -118,28 +103,22 @@ impl CodeType for MapCodeType { #[derive(Debug)] pub struct SetCodeType { - inner: Type, + inner: Box, } impl SetCodeType { - pub fn new(inner: Type) -> Self { + pub fn new(inner: Box) -> Self { Self { inner } } } impl CodeType for SetCodeType { fn type_label(&self) -> String { - format!( - "Set<{}>", - super::SwiftCodeOracle.find(&self.inner).type_label() - ) + format!("Set<{}>", self.inner.type_label()) } fn canonical_name(&self) -> String { - format!( - "Set{}", - super::SwiftCodeOracle.find(&self.inner).canonical_name() - ) + format!("Set{}", self.inner.canonical_name()) } fn default(&self, default: &DefaultValue) -> Result { diff --git a/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs b/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs new file mode 100644 index 00000000..a3a84113 --- /dev/null +++ b/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs @@ -0,0 +1,241 @@ +/* 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::collections::{BTreeSet, HashMap}; + +use heck::ToUpperCamelCase; + +use super::{CodeType, Config}; +use crate::{ + interface::{ComponentInterface, DefaultValue, Type}, + Result, +}; + +/// The external types a crate's bindings name through their crate's Swift module. +/// +/// Swift's documented layout compiles every crate's bindings into one module, where another +/// crate's type is in scope by its bare name, so that is how external types are named. A type +/// whose name is ambiguous here (a local type, or another crate's type, has it too) instead +/// needs each crate in its own module: the bindings name it through its crate's module, which +/// they import. +#[derive(Clone, Debug, Default)] +pub struct ExternalTypes { + crate_name: String, + /// By crate name and type name. + qualified: HashMap<(String, String), Qualifier>, + /// The modules the bindings import. + imports: BTreeSet, +} + +#[derive(Clone, Debug)] +struct Qualifier { + /// The Swift module of the type's crate. + module: String, + /// The type's namespace, which tells this crate's helpers for it apart from its namesake's. + namespace: String, +} + +impl ExternalTypes { + pub fn new(config: &Config, ci: &ComponentInterface) -> Result { + let mut crates_by_name: HashMap<&str, BTreeSet<&str>> = HashMap::new(); + for ty in ci.iter_external_types() { + if let (Some(crate_name), Some(name)) = (ty.crate_name(), ty.name()) { + crates_by_name.entry(name).or_default().insert(crate_name); + } + } + + let mut qualified = HashMap::new(); + for (name, crates) in crates_by_name { + if crates.len() == 1 && ci.get_type(name).is_none() { + continue; + } + for crate_name in crates { + let namespace = ci.namespace_for_module_path(crate_name)?.to_string(); + let module = config.external_module_name(crate_name, &namespace); + qualified.insert( + (crate_name.to_string(), name.to_string()), + Qualifier { module, namespace }, + ); + } + } + // Once any crate needs its own module, every crate is one, so the bindings import each + // crate they use, not only those they name through their module. + let imports = if config.separate_modules { + ci.iter_external_types() + .filter_map(|ty| ty.crate_name()) + .map(|crate_name| { + let namespace = ci.namespace_for_module_path(crate_name)?; + Ok(config.external_module_name(crate_name, namespace)) + }) + .collect::>()? + } else { + qualified.values().map(|q| q.module.clone()).collect() + }; + Ok(Self { + crate_name: ci.crate_name().to_string(), + qualified, + imports, + }) + } + + /// Whether some external type is named through its crate's module. + pub fn is_empty(&self) -> bool { + self.qualified.is_empty() + } + + /// The modules the bindings import. + pub fn modules(&self) -> BTreeSet { + self.imports.clone() + } + + /// The module to qualify `ty` with, if it is an ambiguous external type. + pub fn module(&self, ty: &Type) -> Option<&str> { + self.qualifier(ty).map(|q| q.module.as_str()) + } + + /// Whether `ty` is a local type with the name of an external type the bindings import. + pub fn is_shadowing(&self, ty: &Type) -> bool { + ty.crate_name() == Some(&self.crate_name) + && ty + .name() + .is_some_and(|name| self.qualified.keys().any(|(_, n)| n == name)) + } + + /// Wrap the code type of `ty`, if it is an ambiguous external type. + pub fn wrap(&self, ty: &Type, code_type: Box) -> Box { + match self.qualifier(ty) { + Some(qualifier) => Box::new(QualifiedCodeType { + is_record: matches!(ty, Type::Record { .. }), + qualifier: qualifier.clone(), + inner: code_type, + }), + None => code_type, + } + } + + fn qualifier(&self, ty: &Type) -> Option<&Qualifier> { + let key = (ty.crate_name()?.to_string(), ty.name()?.to_string()); + self.qualified.get(&key) + } +} + +/// An external type named through its crate's module. +#[derive(Debug)] +struct QualifiedCodeType { + is_record: bool, + qualifier: Qualifier, + inner: Box, +} + +impl CodeType for QualifiedCodeType { + fn type_label(&self) -> String { + format!("{}.{}", self.qualifier.module, self.inner.type_label()) + } + + // Only this crate's helpers for compound types use this, so it needs no module, but it + // must differ from the canonical name of the local namesake. + fn canonical_name(&self) -> String { + format!( + "{}{}", + self.qualifier.namespace.to_upper_camel_case(), + self.inner.canonical_name() + ) + } + + fn default(&self, default: &DefaultValue) -> Result { + match default { + DefaultValue::Default if self.is_record => Ok(format!("{}()", self.type_label())), + _ => self.inner.default(default), + } + } + + fn ffi_converter_name(&self) -> String { + format!( + "{}.{}", + self.qualifier.module, + self.inner.ffi_converter_name() + ) + } + + fn initialization_fn(&self) -> Option { + self.inner.initialization_fn() + } +} + +#[cfg(test)] +mod test { + use super::super::needs_separate_modules; + use super::*; + use uniffi_meta::{ + FieldMetadata, FnMetadata, FnParamMetadata, Metadata, MetadataGroup, NamespaceMetadata, + RecordMetadata, + }; + + fn record(module_path: &str, name: &str) -> Type { + Type::Record { + module_path: module_path.to_string(), + name: name.to_string(), + } + } + + fn namespace(name: &str) -> NamespaceMetadata { + NamespaceMetadata { + crate_name: name.to_string(), + name: name.to_string(), + } + } + + // `media` has its own `Frame` and uses `net`'s, plus `extra`'s unambiguous `Other`. + #[test] + fn separate_modules_import_every_crate() { + let frame = RecordMetadata { + module_path: "media".to_string(), + name: "Frame".to_string(), + orig_name: None, + remote: false, + fields: vec![FieldMetadata { + name: "raw".to_string(), + orig_name: None, + ty: record("net", "Frame"), + default: None, + docstring: None, + }], + docstring: None, + }; + let wrap = FnMetadata { + module_path: "media".to_string(), + name: "wrap".to_string(), + orig_name: None, + is_async: false, + inputs: vec![FnParamMetadata::simple("other", record("extra", "Other"))], + return_type: Some(record("media", "Frame")), + throws: None, + checksum: None, + docstring: None, + }; + let mut ci = ComponentInterface::from_metadata(MetadataGroup { + namespace: namespace("media"), + namespace_docstring: None, + items: [Metadata::Record(frame), Metadata::Func(wrap)].into(), + }) + .unwrap(); + ci.set_crate_to_namespace_map( + ["media", "net", "extra"] + .into_iter() + .map(|name| (name.to_string(), namespace(name))) + .collect(), + ); + let mut config = Config::default(); + // `net`'s `Frame` is ambiguous, so every crate needs its own module. + assert!(needs_separate_modules(&config, &ci).unwrap()); + + // `Other` is named unqualified, so `extra` is imported along with `net`. + config.separate_modules = true; + let external = ExternalTypes::new(&config, &ci).unwrap(); + assert_eq!( + external.modules(), + BTreeSet::from(["extra".to_string(), "net".to_string()]) + ); + } +} diff --git a/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index 96fd705c..4b0c2adf 100644 --- a/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -15,6 +15,7 @@ use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use super::Bindings; +use external::ExternalTypes; use crate::interface::*; @@ -22,6 +23,7 @@ mod callback_interface; mod compounds; mod custom; mod enum_; +mod external; mod miscellany; mod object; mod primitives; @@ -186,6 +188,13 @@ pub struct Config { pub(super) exclude: Vec, #[serde(default)] pub(super) rename: toml::Table, + /// The Swift module of each crate in the library, by crate name. + #[serde(skip)] + pub(super) external_modules: HashMap, + /// Whether some crate in the library names an external type through its module, which + /// puts every crate's bindings in their own module. + #[serde(skip)] + pub(super) separate_modules: bool, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -229,6 +238,14 @@ impl Config { .clone() } + /// The name of the Swift module with another crate's bindings. + fn external_module_name(&self, crate_name: &str, namespace: &str) -> String { + match self.external_modules.get(crate_name) { + Some(name) => name.clone(), + None => namespace.to_string(), + } + } + /// The name of the lower-level C module containing the FFI declarations. pub fn ffi_module_name(&self) -> String { match self.ffi_module_name.as_ref() { @@ -488,7 +505,11 @@ impl Config { // Given a trait, work out what the protocol name we generate for it. // This differs based on whether the trait supports foreign impls (ie, // whether is has a "callback interface". -fn trait_protocol_name(ci: &ComponentInterface, trait_ty: &Type) -> Result { +fn trait_protocol_name( + ci: &ComponentInterface, + external_types: &ExternalTypes, + trait_ty: &Type, +) -> Result { let Some(module_path) = &trait_ty.crate_name() else { anyhow::bail!("Invalid trait_type: {trait_ty:?}"); }; @@ -510,11 +531,15 @@ fn trait_protocol_name(ci: &ComponentInterface, trait_ty: &Type) -> Result format!("{module}.{protocol_name}"), + None => protocol_name, + }) } /// Generate UniFFI component bindings for Swift, as strings in memory. @@ -522,8 +547,8 @@ pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result Result Result { + Ok(!ExternalTypes::new(config, ci)?.is_empty()) +} + +/// The Swift modules of other crates that a component's bindings import, which means each crate +/// must be its own module. +#[cfg(feature = "bindgen-tests")] +pub fn imported_modules(config: &Config, ci: &ComponentInterface) -> Result> { + Ok(ExternalTypes::new(config, ci)?.modules()) +} + /// Generate the bridging header for a component pub fn generate_header(config: &Config, ci: &ComponentInterface) -> Result { BridgingHeader::new(config, ci) @@ -550,8 +588,8 @@ pub fn generate_header(config: &Config, ci: &ComponentInterface) -> Result Result { - SwiftWrapper::new(config.clone(), ci) - .render() + SwiftWrapper::new(config.clone(), ci)? + .render_swift() .context("failed to render Swift library") } @@ -581,15 +619,21 @@ pub fn generate_modulemap( pub struct TypeRenderer<'a> { config: &'a Config, ci: &'a ComponentInterface, + external_types: &'a ExternalTypes, // Track imports added with the `add_import()` macro imports: RefCell>, } impl<'a> TypeRenderer<'a> { - fn new(config: &'a Config, ci: &'a ComponentInterface) -> Self { + fn new( + config: &'a Config, + ci: &'a ComponentInterface, + external_types: &'a ExternalTypes, + ) -> Self { Self { config, ci, + external_types, imports: RefCell::new(BTreeSet::new()), } } @@ -660,25 +704,33 @@ impl ModuleMap { pub struct SwiftWrapper<'a> { config: Config, ci: &'a ComponentInterface, + external_types: ExternalTypes, type_helper_code: String, type_imports: BTreeSet, ensure_init_fn_name: String, } impl<'a> SwiftWrapper<'a> { - pub fn new(config: Config, ci: &'a ComponentInterface) -> Self { - let type_renderer = TypeRenderer::new(&config, ci); - let type_helper_code = type_renderer.render().unwrap(); - let type_imports = type_renderer.imports.into_inner(); - Self { + pub fn new(config: Config, ci: &'a ComponentInterface) -> Result { + let external_types = ExternalTypes::new(&config, ci)?; + let type_renderer = TypeRenderer::new(&config, ci, &external_types); + let type_helper_code = render_with_external_types(&type_renderer, &external_types)?; + let mut type_imports = type_renderer.imports.into_inner(); + type_imports.extend(external_types.modules()); + Ok(Self { config, ci, + external_types, type_helper_code, type_imports, ensure_init_fn_name: format!( "uniffiEnsure{}Initialized", ci.crate_name().to_upper_camel_case() ), - } + }) + } + + fn render_swift(&self) -> askama::Result { + render_with_external_types(self, &self.external_types) } pub fn imports(&self) -> Vec { @@ -689,7 +741,7 @@ impl<'a> SwiftWrapper<'a> { let init_fns = self .ci .iter_local_types() - .map(|t| SwiftCodeOracle.find(t)) + .map(|t| SwiftCodeOracle.find(t, &self.external_types)) .filter_map(|ct| ct.initialization_fn()); // Also call global initialization function for any external type we use. @@ -712,6 +764,16 @@ impl<'a> SwiftWrapper<'a> { } } +/// The key of the [`ExternalTypes`] the filters name types with. +const EXTERNAL_TYPES: &str = "external_types"; + +fn render_with_external_types( + template: &impl Template, + external_types: &ExternalTypes, +) -> askama::Result { + template.render_with_values(&(EXTERNAL_TYPES, external_types as &dyn std::any::Any)) +} + #[derive(Clone)] pub struct SwiftCodeOracle; @@ -723,9 +785,8 @@ impl SwiftCodeOracle { // // - When adding additional types here, make sure to also add a match arm to the `Types.swift` template. // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches - #[allow(clippy::only_used_in_recursion)] - fn create_code_type(&self, type_: Type) -> Box { - match type_ { + fn create_code_type(&self, type_: Type, external_types: &ExternalTypes) -> Box { + let code_type: Box = match type_.clone() { Type::UInt8 => Box::new(primitives::UInt8CodeType), Type::Int8 => Box::new(primitives::Int8CodeType), Type::UInt16 => Box::new(primitives::UInt16CodeType), @@ -749,27 +810,33 @@ impl SwiftCodeOracle { Type::CallbackInterface { name, .. } => { Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) } - Type::Optional { inner_type } => { - Box::new(compounds::OptionalCodeType::new(*inner_type)) - } - Type::Sequence { inner_type } => { - Box::new(compounds::SequenceCodeType::new(*inner_type)) - } + Type::Optional { inner_type } => Box::new(compounds::OptionalCodeType::new( + self.create_code_type(*inner_type, external_types), + )), + Type::Sequence { inner_type } => Box::new(compounds::SequenceCodeType::new( + self.create_code_type(*inner_type, external_types), + )), Type::Map { key_type, value_type, - } => Box::new(compounds::MapCodeType::new(*key_type, *value_type)), - Type::Set { inner_type } => Box::new(compounds::SetCodeType::new(*inner_type)), + } => Box::new(compounds::MapCodeType::new( + self.create_code_type(*key_type, external_types), + self.create_code_type(*value_type, external_types), + )), + Type::Set { inner_type } => Box::new(compounds::SetCodeType::new( + self.create_code_type(*inner_type, external_types), + )), Type::Custom { name, builtin, .. } => Box::new(custom::CustomCodeType::new( name, - self.create_code_type(*builtin), + self.create_code_type(*builtin, external_types), )), - Type::Box { inner_type } => self.create_code_type(*inner_type), - } + Type::Box { inner_type } => self.create_code_type(*inner_type, external_types), + }; + external_types.wrap(&type_, code_type) } - fn find(&self, type_: &Type) -> Box { - self.create_code_type(type_.clone()) + fn find(&self, type_: &Type, external_types: &ExternalTypes) -> Box { + self.create_code_type(type_.clone(), external_types) } /// Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc). @@ -889,21 +956,27 @@ pub mod filters { &SwiftCodeOracle } + /// The code type of `ty`, named with the external types passed to the render. + fn find(ty: &Type, values: &dyn askama::Values) -> Result, askama::Error> { + let external_types = askama::get_value::(values, EXTERNAL_TYPES)?; + Ok(oracle().find(ty, external_types)) + } + #[askama::filter_fn] pub fn type_name( as_type: &impl AsType, - _: &dyn askama::Values, + values: &dyn askama::Values, ) -> Result { - Ok(oracle().find(&as_type.as_type()).type_label()) + Ok(find(&as_type.as_type(), values)?.type_label()) } #[askama::filter_fn] pub fn return_type_name( as_type: Option<&impl AsType>, - _: &dyn askama::Values, + values: &dyn askama::Values, ) -> Result { Ok(match as_type { - Some(as_type) => oracle().find(&as_type.as_type()).type_label(), + Some(as_type) => find(&as_type.as_type(), values)?.type_label(), None => "()".to_owned(), }) } @@ -911,30 +984,39 @@ pub mod filters { #[askama::filter_fn] pub fn canonical_name( as_type: &impl AsType, - _: &dyn askama::Values, + values: &dyn askama::Values, ) -> Result { - Ok(oracle().find(&as_type.as_type()).canonical_name()) + Ok(find(&as_type.as_type(), values)?.canonical_name()) } #[askama::filter_fn] pub fn ffi_converter_name( as_type: &impl AsType, - _: &dyn askama::Values, + values: &dyn askama::Values, ) -> Result { - Ok(oracle().find(&as_type.as_type()).ffi_converter_name()) + Ok(find(&as_type.as_type(), values)?.ffi_converter_name()) } + /// The function that lifts an error, passed as a value to the call that may throw it. #[askama::filter_fn] - pub fn ffi_error_converter_name( + pub fn ffi_error_lift_fn( as_type: &impl AsType, - _: &dyn askama::Values, + values: &dyn askama::Values, ) -> Result { + let ty = as_type.as_type(); // special handling for types used as errors. - let mut name = oracle().find(&as_type.as_type()).ffi_converter_name(); - if matches!(&as_type.as_type(), Type::Object { .. }) { + let mut name = find(&ty, values)?.ffi_converter_name(); + if matches!(ty, Type::Object { .. }) { name.push_str("__as_error") } - Ok(name) + // A local error's public `_lift` function is an overload of its imported namesake's, + // which a function value cannot pick between, so it goes through the converter. + let external_types = askama::get_value::(values, EXTERNAL_TYPES)?; + Ok(if external_types.is_shadowing(&ty) { + format!("{name}.lift") + } else { + format!("{name}_lift") + }) } #[askama::filter_fn] @@ -951,10 +1033,10 @@ pub mod filters { #[askama::filter_fn] pub fn lower_fn( as_type: &impl AsType, - _: &dyn askama::Values, + values: &dyn askama::Values, ) -> Result { let ty = &as_type.as_type(); - let ffi_converter_name = oracle().find(ty).ffi_converter_name(); + let ffi_converter_name = find(ty, values)?.ffi_converter_name(); Ok(match ty.name() { Some(_) => format!("{}_lower", ffi_converter_name), None => format!("{}.lower", ffi_converter_name), @@ -964,18 +1046,21 @@ pub mod filters { #[askama::filter_fn] pub fn write_fn( as_type: &impl AsType, - _: &dyn askama::Values, + values: &dyn askama::Values, ) -> Result { let ty = &as_type.as_type(); - let ffi_converter_name = oracle().find(ty).ffi_converter_name(); + let ffi_converter_name = find(ty, values)?.ffi_converter_name(); Ok(format!("{}.write", ffi_converter_name)) } // See above re lower_fn - we always use the public version for named types. #[askama::filter_fn] - pub fn lift_fn(as_type: &impl AsType, _: &dyn askama::Values) -> Result { + pub fn lift_fn( + as_type: &impl AsType, + values: &dyn askama::Values, + ) -> Result { let ty = &as_type.as_type(); - let ffi_converter_name = oracle().find(ty).ffi_converter_name(); + let ffi_converter_name = find(ty, values)?.ffi_converter_name(); Ok(match ty.name() { Some(_) => format!("{}_lift", ffi_converter_name), None => format!("{}.lift", ffi_converter_name), @@ -983,20 +1068,22 @@ pub mod filters { } #[askama::filter_fn] - pub fn read_fn(as_type: &impl AsType, _: &dyn askama::Values) -> Result { + pub fn read_fn( + as_type: &impl AsType, + values: &dyn askama::Values, + ) -> Result { let ty = &as_type.as_type(); - let ffi_converter_name = oracle().find(ty).ffi_converter_name(); + let ffi_converter_name = find(ty, values)?.ffi_converter_name(); Ok(format!("{}.read", ffi_converter_name)) } #[askama::filter_fn] pub fn default_swift( default: &DefaultValue, - _: &dyn askama::Values, + values: &dyn askama::Values, as_type: &T, ) -> Result { - Ok(oracle() - .find(&as_type.as_type()) + Ok(find(&as_type.as_type(), values)? .default(default) .expect("invalid default: {default:?}")) } @@ -1112,13 +1199,13 @@ pub mod filters { /// scope (see `to_ffi_call`). For every other argument, this is the /// standard `{FfiConverter}.lower({name})` expression. #[askama::filter_fn] - pub fn arg_expr(arg: &Argument, _: &dyn askama::Values) -> Result { + pub fn arg_expr(arg: &Argument, values: &dyn askama::Values) -> Result { let name = quote_general_keyword(oracle().var_name(arg.name())); if arg.is_borrowed_bytes() { Ok(format!("{name}Fb")) } else { let ty = arg.as_type(); - let ffi_converter_name = oracle().find(&ty).ffi_converter_name(); + let ffi_converter_name = find(&ty, values)?.ffi_converter_name(); let lower = match ty.name() { Some(_) => format!("{ffi_converter_name}_lower"), None => format!("{ffi_converter_name}.lower"), diff --git a/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs b/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs index e4e7c30c..c4c7f803 100644 --- a/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs +++ b/uniffi/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs @@ -32,7 +32,9 @@ fn render_literal(literal: &Literal) -> Result { { format!( "{}({num_str})", - super::SwiftCodeOracle.find(type_).type_label() + super::SwiftCodeOracle + .find(type_, &Default::default()) + .type_label() ) } _ => bail!("Unexpected literal: {num_str} for type: {type_:?}"), diff --git a/uniffi/uniffi_bindgen/src/bindings/swift/mod.rs b/uniffi/uniffi_bindgen/src/bindings/swift/mod.rs index d5bff8fc..779fcb75 100644 --- a/uniffi/uniffi_bindgen/src/bindings/swift/mod.rs +++ b/uniffi/uniffi_bindgen/src/bindings/swift/mod.rs @@ -41,7 +41,10 @@ use std::collections::HashMap; use std::process::Command; mod gen_swift; -use gen_swift::{generate_bindings, generate_header, generate_modulemap, generate_swift, Config}; +use gen_swift::{ + generate_bindings, generate_header, generate_modulemap, generate_swift, needs_separate_modules, + Config, +}; #[cfg(feature = "bindgen-tests")] pub mod test; @@ -72,7 +75,7 @@ pub fn generate( } let cis = loader.load_cis(metadata)?; let mut components = loader.load_components(cis, parse_config)?; - apply_renames(&mut components); + apply_renames(&mut components)?; for c in components.iter_mut() { // Call derive_ffi_functions after `apply_renames` c.ci.derive_ffi_funcs()?; @@ -164,7 +167,7 @@ pub fn generate_swift_bindings(options: SwiftBindingsOptions) -> Result<()> { let metadata = loader.load_metadata(&options.source)?; let cis = loader.load_cis(metadata)?; let mut components = loader.load_components(cis, parse_config)?; - apply_renames(&mut components); + apply_renames(&mut components)?; // Call derive_ffi_funcs after apply_renames() for Component { ci, .. } in components.iter_mut() { ci.derive_ffi_funcs()?; @@ -240,7 +243,7 @@ pub struct SwiftBindingsOptions { } // A helper for renaming items. -fn apply_renames(components: &mut Vec>) { +fn apply_renames(components: &mut Vec>) -> Result<()> { // Remove excluded items, this happens before renaming for c in components.iter_mut() { apply_exclusions(&mut c.ci, &c.config.exclude); @@ -261,4 +264,23 @@ fn apply_renames(components: &mut Vec>) { rename(&mut c.ci, &module_renames); } } + + // A crate names an external type that shares its name through its crate's module. + let modules = HashMap::::from_iter( + components + .iter() + .map(|c| (c.ci.crate_name().to_string(), c.config.module_name())), + ); + for c in &mut *components { + c.config.external_modules = modules.clone(); + } + + let mut separate_modules = false; + for c in components.iter() { + separate_modules |= needs_separate_modules(&c.config, &c.ci)?; + } + for c in components { + c.config.separate_modules = separate_modules; + } + Ok(()) } diff --git a/uniffi/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/uniffi/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift index 33e00d73..a163fa03 100644 --- a/uniffi/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift +++ b/uniffi/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -3,7 +3,7 @@ {%- let methods = obj.methods() %} {%- let protocol_docstring = obj.docstring() %} -{%- let is_error = ci.is_name_used_as_error(name) %} +{%- let is_error = ci.is_used_as_error(obj) %} {% include "Protocol.swift" %} @@ -166,7 +166,7 @@ public struct {{ ffi_converter_name }}: FfiConverter { {%- endif %} {%- for t in obj.trait_impls() %} -extension {{impl_class_name}}: {{ self::trait_protocol_name(ci, t.trait_ty)? }} {} +extension {{impl_class_name}}: {{ self::trait_protocol_name(ci, self.external_types, t.trait_ty)? }} {} {% endfor %} {# diff --git a/uniffi/uniffi_bindgen/src/bindings/swift/templates/Types.swift b/uniffi/uniffi_bindgen/src/bindings/swift/templates/Types.swift index acbbb024..29912392 100644 --- a/uniffi/uniffi_bindgen/src/bindings/swift/templates/Types.swift +++ b/uniffi/uniffi_bindgen/src/bindings/swift/templates/Types.swift @@ -76,7 +76,7 @@ {%- when Type::Enum { name, module_path } %} {%- let e = ci.get_enum_definition(name).unwrap() %} -{%- if ci.is_name_used_as_error(name) %} +{%- if ci.is_used_as_error(type_) %} {%- include "ErrorTemplate.swift" %} {%- else %} {%- include "EnumTemplate.swift" %} diff --git a/uniffi/uniffi_bindgen/src/bindings/swift/templates/macros.swift b/uniffi/uniffi_bindgen/src/bindings/swift/templates/macros.swift index b4074504..66779ae3 100644 --- a/uniffi/uniffi_bindgen/src/bindings/swift/templates/macros.swift +++ b/uniffi/uniffi_bindgen/src/bindings/swift/templates/macros.swift @@ -7,7 +7,7 @@ {%- macro to_ffi_call(func) -%} {%- call is_try(func) %}{% endcall -%} {%- if let(Some(e)) = func.throws_type() -%} - rustCallWithError({{ e|ffi_error_converter_name }}_lift) { + rustCallWithError({{ e|ffi_error_lift_fn }}) { {%- else -%} rustCall() { {%- endif %} @@ -117,7 +117,7 @@ public convenience init( {%- endmatch %} {%- match callable.throws_type() %} {%- when Some with (e) %} - errorHandler: {{ e|ffi_error_converter_name }}_lift + errorHandler: {{ e|ffi_error_lift_fn }} {%- else %} errorHandler: nil {% endmatch %} diff --git a/uniffi/uniffi_bindgen/src/bindings/swift/test.rs b/uniffi/uniffi_bindgen/src/bindings/swift/test.rs index 33e74ab6..7a1ef72f 100644 --- a/uniffi/uniffi_bindgen/src/bindings/swift/test.rs +++ b/uniffi/uniffi_bindgen/src/bindings/swift/test.rs @@ -47,15 +47,17 @@ pub fn run_script( // We need something better than this env var, but it's a reasonable start. let swift_version = std::env::var("UNIFFI_TEST_SWIFT_VERSION").unwrap_or("5".to_string()); - // Compile the generated sources together to create a single swift module - compile_swift_module( - &out_dir, - &generated_sources.main_module, - &generated_sources.generated_swift_files, - &generated_sources.module_map, - &swift_version, - options, - )?; + // Compile the generated sources into their modules, dependencies first + for (module_name, sources) in &generated_sources.modules { + compile_swift_module( + &out_dir, + module_name, + sources, + &generated_sources.module_map, + &swift_version, + options, + )?; + } // Run the test script against compiled bindings let mut command = create_command("swift", options); @@ -132,8 +134,8 @@ fn compile_swift_module>( // Stores sources generated by `uniffi-bindgen-swift` struct GeneratedSources { - main_module: String, - generated_swift_files: Vec, + /// Each module's name and sources, dependencies first. + modules: Vec<(String, Vec)>, module_map: Utf8PathBuf, } @@ -157,6 +159,34 @@ impl GeneratedSources { .find(|s| s.ci.crate_name() == crate_name) .unwrap(); let main_module = main_source.config.module_name(); + + // Every crate's sources compile into the main module, unless a crate imports another + // crate's module: then each crate is its own module. + let mut imports = vec![]; + for s in &sources { + imports.push(( + s.config.module_name(), + super::gen_swift::imported_modules(&s.config, &s.ci)?, + )); + } + let modules = if imports.iter().all(|(_, imported)| imported.is_empty()) { + vec![(main_module, glob(&out_dir.join("*.swift"))?)] + } else { + let mut modules: Vec<(String, Vec)> = vec![]; + while !imports.is_empty() { + let Some(next) = imports.iter().position(|(_, imported)| { + imported + .iter() + .all(|m| modules.iter().any(|(built, _)| built == m)) + }) else { + bail!("Swift modules import each other: {imports:?}"); + }; + let (module, _) = imports.remove(next); + let source = out_dir.join(format!("{module}.swift")); + modules.push((module, vec![source])); + } + modules + }; let modulemap_glob = glob(&out_dir.join("*.modulemap"))?; let module_map = match modulemap_glob.len() { 0 => bail!("No modulemap files found in {out_dir}"), @@ -181,8 +211,7 @@ impl GeneratedSources { }; Ok(GeneratedSources { - main_module, - generated_swift_files: glob(&out_dir.join("*.swift"))?, + modules, module_map, }) } diff --git a/uniffi/uniffi_bindgen/src/interface/enum_.rs b/uniffi/uniffi_bindgen/src/interface/enum_.rs index 6cc309f7..13f9c2e9 100644 --- a/uniffi/uniffi_bindgen/src/interface/enum_.rs +++ b/uniffi/uniffi_bindgen/src/interface/enum_.rs @@ -109,7 +109,7 @@ //! assert_eq!(err.variants()[0].name(), "one"); //! assert_eq!(err.variants()[1].name(), "two"); //! assert_eq!(err.is_flat(), true); -//! assert!(ci.is_name_used_as_error(&err.name())); +//! assert!(ci.is_used_as_error(err)); //! # Ok::<(), anyhow::Error>(()) //! ``` //! @@ -155,7 +155,7 @@ //! assert_eq!(err.variants()[2].fields()[0].name(), "x"); //! assert_eq!(err.variants()[2].fields()[1].name(), "y"); //! assert_eq!(err.is_flat(), false); -//! assert!(ci.is_name_used_as_error(err.name())); +//! assert!(ci.is_used_as_error(err)); //! # Ok::<(), anyhow::Error>(()) //! ``` @@ -557,7 +557,7 @@ mod test { )); let fret = ci.get_function_definition("returns_an_enum").unwrap(); assert!( - matches!(fret.return_type(), Some(Type::Enum { name, .. }) if name == "TestEnum" && !ci.is_name_used_as_error(name)) + matches!(fret.return_type(), Some(ty @ Type::Enum { name, .. }) if name == "TestEnum" && !ci.is_used_as_error(ty)) ); assert!(matches!( fret.ffi_func().return_type(), @@ -583,7 +583,7 @@ mod test { .get_function_definition("returns_an_enum_with_data") .unwrap(); assert!( - matches!(fret.return_type(), Some(Type::Enum { name, .. }) if name == "TestEnumWithData" && !ci.is_name_used_as_error(name)) + matches!(fret.return_type(), Some(ty @ Type::Enum { name, .. }) if name == "TestEnumWithData" && !ci.is_used_as_error(ty)) ); assert!(matches!( fret.ffi_func().return_type(), @@ -614,7 +614,7 @@ mod test { vec!("one", "two", "three") ); assert!(error.is_flat()); - assert!(ci.is_name_used_as_error(&error.name)); + assert!(ci.is_used_as_error(error)); } #[test] @@ -660,7 +660,7 @@ mod test { vec!("One", "Two") ); assert!(!error.is_flat()); - assert!(ci.is_name_used_as_error(&error.name)); + assert!(ci.is_used_as_error(error)); } #[test] diff --git a/uniffi/uniffi_bindgen/src/interface/exclude.rs b/uniffi/uniffi_bindgen/src/interface/exclude.rs index ad42eabb..6aafb094 100644 --- a/uniffi/uniffi_bindgen/src/interface/exclude.rs +++ b/uniffi/uniffi_bindgen/src/interface/exclude.rs @@ -6,7 +6,9 @@ use std::collections::HashSet; -use crate::interface::{AsType, ComponentInterface, Constructor, Function, Method, TypeUniverse}; +use crate::interface::{ + universe::type_key, AsType, ComponentInterface, Constructor, Function, Method, TypeUniverse, +}; pub fn apply_exclusions(ci: &mut ComponentInterface, exclusions: &[String]) { let exclusions: HashSet<&str> = exclusions.iter().map(String::as_str).collect(); @@ -43,7 +45,7 @@ fn remove_type_definitions( items: &mut Vec, types: &mut TypeUniverse, ) { - let mut removed_type_names = HashSet::new(); + let mut removed_types = HashSet::new(); items.retain(|ty| { let ty = ty.as_type(); let Some(name) = ty.name() else { @@ -51,7 +53,7 @@ fn remove_type_definitions( }; if excludes.contains(name) { - removed_type_names.insert(name.to_string()); + removed_types.extend(type_key(&ty)); false } else { true @@ -59,10 +61,10 @@ fn remove_type_definitions( }); types .type_definitions - .retain(|name, _| !removed_type_names.contains(name.as_str())); - types.all_known_types.retain(|ty| match ty.name() { + .retain(|key, _| !removed_types.contains(key)); + types.all_known_types.retain(|ty| match type_key(ty) { None => true, - Some(name) => !removed_type_names.contains(name), + Some(key) => !removed_types.contains(&key), }); } diff --git a/uniffi/uniffi_bindgen/src/interface/function.rs b/uniffi/uniffi_bindgen/src/interface/function.rs index 0a6a0087..a13de8fc 100644 --- a/uniffi/uniffi_bindgen/src/interface/function.rs +++ b/uniffi/uniffi_bindgen/src/interface/function.rs @@ -112,7 +112,7 @@ impl Function { } pub fn throws_name(&self) -> Option<&str> { - super::throws_name(&self.throws) + super::throws_name(self.throws.as_ref()) } pub fn throws_type(&self) -> Option<&Type> { @@ -412,7 +412,7 @@ mod test { } ); assert!( - matches!(func2.throws_type(), Some(Type::Enum { name, .. }) if name == "TestError" && ci.is_name_used_as_error(name)) + matches!(func2.throws_type(), Some(ty @ Type::Enum { name, .. }) if name == "TestError" && ci.is_used_as_error(ty)) ); assert_eq!(func2.arguments().len(), 2); assert_eq!(func2.arguments()[0].name(), "arg1"); diff --git a/uniffi/uniffi_bindgen/src/interface/mod.rs b/uniffi/uniffi_bindgen/src/interface/mod.rs index fa086d11..ecc41a2c 100644 --- a/uniffi/uniffi_bindgen/src/interface/mod.rs +++ b/uniffi/uniffi_bindgen/src/interface/mod.rs @@ -54,7 +54,7 @@ use anyhow::{anyhow, bail, ensure, Context, Result}; pub mod universe; pub use uniffi_meta::{AsType, EnumShape, ObjectImpl, TraitKind, Type}; -use universe::{TypeIterator, TypeUniverse}; +use universe::{type_key, TypeIterator, TypeKey, TypeUniverse}; mod callbacks; pub use callbacks::CallbackInterface; @@ -103,9 +103,9 @@ pub struct ComponentInterface { objects: Vec, custom_types: Vec, pub(crate) callback_interfaces: Vec, - // Type names which were seen used as an error. - errors: HashSet, - // Type names which participate in a recursive type cycle. + // Types which were seen used as an error. + errors: HashSet, + // Names of local types which participate in a recursive type cycle. recursive_types: HashSet, // Types which were seen used as callback interface error. callback_interface_throws_types: BTreeSet, @@ -347,7 +347,7 @@ impl ComponentInterface { ) .any(|m| m.throws_type() == Some(&e.as_type())); - self.is_name_used_as_error(&e.name) && (fielded || used_in_foreign_interface) + self.is_used_as_error(e) && (fielded || used_in_foreign_interface) } /// Iterate over all known local types in the interface. @@ -461,10 +461,12 @@ impl ComponentInterface { &self.all_component_interfaces } + /// The interface of the crate a module path belongs to. pub fn find_component_interface(&self, module_path: &str) -> Option<&ComponentInterface> { + let crate_name = module_path.split("::").next().unwrap_or(module_path); self.all_component_interfaces .iter() - .find(|ci| ci.crate_name() == module_path) + .find(|ci| ci.crate_name() == crate_name) } // The namespace to use in crate-level FFI function definitions. Not used as the ffi @@ -928,7 +930,7 @@ impl ComponentInterface { ); } if matches!(defn.shape, EnumShape::Error { .. }) { - self.errors.insert(defn.name.clone()); + self.errors.extend(type_key(&defn.as_type())); } self.types .add_known_types(defn.iter_types()) @@ -982,8 +984,7 @@ impl ComponentInterface { self.types .add_known_types(defn.iter_types()) .with_context(|| format!("adding function {defn:?}"))?; - defn.throws_name() - .map(|n| self.errors.insert(n.to_string())); + add_error(&mut self.errors, defn.throws_type()); self.functions.push(defn); Ok(()) @@ -997,24 +998,21 @@ impl ComponentInterface { self.types .add_known_types(defn.iter_types()) .with_context(|| format!("adding constructor {defn:?}"))?; - defn.throws_name() - .map(|n| self.errors.insert(n.to_string())); + add_error(&mut self.errors, defn.throws_type()); object.constructors.push(defn); } else if let Some(record) = self.records.iter_mut().find(|r| &r.name == self_name) { let defn: Constructor = meta.into(); self.types .add_known_types(defn.iter_types()) .with_context(|| format!("adding constructor {defn:?}"))?; - defn.throws_name() - .map(|n| self.errors.insert(n.to_string())); + add_error(&mut self.errors, defn.throws_type()); record.constructors.push(defn); } else if let Some(enum_) = self.enums.iter_mut().find(|e| &e.name == self_name) { let defn: Constructor = meta.into(); self.types .add_known_types(defn.iter_types()) .with_context(|| format!("adding constructor {defn:?}"))?; - defn.throws_name() - .map(|n| self.errors.insert(n.to_string())); + add_error(&mut self.errors, defn.throws_type()); enum_.constructors.push(defn); } else { bail!("add_constructor_meta: type {} not found", self_name); @@ -1031,27 +1029,21 @@ impl ComponentInterface { self.types .add_known_types(method.iter_types()) .with_context(|| format!("adding method {method:?}"))?; - method - .throws_name() - .map(|n| self.errors.insert(n.to_string())); + add_error(&mut self.errors, method.throws_type()); object.methods.push(method); } else if let Some(record) = self.records.iter_mut().find(|r| &r.name == self_name) { let method = Method::from_metadata(meta, record.as_type()); self.types .add_known_types(method.iter_types()) .with_context(|| format!("adding method {method:?}"))?; - method - .throws_name() - .map(|n| self.errors.insert(n.to_string())); + add_error(&mut self.errors, method.throws_type()); record.methods.push(method); } else if let Some(enum_) = self.enums.iter_mut().find(|e| &e.name == self_name) { let method = Method::from_metadata(meta, enum_.as_type()); self.types .add_known_types(method.iter_types()) .with_context(|| format!("adding method {method:?}"))?; - method - .throws_name() - .map(|n| self.errors.insert(n.to_string())); + add_error(&mut self.errors, method.throws_type()); enum_.methods.push(method); } else { bail!("add_method_meta: type {} not found", self_name); @@ -1099,12 +1091,16 @@ impl ComponentInterface { Ok(()) } - pub fn is_name_used_as_error(&self, name: &str) -> bool { - self.errors.contains(name) + /// Whether a type is used as an error, here or in any other crate's interface. + pub fn is_used_as_error(&self, ty: &impl AsType) -> bool { + let Some(key) = type_key(&ty.as_type()) else { + return false; + }; + self.errors.contains(&key) || self .all_component_interfaces .iter() - .any(|ci| ci.errors.contains(name)) + .any(|ci| ci.errors.contains(&key)) } /// Called by `APIBuilder` impls to add a newly-parsed callback interface definition to the `ComponentInterface`. @@ -1142,9 +1138,7 @@ impl ComponentInterface { self.types .add_known_types(method.iter_types()) .with_context(|| format!("adding trait method {method:?}"))?; - method - .throws_name() - .map(|n| self.errors.insert(n.to_string())); + add_error(&mut self.errors, method.throws_type()); // finally take a mut ref to the cbi, to avoid taking an early mut ref to self. unwrap as we know it exists let cbi = get_callback_interface(&mut self.callback_interfaces, &meta.trait_name).unwrap(); @@ -1240,7 +1234,7 @@ impl ComponentInterface { .variants() .iter() .flat_map(|v| v.fields()) - .flat_map(|f| type_names_in_type(&f.as_type())) + .flat_map(|f| type_names_in_type(self.crate_name(), &f.as_type())) .collect(); (e.name().to_string(), deps) }); @@ -1248,7 +1242,7 @@ impl ComponentInterface { let deps: HashSet = r .fields() .iter() - .flat_map(|f| type_names_in_type(&f.as_type())) + .flat_map(|f| type_names_in_type(self.crate_name(), &f.as_type())) .collect(); (r.name().to_string(), deps) }); @@ -1329,10 +1323,15 @@ impl<'a> RecursiveTypeIterator<'a> { } /// Find the component interface with the type definitions for the given module. - fn find_ci_for(&self, module_path: &str) -> &'a ComponentInterface { - self.ci - .find_component_interface(module_path) - .unwrap_or(self.ci) + /// + /// Our own interface stands in only for our own types: an external type may share a name with + /// one of them. + fn find_ci_for(&self, module_path: &str) -> Option<&'a ComponentInterface> { + if module_path.split("::").next() == Some(self.ci.crate_name()) { + Some(self.ci) + } else { + self.ci.find_component_interface(module_path) + } } /// Advance the iterator to recurse into the next pending type, if any. @@ -1352,25 +1351,25 @@ impl<'a> RecursiveTypeIterator<'a> { module_path, name, .. } => self .find_ci_for(module_path) - .get_record_definition(name) + .and_then(|ci| ci.get_record_definition(name)) .map(Record::iter_types), Type::Enum { module_path, name, .. } => self .find_ci_for(module_path) - .get_enum_definition(name) + .and_then(|ci| ci.get_enum_definition(name)) .map(Enum::iter_types), Type::Object { module_path, name, .. } => self .find_ci_for(module_path) - .get_object_definition(name) + .and_then(|ci| ci.get_object_definition(name)) .map(Object::iter_types), Type::CallbackInterface { module_path, name, .. } => self .find_ci_for(module_path) - .get_callback_interface_definition(name) + .and_then(|ci| ci.get_callback_interface_definition(name)) .map(CallbackInterface::iter_types), _ => None, }; @@ -1399,8 +1398,15 @@ impl<'a> Iterator for RecursiveTypeIterator<'a> { } } +/// Record a callable's error type, if any, as used as an error. +fn add_error(errors: &mut HashSet, throws: Option<&Type>) { + if throws_name(throws).is_some() { + errors.extend(throws.and_then(type_key)); + } +} + // Helpers for functions/methods/constructors which all have the same "throws" semantics. -fn throws_name(throws: &Option) -> Option<&str> { +fn throws_name(throws: Option<&Type>) -> Option<&str> { // Type has no `name()` method, just `canonical_name()` which isn't what we want. match throws { None => None, @@ -1414,24 +1420,32 @@ fn throws_name(throws: &Option) -> Option<&str> { } } -/// Return all enum and record names directly reachable from `ty`. +/// Return all enum and record names of `crate_name` directly reachable from `ty`. /// /// Unwraps `Optional`/`Sequence`/`Map` wrappers but does not cross type /// definition boundaries — both `Enum` and `Record` references are returned /// as-is rather than recursed into, because they are nodes in their own right. -fn type_names_in_type(ty: &Type) -> Vec { +/// Another crate's types are left out: they cannot contain this crate's, and may share a name +/// with one of them. +fn type_names_in_type(crate_name: &str, ty: &Type) -> Vec { match ty { - Type::Enum { name, .. } | Type::Record { name, .. } => vec![name.clone()], + Type::Enum { name, .. } | Type::Record { name, .. } => { + if ty.crate_name() == Some(crate_name) { + vec![name.clone()] + } else { + vec![] + } + } Type::Box { inner_type } | Type::Optional { inner_type } | Type::Sequence { inner_type } - | Type::Set { inner_type } => type_names_in_type(inner_type), + | Type::Set { inner_type } => type_names_in_type(crate_name, inner_type), Type::Map { key_type, value_type, } => { - let mut names = type_names_in_type(key_type); - names.extend(type_names_in_type(value_type)); + let mut names = type_names_in_type(crate_name, key_type); + names.extend(type_names_in_type(crate_name, value_type)); names } _ => vec![], @@ -1614,7 +1628,7 @@ new definition: Enum { let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); assert!(!ci.item_contains_unsigned_types(&Type::Object { name: "Testing".into(), - module_path: "".into(), + module_path: "crate_name".into(), imp: ObjectImpl::Struct, })); } @@ -1636,7 +1650,7 @@ new definition: Enum { let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); assert!(ci.item_contains_unsigned_types(&Type::Object { name: "TestObj".into(), - module_path: "".into(), + module_path: "crate_name".into(), imp: ObjectImpl::Struct, })); } @@ -1664,7 +1678,7 @@ new definition: Enum { #[test] fn test_names() { - let mut ci = ComponentInterface::default(); + let mut ci = ComponentInterface::new("mp"); let ob = Object { name: "ob".to_string(), @@ -1709,6 +1723,149 @@ new definition: Enum { assert!(ci.get_callback_interface_definition("cb").is_some()); } + #[test] + fn test_same_name_as_external_type() { + use uniffi_meta::{ + EnumMetadata, FieldMetadata, FnMetadata, Metadata, MetadataGroup, RecordMetadata, + }; + + let record = |module_path: &str| Type::Record { + module_path: module_path.to_string(), + name: "Frame".to_string(), + }; + let error = |module_path: &str| Type::Enum { + module_path: module_path.to_string(), + name: "Error".to_string(), + }; + let frame = RecordMetadata { + module_path: "media::frame".to_string(), + name: "Frame".to_string(), + orig_name: None, + remote: false, + fields: vec![FieldMetadata { + name: "raw".to_string(), + orig_name: None, + ty: record("net::frame"), + default: None, + docstring: None, + }], + docstring: None, + }; + // A local `Error` that is never thrown, while `net`'s is. + let local_error = EnumMetadata { + module_path: "media".to_string(), + name: "Error".to_string(), + orig_name: None, + shape: EnumShape::Enum, + remote: false, + variants: vec![], + discr_type: None, + non_exhaustive: false, + docstring: None, + }; + let decode = FnMetadata { + module_path: "media".to_string(), + name: "decode".to_string(), + orig_name: None, + is_async: false, + inputs: vec![], + return_type: Some(error("media")), + throws: Some(error("net")), + checksum: None, + docstring: None, + }; + let ci = ComponentInterface::from_metadata(MetadataGroup { + namespace: NamespaceMetadata { + crate_name: "media".to_string(), + name: "media".to_string(), + }, + namespace_docstring: None, + items: [ + Metadata::Record(frame), + Metadata::Enum(local_error), + Metadata::Func(decode), + ] + .into(), + }) + .unwrap(); + + let local = ci.get_type("Frame").unwrap(); + assert_eq!(local.crate_name(), Some("media")); + assert_eq!(ci.iter_external_types().count(), 2); + assert!(ci.is_used_as_error(&error("net"))); + assert!(!ci.is_used_as_error(&error("media"))); + // Holding `net`'s `Frame` does not make `media`'s recursive. + assert!(!ci.is_recursive("Frame")); + } + + #[test] + fn test_walks_external_type_in_its_own_interface() { + use uniffi_meta::{FieldMetadata, Metadata, MetadataGroup, ObjectMetadata, RecordMetadata}; + + let frame = |module_path: &str, field: Type| RecordMetadata { + module_path: module_path.to_string(), + name: "Frame".to_string(), + orig_name: None, + remote: false, + fields: vec![FieldMetadata { + name: "field".to_string(), + orig_name: None, + ty: field, + default: None, + docstring: None, + }], + docstring: None, + }; + let group = |crate_name: &str, items: Vec| { + ComponentInterface::from_metadata(MetadataGroup { + namespace: NamespaceMetadata { + crate_name: crate_name.to_string(), + name: crate_name.to_string(), + }, + namespace_docstring: None, + items: items.into_iter().collect(), + }) + .unwrap() + }; + // `net` defines its `Frame` in a submodule, so its module path is not the crate name. + let net_frame = Type::Record { + module_path: "net::frame".to_string(), + name: "Frame".to_string(), + }; + let net = group( + "net", + vec![Metadata::Record(frame("net::frame", Type::Bytes))], + ); + let consumer = ObjectMetadata { + module_path: "media".to_string(), + name: "Consumer".to_string(), + orig_name: None, + imp: ObjectImpl::Struct, + remote: false, + docstring: None, + }; + let consumer_type = Type::Object { + module_path: "media".to_string(), + name: "Consumer".to_string(), + imp: ObjectImpl::Struct, + }; + let mut media = group( + "media", + vec![ + Metadata::Object(consumer), + Metadata::Record(frame("media", consumer_type)), + ], + ); + media.set_all_component_interfaces(vec![net, media.clone()]); + + // `net`'s `Frame` is walked through `net`'s definition, not `media`'s namesake. + assert!(!media.item_contains_object_references(&net_frame)); + assert!(media.item_contains_object_references(&Type::Record { + module_path: "media".to_string(), + name: "Frame".to_string(), + })); + } + #[test] fn test_namespaces() { let mut ci = ComponentInterface::new("crate"); diff --git a/uniffi/uniffi_bindgen/src/interface/object.rs b/uniffi/uniffi_bindgen/src/interface/object.rs index 06fb4b66..ada90061 100644 --- a/uniffi/uniffi_bindgen/src/interface/object.rs +++ b/uniffi/uniffi_bindgen/src/interface/object.rs @@ -426,7 +426,7 @@ impl Constructor { } pub fn throws_name(&self) -> Option<&str> { - super::throws_name(&self.throws) + super::throws_name(self.throws.as_ref()) } pub fn throws_type(&self) -> Option<&Type> { @@ -573,7 +573,7 @@ impl Method { } pub fn throws_name(&self) -> Option<&str> { - super::throws_name(&self.throws) + super::throws_name(self.throws.as_ref()) } pub fn throws_type(&self) -> Option<&Type> { diff --git a/uniffi/uniffi_bindgen/src/interface/rename.rs b/uniffi/uniffi_bindgen/src/interface/rename.rs index 02e77762..e5844f7d 100644 --- a/uniffi/uniffi_bindgen/src/interface/rename.rs +++ b/uniffi/uniffi_bindgen/src/interface/rename.rs @@ -143,8 +143,8 @@ impl VisitMut for TomlRenamer<'_> { } } - fn visit_error_name(&self, name: &mut String) { - if let Some(new_name) = self.new_name(&self.this_module_path, name) { + fn visit_error_name(&self, module_path: &str, name: &mut String) { + if let Some(new_name) = self.new_name(module_path, name) { *name = new_name; } } diff --git a/uniffi/uniffi_bindgen/src/interface/universe.rs b/uniffi/uniffi_bindgen/src/interface/universe.rs index e84dcd58..04611a0b 100644 --- a/uniffi/uniffi_bindgen/src/interface/universe.rs +++ b/uniffi/uniffi_bindgen/src/interface/universe.rs @@ -27,8 +27,9 @@ pub(crate) struct TypeUniverse { pub namespace: NamespaceMetadata, pub namespace_docstring: Option, - // Named type definitions (including aliases). - pub(super) type_definitions: HashMap, + // Named type definitions (including aliases), by crate name and type name, so a local type + // and an external one may share a name. + pub(super) type_definitions: HashMap, // All the types in the universe, by canonical type name, in a well-defined order. pub(super) all_known_types: BTreeSet, } @@ -42,8 +43,8 @@ impl TypeUniverse { } /// Add the definition of a named [Type]. - fn add_type_definition(&mut self, name: &str, type_: &Type) -> Result<()> { - match self.type_definitions.entry(name.to_string()) { + fn add_type_definition(&mut self, key: TypeKey, type_: &Type) -> Result<()> { + match self.type_definitions.entry(key) { Entry::Occupied(o) => { // mismatched types are bad. let cur = o.get(); @@ -60,9 +61,11 @@ impl TypeUniverse { } } - /// Get the [Type] corresponding to a given name, if any. + /// Get the local [Type] corresponding to a given name, if any. pub(super) fn get_type_definition(&self, name: &str) -> Option { - self.type_definitions.get(name).cloned() + self.type_definitions + .get(&(self.namespace.crate_name.clone(), name.to_string())) + .cloned() } /// Add a [Type] to the set of all types seen in the component interface. @@ -76,8 +79,9 @@ impl TypeUniverse { for sub in type_.iter_nested_types() { self.add_known_type(sub)?; } - if let Some(name) = type_.name() { - self.add_type_definition(name, type_) + if let Some(key) = type_key(type_) { + let name = key.1.clone(); + self.add_type_definition(key, type_) .with_context(|| format!("adding named type {name}"))?; } Ok(()) @@ -121,6 +125,14 @@ impl TypeUniverse { } } +/// A named type's crate name and name, which identify it across crates. +pub(super) type TypeKey = (String, String); + +/// The [TypeKey] of a named type, or `None` for a builtin one. +pub(super) fn type_key(ty: &Type) -> Option { + Some((ty.crate_name()?.to_string(), ty.name()?.to_string())) +} + /// Normalize the `module_path` field for a type /// /// Types from proc-macros include the entire module path diff --git a/uniffi/uniffi_bindgen/src/interface/visit_mut.rs b/uniffi/uniffi_bindgen/src/interface/visit_mut.rs index c6b20f25..08a7ee2c 100644 --- a/uniffi/uniffi_bindgen/src/interface/visit_mut.rs +++ b/uniffi/uniffi_bindgen/src/interface/visit_mut.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::interface::FfiDefinition; +use crate::interface::{universe::type_key, AsType, FfiDefinition}; use crate::{ComponentInterface, VisitMut}; use std::collections::BTreeSet; use uniffi_meta::Type; @@ -16,9 +16,14 @@ impl ComponentInterface { /// 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_); - } + // A renamed type is found by its new name. + self.types.type_definitions = std::mem::take(&mut self.types.type_definitions) + .into_values() + .map(|mut type_| { + visitor.visit_type(&mut type_); + (type_key(&type_).expect("a type definition is named"), type_) + }) + .collect(); let mut all_known_types_altered: BTreeSet = BTreeSet::new(); @@ -32,7 +37,8 @@ impl ComponentInterface { let errors_clone = self.errors.clone(); for enum_item in self.enums.iter_mut() { - let is_error = errors_clone.contains(enum_item.name()); + let is_error = + type_key(&enum_item.as_type()).is_some_and(|k| errors_clone.contains(&k)); visitor.visit_enum(is_error, enum_item); @@ -150,9 +156,9 @@ impl ComponentInterface { self.errors = self .errors .drain() - .map(|mut name| { - visitor.visit_error_name(&mut name); - name + .map(|(crate_name, mut name)| { + visitor.visit_error_name(&crate_name, &mut name); + (crate_name, name) }) .collect() } diff --git a/uniffi/uniffi_bindgen/src/lib.rs b/uniffi/uniffi_bindgen/src/lib.rs index 1f04a6b2..94cff1f8 100644 --- a/uniffi/uniffi_bindgen/src/lib.rs +++ b/uniffi/uniffi_bindgen/src/lib.rs @@ -225,10 +225,10 @@ pub trait VisitMut { /// naming conventions. fn visit_type(&self, type_: &mut Type); - /// Go through each error name in the interface and adjust it to language specific naming - /// conventions. The new name must match the name of the Enum/Object definition after it's - /// visited. - fn visit_error_name(&self, name: &mut String); + /// Go through each error name in the interface, with the module path of the crate that + /// defines it, and adjust it to language specific naming conventions. The new name must + /// match the name of the Enum/Object definition after it's visited. + fn visit_error_name(&self, module_path: &str, name: &mut String); /// Go through each `Method` of an `Object` or `CallbackInterface` and /// adjust it to language specific naming conventions. diff --git a/uniffi/uniffi_bindgen/src/pipeline/general/context.rs b/uniffi/uniffi_bindgen/src/pipeline/general/context.rs index 692a054a..926d6e0b 100644 --- a/uniffi/uniffi_bindgen/src/pipeline/general/context.rs +++ b/uniffi/uniffi_bindgen/src/pipeline/general/context.rs @@ -15,7 +15,8 @@ pub struct Context { pub current_type: Option, pub current_variant: Option, pub current_arg_or_field_type: Option, - pub names_used_as_error: HashSet, + /// The namespace and name of each type used as an error. + pub names_used_as_error: HashSet<(String, String)>, // Maps namespaces to rename tables from the TOML config pub rename_tables: HashMap, // Maps namespaces to exclude tables from the TOML config @@ -126,7 +127,8 @@ impl Context { // historically worked. namespace.visit(|en: &initial::Enum| { if matches!(en.shape, EnumShape::Error { .. }) { - self.names_used_as_error.insert(en.name.clone()); + self.names_used_as_error + .insert((namespace.name.clone(), en.name.clone())); } }); Ok(()) @@ -134,15 +136,25 @@ impl Context { fn update_from_throws(&mut self, throws: &Option) -> Result<()> { if let Some(ty) = throws { - let type_name = match ty { - Type::Interface { name, .. } - | Type::Record { name, .. } - | Type::Enum { name, .. } - | Type::CallbackInterface { name, .. } - | Type::Custom { name, .. } => name.to_string(), + let key = match ty { + Type::Interface { + namespace, name, .. + } + | Type::Record { + namespace, name, .. + } + | Type::Enum { + namespace, name, .. + } + | Type::CallbackInterface { + namespace, name, .. + } + | Type::Custom { + namespace, name, .. + } => (namespace.to_string(), name.to_string()), _ => bail!("Invalid throws type: {ty:?}"), }; - self.names_used_as_error.insert(type_name); + self.names_used_as_error.insert(key); } Ok(()) } @@ -198,11 +210,23 @@ impl Context { pub fn type_is_used_as_error(&self, ty: &Type) -> bool { match ty { - Type::Interface { name, .. } - | Type::Record { name, .. } - | Type::Enum { name, .. } - | Type::CallbackInterface { name, .. } - | Type::Custom { name, .. } => self.names_used_as_error.contains(name), + Type::Interface { + namespace, name, .. + } + | Type::Record { + namespace, name, .. + } + | Type::Enum { + namespace, name, .. + } + | Type::CallbackInterface { + namespace, name, .. + } + | Type::Custom { + namespace, name, .. + } => self + .names_used_as_error + .contains(&(namespace.clone(), name.clone())), _ => false, } } diff --git a/uniffi/uniffi_bindgen/src/pipeline/general/infer_recursive_enums.rs b/uniffi/uniffi_bindgen/src/pipeline/general/infer_recursive_enums.rs index 2c38c2d6..ff9c93f2 100644 --- a/uniffi/uniffi_bindgen/src/pipeline/general/infer_recursive_enums.rs +++ b/uniffi/uniffi_bindgen/src/pipeline/general/infer_recursive_enums.rs @@ -29,19 +29,21 @@ fn type_dep_graph(type_defs: &[TypeDefinition]) -> HashMap { + let namespace = e.self_type.ty.namespace(); let deps: HashSet = e .variants .iter() .flat_map(|v| v.fields.iter()) - .flat_map(|f| type_names_in_type(&f.ty.ty)) + .flat_map(|f| type_names_in_type(namespace, &f.ty.ty)) .collect(); Some((e.name.clone(), deps)) } TypeDefinition::Record(r) => { + let namespace = r.self_type.ty.namespace(); let deps: HashSet = r .fields .iter() - .flat_map(|f| type_names_in_type(&f.ty.ty)) + .flat_map(|f| type_names_in_type(namespace, &f.ty.ty)) .collect(); Some((r.name.clone(), deps)) } @@ -121,19 +123,22 @@ fn dfs( /// Unwraps `Optional`/`Sequence`/`Map` wrappers but does not cross type /// definition boundaries — both `Enum` and `Record` references are returned /// as-is rather than recursed into, because they are nodes in their own right. -fn type_names_in_type(ty: &Type) -> Vec { +fn type_names_in_type(namespace: Option<&str>, ty: &Type) -> Vec { match ty { - Type::Enum { name, .. } | Type::Record { name, .. } => vec![name.clone()], + // Another namespace's types cannot contain this one's, and may share a name with them. + Type::Enum { name, .. } | Type::Record { name, .. } if ty.namespace() == namespace => { + vec![name.clone()] + } Type::Box { inner_type } | Type::Optional { inner_type } | Type::Sequence { inner_type } - | Type::Set { inner_type } => type_names_in_type(inner_type), + | Type::Set { inner_type } => type_names_in_type(namespace, inner_type), Type::Map { key_type, value_type, } => { - let mut names = type_names_in_type(key_type); - names.extend(type_names_in_type(value_type)); + let mut names = type_names_in_type(namespace, key_type); + names.extend(type_names_in_type(namespace, value_type)); names } _ => vec![], diff --git a/uniffi/uniffi_bindgen/src/pipeline/general/sort.rs b/uniffi/uniffi_bindgen/src/pipeline/general/sort.rs index 4a994c7a..0c18e90c 100644 --- a/uniffi/uniffi_bindgen/src/pipeline/general/sort.rs +++ b/uniffi/uniffi_bindgen/src/pipeline/general/sort.rs @@ -130,6 +130,15 @@ impl FfiDefinitionDependencyLogic { struct TypeDefinitionDependencyLogic; +impl TypeDefinitionDependencyLogic { + fn type_name(ty: &TypeNode) -> String { + match ty.ty.namespace() { + Some(namespace) => format!("{namespace}.{}", ty.canonical_name), + None => ty.canonical_name.clone(), + } + } +} + impl DependencyLogic for TypeDefinitionDependencyLogic { type Item = TypeDefinition; @@ -145,9 +154,9 @@ impl DependencyLogic for TypeDefinitionDependencyLogic { | TypeDefinition::Enum(Enum { self_type, .. }) | TypeDefinition::Interface(Interface { self_type, .. }) | TypeDefinition::CallbackInterface(CallbackInterface { self_type, .. }) - | TypeDefinition::Custom(CustomType { self_type, .. }) - | TypeDefinition::External(ExternalType { self_type, .. }) => { - self_type.canonical_name.clone() + | TypeDefinition::Custom(CustomType { self_type, .. }) => Self::type_name(self_type), + TypeDefinition::External(ExternalType { self_type, .. }) => { + format!("External:{}", Self::type_name(self_type)) } } } @@ -159,25 +168,21 @@ impl DependencyLogic for TypeDefinitionDependencyLogic { | TypeDefinition::Optional(OptionalType { inner, .. }) | TypeDefinition::Sequence(SequenceType { inner, .. }) | TypeDefinition::Set(SetType { inner, .. }) => { - vec![inner.canonical_name.clone()] + vec![Self::type_name(inner)] } TypeDefinition::Map(MapType { key, value, .. }) => { - vec![key.canonical_name.clone(), value.canonical_name.clone()] + vec![Self::type_name(key), Self::type_name(value)] } - TypeDefinition::Record(r) => r - .fields - .iter() - .map(|f| f.ty.canonical_name.clone()) - .collect(), + TypeDefinition::Record(r) => r.fields.iter().map(|f| Self::type_name(&f.ty)).collect(), TypeDefinition::Enum(e) => e .variants .iter() - .flat_map(|v| v.fields.iter().map(|f| f.ty.canonical_name.clone())) + .flat_map(|v| v.fields.iter().map(|f| Self::type_name(&f.ty))) .collect(), TypeDefinition::Interface(i) => { i.trait_impls .iter() - .map(|i| i.trait_ty.canonical_name.clone()) + .map(|i| Self::type_name(&i.trait_ty)) .chain( i.methods .iter() @@ -192,7 +197,7 @@ impl DependencyLogic for TypeDefinitionDependencyLogic { .map(|a| &a.ty) .chain(&callable.return_type.ty) .chain(&callable.throws_type.ty) - .map(|ty| ty.canonical_name.clone()) + .map(Self::type_name) }), ) .collect() @@ -209,12 +214,85 @@ impl DependencyLogic for TypeDefinitionDependencyLogic { .chain(&m.callable.return_type.ty) .chain(&m.callable.throws_type.ty) }) - .map(|ty| ty.canonical_name.clone()) + .map(Self::type_name) .collect(), TypeDefinition::Custom(custom) => { - vec![custom.builtin.canonical_name.clone()] + vec![Self::type_name(&custom.builtin)] } TypeDefinition::External(_) => vec![], } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn custom_definition(namespace: &str, name: &str) -> TypeDefinition { + TypeDefinition::Custom(CustomType { + self_type: TypeNode { + // How `types::canonical_name` names a user type + canonical_name: format!("Type{name}"), + id: 0, + is_used_as_error: false, + ffi_type: FfiType::UInt64, + ty: Type::Custom { + namespace: namespace.to_string(), + name: name.to_string(), + orig_name: name.to_string(), + builtin: Box::new(Type::UInt64), + }, + }, + orig_name: name.to_string(), + name: name.to_string(), + builtin: TypeNode { + canonical_name: "UInt64".to_string(), + id: 1, + is_used_as_error: false, + ffi_type: FfiType::UInt64, + ty: Type::UInt64, + }, + docstring: None, + }) + } + + /// The `External` entry a namespace gets for a type defined in another one + /// + /// Built the way `type_definitions_from_api` builds it: from the type's own self + /// type, recording the namespace that *defines* the type rather than the one using + /// it. + fn external_for(type_def: &TypeDefinition) -> TypeDefinition { + let TypeDefinition::Custom(custom) = type_def else { + panic!("expected a custom type definition, got {type_def:?}"); + }; + let self_type = custom.self_type.clone(); + TypeDefinition::External(ExternalType { + namespace: self_type + .ty + .namespace() + .expect("a user type has a namespace") + .to_string(), + name: custom.name.clone(), + self_type, + }) + } + + #[test] + fn test_same_named_types_in_different_namespaces_are_both_kept() { + let sorted = sort_type_definitions(vec![ + custom_definition("crate_a", "Thing"), + custom_definition("crate_b", "Thing"), + ]); + assert_eq!(sorted.len(), 2); + } + + #[test] + fn test_external_types_do_not_replace_real_definitions() { + let custom = &custom_definition("crate_a", "Thing"); + let external = &external_for(custom); + for type_definitions in [[custom, external], [external, custom]] { + let sorted = sort_type_definitions(type_definitions.into_iter().cloned()); + assert_eq!(sorted.len(), 2, "a definition was dropped: {sorted:?}"); + } + } +} diff --git a/uniffi/uniffi_bindgen/src/pipeline/general/types.rs b/uniffi/uniffi_bindgen/src/pipeline/general/types.rs index c0085001..c0573006 100644 --- a/uniffi/uniffi_bindgen/src/pipeline/general/types.rs +++ b/uniffi/uniffi_bindgen/src/pipeline/general/types.rs @@ -3,18 +3,24 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::*; +use heck::ToUpperCamelCase; pub fn map_type_node(ty: Type, context: &Context) -> Result { Ok(TypeNode { id: context.get_type_id(&ty)?, - canonical_name: canonical_name(&ty), + canonical_name: canonical_name(&ty, context.current_namespace_name.as_deref()), is_used_as_error: context.type_is_used_as_error(&ty), ffi_type: ffi_types::ffi_type(&ty, context)?, ty: ty.map_node(context)?, }) } -pub fn canonical_name(ty: &Type) -> String { +/// A unique name for a type within `namespace`, or among the builtin types when `None`. +/// +/// A named type from another namespace is spelled with that namespace inside compound types, +/// whose helpers `namespace` defines itself, so they do not collide with the helpers for a +/// local type of the same name. +pub fn canonical_name(ty: &Type, namespace: Option<&str>) -> String { match ty { Type::UInt8 => "UInt8".to_string(), Type::Int8 => "Int8".to_string(), @@ -37,10 +43,10 @@ pub fn canonical_name(ty: &Type) -> String { | Type::Enum { name, .. } | Type::Custom { name, .. } => format!("Type{name}"), Type::Optional { inner_type } => { - format!("Optional{}", canonical_name(inner_type)) + format!("Optional{}", inner_canonical_name(inner_type, namespace)) } Type::Sequence { inner_type } => { - format!("Sequence{}", canonical_name(inner_type)) + format!("Sequence{}", inner_canonical_name(inner_type, namespace)) } // Note: this is currently guaranteed to be unique because keys can only be primitive // types. If we allowed user-defined types, there would be potential collisions. For @@ -50,16 +56,29 @@ pub fn canonical_name(ty: &Type) -> String { value_type, } => format!( "Map{}{}", - canonical_name(key_type), - canonical_name(value_type), + inner_canonical_name(key_type, namespace), + inner_canonical_name(value_type, namespace), ), - Type::Box { inner_type } => format!("Box{}", canonical_name(inner_type)), + Type::Box { inner_type } => format!("Box{}", inner_canonical_name(inner_type, namespace)), Type::Set { inner_type } => { - format!("Set{}", canonical_name(inner_type)) + format!("Set{}", inner_canonical_name(inner_type, namespace)) } } } +fn inner_canonical_name(ty: &Type, namespace: Option<&str>) -> String { + match ty.namespace() { + Some(other) if namespace.is_some_and(|namespace| namespace != other) => { + format!( + "{}{}", + other.to_upper_camel_case(), + canonical_name(ty, namespace) + ) + } + _ => canonical_name(ty, namespace), + } +} + pub fn map_type(mut ty: Type, context: &Context) -> Result { Ok(match ty { // Map names for top-level types diff --git a/uniffi/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs b/uniffi/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs index fe4b12bf..fd81bbbb 100644 --- a/uniffi/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs +++ b/uniffi/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs @@ -16,7 +16,7 @@ uniffi::deps::static_assertions::assert_impl_all!({{ k|type_rs }}: ::std::cmp::E {% endfor %} {% for e in ci.enum_definitions() %} -{% if ci.is_name_used_as_error(e.name()) %} +{% if ci.is_used_as_error(e) %} // Error definitions, corresponding to `error` in the UDL. {% include "ErrorTemplate.rs" %} {% else %}