Repository navigation
codegen: rename a nested-types module whose name is not available - #560
Merged
Merged
Conversation
A message or enum named after a Rust keyword (`message type`) made codegen fail with "generated code failed to parse as Rust", and one named after a primitive type that generated code uses (`message bool`) shadowed that primitive in the generated module. The Rust name of a type is now escaped: a keyword becomes a raw identifier (`r#type`); `Self`, `self`, `super`, `crate` and the ten primitive names `bool`, `str`, `u8`, `usize`, `i32`, `i64`, `u32`, `u64`, `f32` and `f64` get a trailing underscore (`bool_`). The type map uses the same function, so declarations and references agree. The proto name is unchanged in JSON, the text format, type URLs and reflection, and `Debug` prints `type` for `r#type`. Two types in one package or one message that end up with one Rust name (`bool` beside `bool_`) are rejected with the new `CodeGenError::TypeNameConflict`. A primitive-named type is renamed rather than left in place with `::core::primitive::` paths at each use, because the output of `serde_derive` names `str`, `u8`, `u64` and `usize` with no path.
`derive(Arbitrary)` builds an identifier from the type name and panics on a raw one, so `message type` under `generate_arbitrary` failed with "`RECURSIVE_COUNT_r#type` is not a valid identifier". A keyword-named type is now `type_`, the rule `Self` and the primitive names already follow, and the `r#` handling in the `Debug` and serde text is gone. The fixture builds with `generate_arbitrary` so the all-features check compiles the derive on each escaped name.
`message gen` is the one keyword-named type that generates today, and it compiles on edition 2021, so its rename to `gen_` goes in the breaking fragment. The fixture gives `message type` a nested message and a oneof, which live in `r#type` beside the struct `type_`. The guide names the keyword set and the messages that still cannot hold nested items.
A message's nested types are generated in a module named `snake_case(Name)`. The module now gets a trailing underscore when a generated type in its scope has that name (`message item` declares `struct item`), when the name is `self`, `super`, `crate` or `arbitrary`, and for a top-level message when it is `oneof`. Each of these failed to compile once the message had a nested message, a nested enum, an `extend` block or a oneof. The rule is the one that already renames a module beside a sub-package of the same name, applied at every nesting level. The trees under `__buffa` use the same module name as the owned tree. For a module renamed beside a sub-package they kept the plain name, and the view path of a nested type resolved only through the owned module's re-export. A per-type `extern_path` entry resolves nested types to the module a generated crate declares beside a type of the mapped name.
|
All contributors have signed the CLA ✍️ ✅ |
# Conflicts: # docs/guide.md
…odule-names # Conflicts: # docs/guide.md
iainmcgin
marked this pull request as ready for review
October 7, 2026 15:34
# Conflicts: # buffa-codegen/src/context.rs # buffa-test/build.rs # buffa-test/src/lib.rs # docs/guide.md
chrisfinazzo
pushed a commit
to chrisfinazzo/buffa
that referenced
this pull request
Oct 7, 2026
…ropics#556) A message or enum named after a Rust keyword (`message type`) fails in codegen today with `generated code failed to parse as Rust`. One named after a primitive type that generated code writes with no path (`message u64`) fails to compile, because the struct shadows the primitive in its module. The Rust name of such a type now gets a trailing underscore. The proto name is unchanged in JSON, the text format, type URLs and reflection. | Proto name | Rust name | View | |---|---|---| | a strict or reserved keyword of any edition through 2024 (`type`, `match`, `Self`, `gen`) | `type_` | `type_View` | | `bool`, `str`, `u8`, `usize`, `i32`, `i64`, `u32`, `u64`, `f32`, `f64` | `bool_` | `bool_View` | A `type_name_prefix` is applied before the escape, so prefix `Pb` and `message bool` give `Pbbool`. A type reached through a prefix-mapped `extern_path` follows the same rule, so a crate and the crates that refer to its types regenerate together. **Breaking.** A message or enum named `bool`, `i32`, `i64`, `f32`, `f64` or `usize` (and `u8` with views off) compiles today in a package whose generated code does not use that primitive, for example `message bool` in a package with no `bool` field. `gen` is the one keyword that generates today, and it compiles on edition 2021 and earlier. These are now `bool_`, `gen_` and so on, and code that uses them must change: ```diff -let m = pkg::bool::default(); +let m = pkg::bool_::default(); ``` Other primitive names (`char`, `i8`, `u16`, …) compile today and keep their spelling. Two alternatives were set aside: - Leaving a primitive-named type in place and writing `::core::primitive::` at each generated use: the output of `serde_derive` uses `str`, `u8`, `u64` and `usize` with no path. - A raw identifier (`r#type`) for a keyword, as fields get: `derive(Arbitrary)` builds an identifier from the type's name and panics on a raw one. New API: `CodeGenError::TypeNameConflict`, returned when two types in one package or one message end up with one Rust name (`bool` beside `bool_`). Still failing, as on `main`: a message that contains a nested message, a nested enum, an `extend` block or a oneof, and whose name is lower-case with no underscore added (`item`) or is `crate`, `super` or `self` in either case. Its nested-types module takes a name that the struct has or that cannot name a module. anthropics#560 renames that module.
azdagron
previously approved these changes
Oct 13, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Oct 13, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Oct 13, 2026
The oneof support added to the table codec calls ancillary_prefix, which takes the codegen context as its first argument on this branch.
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.
A message's nested types are generated in a module named
snake_case(Name). Where that name is not available, the schema fails to compile today once the message contains a nested message, a nested enum, anextendblock or a oneof:message item:struct itemandmod itemare one name (E0428).message Modebesideenum mode: the enum andmod modeare one name. Adding such an enum later movesmode::Detailtomode_::Detail.message Crate,SuperorSelf: the module is declaredcrate_, and paths to its contents are writtencrate::….message Arbitrary, withgenerate_arbitraryand thearbitraryfeature on: the output ofderive(Arbitrary)starts its paths witharbitrary::, which resolves to the module.message Oneof, with views on:__buffa::view::oneofis also the root of the view-oneof tree.The module now gets a trailing underscore in these cases, by the rule #135 added for a message beside a sub-package of the same name. It applies at every nesting level, and a per-type
extern_pathentry resolves nested types to the same module. The first two cases compare against the Rust type names, so withtype_name_prefixPb,message itemisstruct Pbitembesidemod item. A crate that refers to these types throughextern_pathcomputes the name from its own descriptor set, which must contain the file that declares the colliding type.The trees under
__buffanow use the same module name as the owned tree.Breaking, in two cases:
A schema with the Module redefinition error when messages and packages share a name #135 collision (
message Oofbesidepackage foo.oof). Its owned module is alreadyoof_; its__buffamodules wereoof. The re-exports in the owned module (foo::oof_::Kind) keep their paths.message Arbitrary, or a top-levelmessage Oneof, that compiles today (thearbitraryfeature off, or views off):pkg::arbitrary::Innerbecomespkg::arbitrary_::Inner. The two names are reserved whatever the configuration, so that a crate and a crate that refers to its types throughextern_pathchoose the same module.On
main, a message namedInnerViewnested besideInnerunder a #135-renamed module fails (E0107): the view path ofInnerresolves through the owned module's re-export, whichInnerViewreplaces. The shared module name fixes that.