Skip to content

codegen: closed-enum extension without a default reads as its first value - #801

Open
v0ropaev wants to merge 1 commit into
anthropics:mainfrom
v0ropaev:fix/extension-closed-enum-default
Open

v0ropaev wants to merge 1 commit into
anthropics:mainfrom
v0ropaev:fix/extension-closed-enum-default

Conversation

@v0ropaev

Copy link
Copy Markdown
Contributor

A proto2 extension of an enum that doesn't start at zero reads as 0 when it's absent and has no [default = ...].

enum Level { LOW = 1; HIGH = 2; }
extend Carrier { optional Level level = 100; }

On main Carrier::default().extension_or_default(&LEVEL) returns 0, which isn't a Level. An editions enum with option features.enum_type = CLOSED and values 3 and 4 does the same and gives 0 instead of 3. Everywhere else buffa already reads an absent closed enum as its first value: the generated Default, reflection since #535, and the map codec. That's also the default the descriptor gives the extension.

The cause is in buffa-codegen/src/extension.rs, which only builds a default fn when default_value is non-empty. These extensions get Extension::new, so extension_or_default falls through to i32::default().

Now a singular extension whose enum resolves as closed gets Extension::with_default with a fn returning Enumeration::to_i32(&<Level as Default>::default()). That's the expression reflection already uses (reflect_view::enum_default_number). Going through Default keeps it working for an extern_path enum whose values codegen can't see, which is also why it isn't a const fn. Open enums are left alone because their first value has to be 0, and repeated extensions have no default. A closed enum that does start at 0, like Color in ext_json.proto, gets a fn that returns 0. That's harmless, and I didn't special-case it because codegen only records first values when enum_type overrides are configured.

enum_extension_doc_mentions_type now uses the Color from gen_in_pkg, since it pointed at an enum missing from its context and the closed-enum path now resolves it.

There are two new codegen unit tests and two buffa-test cases on real generated code (proto2 in custom_options.proto, editions in group_ext.proto with an open enum next to it as a control). With only the tests applied on main, the closed-enum codegen test fails and the buffa-test pair fails with left: 0, right: 1 and left: 0, right: 3.

fmt, clippy with -D warnings and the buffa-codegen, buffa and buffa-test suites pass on 1.95.0. No checked-in generated code changes, since none of the bundled protos declares an enum extension.

@github-actions

Copy link
Copy Markdown

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

…alue

extension_or_default fell back to i32::default() for a singular enum
extension with no [default = ...], so a proto2 extension of
enum Level { LOW = 1; HIGH = 2; } read as 0, a number the enum does not
declare. The same happened for an editions enum with enum_type = CLOSED.
An unset field of that enum already reads as LOW, through the generated
Default impl, reflection and the map codec.

Codegen now passes Extension::with_default a fn returning the enum's
first value via its Default impl, so extern_path enums work too. Open
enums start at 0 and repeated extensions have no default, so neither
changes.
@v0ropaev
v0ropaev force-pushed the fix/extension-closed-enum-default branch from c5644b3 to c34450f Compare October 28, 2026 10:16

This branch has not been deployed

No deployments
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.

1 participant