Repository navigation
feat(uniffi): same-named types across crates in every generator - #21
Conversation
Quest-Claim: dece19136b3093e7
…drop TypeUniverse, the error set, and the recursion graph key named types by crate and name, so a crate can use another crate's type of the same name. Python's pipeline keeps both definitions (mozilla/uniffi-rs#2985) and names container helpers for external types after their namespace. Adds the two-crate namespaces fixture with its Python consumer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
External types' compound helpers carry their namespace, and error-ness comes from the type rather than its bare name. Adds the namespaces fixture to the Go fixtures with its consumer test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Go cannot hold a function and a record both named Bounds in one package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Go cannot hold a function and a record both named Bounds in one package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Go cannot hold a function and a record both named Bounds in one package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Go cannot hold a function and a record both named Bounds in one package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Go cannot hold a function and a record both named Bounds in one package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
C++: canonical and converter names take the interface, so an external type's helpers carry its namespace (`FfiConverterNetTypeFrame`, aliased to `::net::uniffi::FfiConverterTypeFrame`) and never shadow a local namesake. External types are qualified from the global namespace, error and enum-struct checks look the type up in its own crate, the definition sort ignores external types, and `strip_type_prefix` renames are keyed by crate. C: external types are supported. Their C names and converters are their own crate's, whose header is included; every C name now carries the namespace prefix unless it already starts with it, since crates share one C scope. Adds both namespaces crates to the C++ fixtures, with a C++ and a C test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
…-namespaces # Conflicts: # uniffi/fixtures/namespaces/media/tests/test_generated_bindings.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: da5520b0b9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // 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))); |
There was a problem hiding this comment.
Resolve an external enum's shape in its defining interface
When a local flat enum and an external data-carrying enum share a name, a variant containing the external enum is serialized incorrectly. The is_flat_enum helper still calls type_helper.get_enum(name), which looks up the local namesake, while the converter now correctly refers to the external enum. Consequently, allocation and writing reserve and copy only four bytes, discarding the external enum's payload; reading likewise passes only its discriminant to the external converter. Resolve the enum definition through its owning component, or use the converter's normal read/write/allocation methods without this special case.
AGENTS.md reference: AGENTS.md:L15-L15
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 899cc21: is_flat_enum now resolves the enum in the crate that defines it. The fixture gained a data-carrying net::Kind next to a flat media::Kind, both held by media::Track variants; the Dart test failed with a RangeError before the fix and passes after.
(written by Opus 5.5)
| if crates.len() == 1 && ci.get_type(name).is_none() { | ||
| continue; |
There was a problem hiding this comment.
Import all dependencies when Swift requires separate modules
When a library contains a name collision plus an unambiguous dependency from another crate, this early continue omits an import required by the separate-module layout. For example, if media defines Frame, uses net::Frame, and also uses extra::Other, the generated Swift imports net but neither imports extra nor qualifies Other. The updated Swift test runner splits every crate into its own module as soon as any collision requires an import, so Other and its converters are then out of scope. Determine the module layout across the components and include every external dependency's module when using the split layout, rather than importing only types whose names collide.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 899cc21: once any crate in the library needs its own Swift module, every crate's bindings import every crate they use, not only the ones they qualify. The layout is decided across components in apply_renames (separate_modules). A unit test with a third crate's unambiguous type fails on the old code and passes now.
(written by Opus 5.5)
…very crate in Swift's split layout Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
…paces Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn
|
Merging at 8670b55.
(written by Opus 5.5) |
Problem
Two UniFFI crates linked into one library could not each export a type with the same name, so
media.GroupConsumer(group: GroupConsumer)was impossible: every generator built onComponentInterfacerefused it withconflicting types, and Python silently dropped the local type (mozilla/uniffi-rs#3026, unfixed upstream). This finishes the namespaces quest; moq's per-layer crate split (moq-dev/moq#5460) is the consumer.Approach
uniffi_bindgen):TypeUniverse, the set of errors, and the recursion graph key named types by crate and name.is_name_used_as_error(name)becomesis_used_as_error(&ty),get_type(name)is local-only, and the recursive type walk resolves an external type in its own crate's interface instead of falling back to a same-named local one (also when the type lives in a submodule).SequenceNetTypeFramenext toSequenceTypeFrame):KotlinCi { ci, config }context.uniffi_<namespace>(a barenetprefix is hidden by a member namednet). The shared runtime now comes from the same crate every run, and the harness drives library mode.::net::Frame), aliased converters, per-crate definition sort andstrip_type_prefix.uniffi/fixtures/namespaces/{net,media}: both exportGroupConsumer,Frame,Priority, andGroupError, eachmediatype holds or wraps itsnetnamesake, and containers hold both. Kotlin, Swift, Python, Go, Dart, C++, and C each compile and run a consumer.No FFI contract change: metadata, checksums, and scaffolding are untouched, so the contract quest stays as it is.
Proof: moq-ffi bindings are byte-identical
moq-ffi at moq-dev/moq@4a16316 (default features, crates.io uniffi 0.32), generated for Kotlin, Swift, and Python (
--no-format), Go, Dart (dart/uniffi.toml,--no-format), and C++ (cpp/moq/uniffi.toml):v0.10.0-kixelated.1, moq's flake Dart and C++)The C backend refuses moq-ffi with both, with the same error (duplicate
moq_audio_encoder_finish). Each generator's fork also diffed its own single-crate fixtures before and after: identical. Only crates with external types change.Checks
just checkandjust testpass.just uniffi fixturesinnix develop .#uniffi: 355 nextest tests (Kotlin, Swift, Python, Dart fixtures, including the new namespaces consumers), 60 bindgen tests, Gogo test, and 33 C++/C ctest cases pass. The new C++ and C consumers are clean under valgrind memcheck (run by hand, since valgrind is not in the dev shell).target/debug, so a stale dev build of a fixture fails withUnsatisfiedLinkErroruntil rebuilt. CI builds from clean.Impact
uniffi_bindgen(breaking, inuniffi/MIGRATE.md):ComponentInterface::is_name_used_as_erroris replaced byis_used_as_error(&impl AsType);get_typefinds only local types;find_component_interfaceaccepts a module path;VisitMut::visit_error_nametakes the defining module path.uniffi/MIGRATE.md).uniffi-fixture-namespaces-{net,media}anddart_namespaces(unpublished).Alternatives
main.Follow-ups
uniffi); Dart avoids it with itsuniffi_prefix, C++ with::.uniffi-bindgen-swift's xcframework path compiles all crates into one module, so same-named types still cannot use it.net::Frameand for a local type namedNetFrameshare a name. The generated code then fails to compile instead of mixing them up.(written by Opus 5.5)
🤖 Generated with Claude Code
https://claude.ai/code/session_0115AT2ue6x9cT5e5J9SZtCn