Skip to content

codegen: rename a nested-types module whose name is not available - #560

Merged
iainmcgin merged 18 commits into
mainfrom
nested-module-names
Oct 13, 2026
Merged

iainmcgin merged 18 commits into
mainfrom
nested-module-names

Conversation

@iainmcgin

@iainmcgin iainmcgin commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

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, an extend block or a oneof:

  • message item: struct item and mod item are one name (E0428).
  • message Mode beside enum mode: the enum and mod mode are one name. Adding such an enum later moves mode::Detail to mode_::Detail.
  • message Crate, Super or Self: the module is declared crate_, and paths to its contents are written crate::….
  • message Arbitrary, with generate_arbitrary and the arbitrary feature on: the output of derive(Arbitrary) starts its paths with arbitrary::, which resolves to the module.
  • A top-level message Oneof, with views on: __buffa::view::oneof is 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_path entry resolves nested types to the same module. The first two cases compare against the Rust type names, so with type_name_prefix Pb, message item is struct Pbitem beside mod item. A crate that refers to these types through extern_path computes the name from its own descriptor set, which must contain the file that declares the colliding type.

The trees under __buffa now 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 Oof beside package foo.oof). Its owned module is already oof_; its __buffa modules were oof. The re-exports in the owned module (foo::oof_::Kind) keep their paths.

    -foo::__buffa::oneof::oof::Kind
    +foo::__buffa::oneof::oof_::Kind
  • message Arbitrary, or a top-level message Oneof, that compiles today (the arbitrary feature off, or views off): pkg::arbitrary::Inner becomes pkg::arbitrary_::Inner. The two names are reserved whatever the configuration, so that a crate and a crate that refers to its types through extern_path choose the same module.

On main, a message named InnerView nested beside Inner under a #135-renamed module fails (E0107): the view path of Inner resolves through the owned module's re-export, which InnerView replaces. The shared module name fixes that.

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.
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@iainmcgin
iainmcgin marked this pull request as ready for review October 7, 2026 15:34
Base automatically changed from type-name-escapes to main October 7, 2026 15:52
# 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
azdagron previously approved these changes Oct 13, 2026
@iainmcgin
iainmcgin added this pull request to the merge queue Oct 13, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 13, 2026
@iainmcgin
iainmcgin added this pull request to the merge queue Oct 13, 2026
@github-merge-queue
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.
@iainmcgin
iainmcgin merged commit 6476ef9 into main Oct 13, 2026
11 checks passed
@iainmcgin
iainmcgin deleted the nested-module-names branch October 13, 2026 22:55
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants