codegen: re-export __buffa ancillary types at natural paths#96
Merged
codegen: re-export __buffa ancillary types at natural paths#96
Conversation
Ancillary generated types (views, oneof enums, view-of-oneof enums,
file-level extension consts, register_types) live unconditionally under
the per-package __buffa:: sentinel module so they can never collide with
user-declared proto types. As an ergonomic convenience, additionally
emit a pub use re-export for each one at the path a user would write
first, mirroring the pre-0.4.0 (and prost) layout:
pkg::FooView <- __buffa::view::FooView
pkg::foo::BarView <- __buffa::view::foo::BarView
pkg::foo::Kind <- __buffa::oneof::foo::Kind
pkg::foo::KindView <- __buffa::view::oneof::foo::Kind (renamed)
pkg::MY_EXT <- __buffa::ext::MY_EXT
pkg::register_types <- __buffa::register_types
A re-export is silently skipped when the natural name is already taken by
a real proto item or by another candidate re-export. When two candidates
collide with each other, both are dropped — never "first one wins" — so
the result is order-independent. The canonical __buffa:: path is the
source of truth: generated method signatures, field types, and downstream
codegen always use it, so a skipped re-export never breaks anything.
Package-root re-exports use a self::__buffa:: prefix because consumers
that nest packages with use super::* chains (buffa-build's _include.rs
does this) glob-import a parent package's __buffa, and Rust's import
resolution treats a glob-imported name as ambiguous against a
macro-expanded local one (E0659). Caught by the googleapis stress test;
locked in by a new buffa-test/protos/nestpkg_{outer,inner} fixture that
runs in CI.
Re-exports carry #[doc(inline)] so cargo doc renders the full type page
at the natural path instead of a re-export stub.
examples/conflicts demonstrates a proto that deliberately shadows every
re-export shape and the __buffa:: import-alias fallback.
Closes #80
|
All contributors have signed the CLA ✍️ ✅ |
rpb-ant
previously approved these changes
May 5, 2026
…orts # Conflicts: # CHANGELOG.md
rpb-ant
approved these changes
May 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #80.
Ancillary generated types (views, oneof enums, view-of-oneof enums, file-level extension consts,
register_types) live unconditionally under the per-package__buffa::sentinel module so they can never collide with user-declared proto types. As an ergonomic convenience, codegen now additionally emits apub usere-export for each one at the path a Rust user would write first — mirroring the pre-0.4.0 (and prost) layout:__buffa::)Foo__buffa::view::FooViewFooViewFoo.Bar__buffa::view::foo::BarViewfoo::BarViewkindofFoo__buffa::oneof::foo::Kindfoo::Kindkind__buffa::view::oneof::foo::Kindfoo::KindView(renamed viaas)extend__buffa::ext::MY_EXTMY_EXTregister_types__buffa::register_typesregister_typesA re-export is silently skipped when the natural name is already taken by a real proto item (message, enum, extension const) or by another candidate re-export. When two candidates collide with each other, both are dropped — never "first one wins" — so the result is order-independent. The canonical
__buffa::path is the source of truth: generated method signatures, field types, and downstream codegen always use it, so a skipped re-export never breaks anything; only a hand-written import of the natural path needs adjusting.This is the "simple with fallback" approach agreed in #80. No new config flag — the re-exports are unconditional and additive.
Highlights
self::__buffa::…prefix on package-root re-exports. Consumers that nest packages withuse super::*chains (e.g.buffa-build's_include.rsforgoogle.api+google.api.expr.v1alpha1) glob-import a parent package's__buffainto the child's scope. Rust's import-resolution pass then treats a bare__buffain ausepath as ambiguous against the locally-include!d one (E0659 — glob-import vs. macro-expanded item).self::disambiguates. Caught by the googleapis stress test; locked in by a newbuffa-test/protos/nestpkg_{outer,inner}fixture that runs in CI.#[doc(inline)]on every re-export socargo docrenders the full type page at the natural path instead of a "Re-export of …" stub.examples/conflictsis a new self-contained example whose proto deliberately shadows every re-export shape (Probe+ nestedReading/ReadingView+ top-levelProbeView). It demonstrates the__buffa::import-alias fallback for the cases where a natural re-export was dropped, alongsideEventshowing the happy path.examples/addressbookupdated to use the naturalperson::Addresspath (was__buffa::oneof::person::Address), with a comment pointing atexamples/conflictsfor the conflict story.Generated-output diff
The WKT regen shows the shape (
buffa-types/src/generated/google.protobuf.mod.rs):and
Value's oneof ingoogle.protobuf.struct.rs:Notes / trade-offs
message FooViewnext tomessage Foomakespkg::FooViewresolve to the new struct instead ofFoo's view re-export. The canonical__buffa::path is always stable. Documented inDESIGN.mdandCHANGELOG.md— this is the agreed trade-off (predictability over stability of every spelling).__buffa::stays visible in docs (no#[doc(hidden)]), per the discussion in New deeply nested oneof and view paths are difficult to use #80 — predictable behavior over a flickering doc surface.pub mod {msg_snake} { … }block in the owned tree (to host the re-export); pre-New deeply nested oneof and view paths are difficult to use #80 they did not. Documented inDESIGN.md.Testing
cargo test --workspace— 1525 pass (11 new inbuffa-codegen/src/tests/reexports.rscovering happy path at all nesting depths, every collision rule, views-disabled path, and thepub modblock being emitted for re-exports-only messages; 2 new inbuffa-test/src/tests/nestpkg.rsfor the nested-package consumer pattern)cargo clippy --workspace --all-targets -- -D warnings— cleancargo build --workspace --no-default-features— no_std cleantask build-examples— all 4 build (incl. newconflicts);cargo runonconflictspasses its assertionstask stress-googleapis— PASS (this is what caught theself::requirement)task lint-md— cleantask gen-wkt-types/task gen-bootstrap-types— checked-in generated code regenerateddocker-credential-gh)