Skip to content

fix: qualify Option in nested scopes to prevent prelude shadowing (#36)#39

Draft
th0114nd wants to merge 6 commits intoanthropics:mainfrom
th0114nd:fix/option-prelude-shadow
Draft

fix: qualify Option in nested scopes to prevent prelude shadowing (#36)#39
th0114nd wants to merge 6 commits intoanthropics:mainfrom
th0114nd:fix/option-prelude-shadow

Conversation

@th0114nd
Copy link
Copy Markdown

@th0114nd th0114nd commented Apr 5, 2026

Summary

Fixes #36 — a proto message named Option shadows core::option::Option, causing type errors on oneof fields in nested scopes.

  • Extended ImportResolver with child_for_message() to propagate blocked prelude names through the use super::* module chain
  • Each recursive generate_message call now receives a child resolver that accounts for sibling types in its module scope
  • Qualified bare Option in custom deserialize temporaries and skip_serializing_if predicates
  • Added tests for nested and top-level Option message scenarios

Test plan

  • cargo test -p buffa-codegen (239 tests pass)
  • cargo test --workspace (all pass)
  • cargo clippy --workspace --all-targets (clean)
  • No WKT/bootstrap regen needed (no output changes for non-shadowed cases)

Made with Cursor

th0114nd and others added 6 commits April 5, 2026 18:24
)

When a oneof's PascalCase name collides with a nested message or enum
name in the same module, suffix the oneof enum with "Oneof" instead of
returning a hard NestedTypeOneofConflict error. This unblocks protos
like riderperks PerkRestrictions (message RegionCodes + oneof
region_codes).

The suffix is applied consistently across all code paths: struct fields,
enum definitions, view enums, binary encode/decode, text format, and
JSON serde. If the suffixed name also collides (pathological case), an
OneofSuffixConflict error is returned.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Made-with: Cursor
When a nested type triggers the `Oneof` suffix for one oneof enum
(e.g., `my_field` → `MyFieldOneof`), a second oneof named
`my_field_oneof` would also produce `MyFieldOneof`, creating duplicate
type names.

Introduce `resolve_oneof_idents` which processes oneofs sequentially,
growing the reserved set after each allocation. All call sites now
look up pre-computed idents instead of independently calling
`oneof_enum_ident`, ensuring consistency across struct fields, binary
encode/decode, text format, JSON serde, and view generation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Made-with: Cursor
When a oneof's PascalCase name matches the parent message name (e.g.
message DataType { oneof data_type { ... } }), the generated oneof enum
shadows the parent struct imported via use super::*. Include the parent
message name in the reserved set so the oneof enum gets the Oneof suffix
(DataTypeOneof), preventing the shadow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Made-with: Cursor
…ng (anthropics#32)

When a user-defined message `FooView` exists alongside `Foo`, the
generated `FooView<'a>` view type for `Foo` would collide. Previously
this was a hard `ViewNameConflict` error blocking the entire crate.

Now the codegen skips generating the view for `Foo` and continues.
The user-defined `FooView` message (and its own view `FooViewView`)
are generated normally. Downstream code that references `Foo`'s view
will get a targeted compile error rather than a blanket codegen failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Made-with: Cursor
…thropics#36)

When a proto message named `Option` is nested inside another message,
the generated `pub struct Option` shadows `core::option::Option` in
the parent module scope. This caused struct fields like
`pub value: Option<option::Value>` to resolve to the proto struct
(0 generic args) instead of the standard library type.

Fix: extend ImportResolver with `child_for_message()` to propagate
blocked prelude names through the `use super::*` module chain.
Each recursive `generate_message` call now receives a child resolver
that accounts for sibling types in its module scope. Also qualify
bare `Option` in custom deserialize temporaries and
`skip_serializing_if` predicates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Made-with: Cursor
When multiple protos share a package and are include!d into one Rust module,
a top-level message Option in one file shadows std's Option for siblings.
Union prelude-blocked names per protobuf package (not the whole batch) so
other packages in the same generate() call still emit bare Option.

Add regression tests for same-package cross-file shadowing and for
isolation across packages.

Made-with: Cursor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proto message named Option shadows core::option::Option, causing type errors on oneof fields

1 participant