Skip to content

fix: always fully-qualify Option to prevent cross-file prelude shadowing#67

Merged
iainmcgin merged 1 commit intoanthropics:mainfrom
th0114nd:fix/cross-file-option-shadow
Apr 27, 2026
Merged

fix: always fully-qualify Option to prevent cross-file prelude shadowing#67
iainmcgin merged 1 commit intoanthropics:mainfrom
th0114nd:fix/cross-file-option-shadow

Conversation

@th0114nd
Copy link
Copy Markdown
Contributor

Summary

Fixes #64.

The per-package stitcher (include!) combines all .proto files from one package into a single Rust module scope. If any file in the package defines message Option, it shadows core::option::Option for the entire module — including code generated from sibling files that don't define Option themselves.

The previous ImportResolver::for_file only checked names within a single file, so it missed cross-file shadowing. Rather than extending the resolver with sibling-awareness (which adds complexity and could still miss edge cases in future include patterns), this PR unconditionally emits ::core::option::Option everywhere. Generated code readability is not a concern, and this eliminates the entire class of prelude-shadowing bugs.

Changes

  • imports.rs: Removed collision-detection machinery (PRELUDE_NAMES, check_names_for_prelude_collisions, blocked set, for_file, for_file_with_siblings, child_for_message, is_available). ImportResolver::option() now unconditionally returns ::core::option::Option.
  • message.rs: Removed child_resolver (no longer needed since qualification is unconditional).
  • lib.rs: Use ImportResolver::new() instead of for_file.
  • Tests: Added test_cross_file_message_named_option_shadows_prelude reproducing the bug. Updated 9 assertions across unit and integration tests to expect ::core::option::Option<...>.

Reproduction

Two .proto files in the same package:

  • message.proto: defines message Option { string text = 1; }
  • session.proto: defines message Wrapper { oneof kind { string a = 1; } }

Before this fix, session.rs emitted pub kind: Option<Kind> — the bare Option resolved to message.proto's pub struct Option instead of core::option::Option.

Test plan

  • New test test_cross_file_message_named_option_shadows_prelude fails before fix, passes after
  • All 276 unit tests pass
  • All 37 integration tests pass
  • Verified against Lyft IDL corpus (2831 crates, 0 codegen failures)

Made with Cursor

@iainmcgin iainmcgin enabled auto-merge (squash) April 27, 2026 16:26
Copy link
Copy Markdown
Collaborator

@iainmcgin iainmcgin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[claude code] Correct fix for cross-file prelude shadowing — per-file detection cannot catch this since each file is generated independently and merged later by the stitcher. Unconditional ::core::option::Option makes message.rs consistent with the rest of the codegen. Integration test in prelude_shadow_sibling.proto proves it end-to-end. Thanks for the thorough analysis and fix.

@iainmcgin iainmcgin disabled auto-merge April 27, 2026 16:33
…ing (anthropics#67)

The per-package stitcher (include!) combines all .proto files from one
package into a single Rust module scope. If any file in the package
defines `message Option`, it shadows core::option::Option for the entire
module — including code generated from sibling files that do not define
Option themselves.

The previous ImportResolver per-file detection cannot catch this since
each file is generated independently. Rather than extending the resolver
with sibling-awareness (which adds complexity and breaks per-file plugin
invocation), unconditionally emit ::core::option::Option everywhere.
This makes message.rs consistent with view.rs/enumeration.rs/
impl_message.rs which already qualify unconditionally.

ImportResolver becomes a stateless unit struct retained as the single
source of truth for type-path emission.

Adds prelude_shadow_sibling.proto (shares package with
prelude_shadow.proto) as an end-to-end integration test.

Fixes anthropics#64.

Co-authored-by: Tim Holland <th0114nd@users.noreply.github.com>
Co-authored-by: Iain McGinniss <309153+iainmcgin@users.noreply.github.com>
@iainmcgin iainmcgin force-pushed the fix/cross-file-option-shadow branch from 193ea23 to 1427936 Compare April 27, 2026 16:34
@iainmcgin iainmcgin enabled auto-merge (squash) April 27, 2026 16:35
@iainmcgin iainmcgin merged commit cf2604a into anthropics:main Apr 27, 2026
7 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 27, 2026
@iainmcgin
Copy link
Copy Markdown
Collaborator

[claude code] Thanks for the contribution and the thorough cross-file analysis — this was a real gap.

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.

codegen: top-level message named Option shadows Rust prelude

2 participants