Skip to content

fix: allow native_enum to shadow an Enum member with the same name in export_values#6039

Open
kounelisagis wants to merge 1 commit intopybind:masterfrom
kounelisagis:fix/native-enum-export-values-collision
Open

fix: allow native_enum to shadow an Enum member with the same name in export_values#6039
kounelisagis wants to merge 1 commit intopybind:masterfrom
kounelisagis:fix/native-enum-export-values-collision

Conversation

@kounelisagis
Copy link
Copy Markdown
Contributor

Description

Fixes #6031.

Two native_enum types that both call .export_values() currently fail to import when they share a value name. The second binding hits the "an object with that name is already defined" check, even though the existing attribute is just an Enum member from the first binding.

The collision check in native_enum_data::finalize() now only treats an existing parent-scope attribute as a conflict when it is not already an Enum instance. Collisions with non-Enum attributes are still rejected, so names the user has explicitly set in the parent scope remain protected (the existing test_native_enum_value_name_clash still passes).

Classic py::enum_::export_values() has no collision check at all, so two classic enums sharing a value name have always worked with last-wins semantics. native_enum now behaves consistently in this respect, while keeping a stricter check against non-Enum attributes.

Test

A second native_enum, export_values2, is added to the test submodule. It shares exv0 with the existing export_values enum. test_export_values verifies:

  • both enums import successfully;
  • each enum keeps its own distinct members (export_values.exv0 is not export_values2.exv0);
  • the shared parent-scope attribute points to the later binding (m.exv0 is m.export_values2.exv0).

Confirmed by reverting just the header change: the test module then fails to import with exactly the error from the issue.

Suggested changelog entry:

  • Fix ImportError when two native_enum types call export_values() and share a value name.

@rwgk
Copy link
Copy Markdown
Collaborator

rwgk commented Apr 17, 2026

Silently clobbering previously exported names in the parent scope is exactly the kind of footgun native_enum was designed to avoid. It turns a clear, deterministic import-time error into a non-local, version-dependent behavior change: an upstream dependency that you do not control can add a new enum or a new member name, your extension still compiles and imports, and an existing exported symbol now resolves to a different enum member, or even a different enum type. The resulting failures may surface much later and far away from the binding code that introduced the collision, which makes them disproportionately difficult and time-consuming to diagnose. For native_enum, fail-fast on exported-name collisions is a feature, not something we should relax by default.

It'd seem acceptable to me to add an opt-in API, for example

.export_values_allowing_unsafe_parent_scope_name_clobbering()

or

.export_values(py::native_enum_export_values_policy::allow_unsafe_parent_scope_name_clobbering)

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.

[BUG]: "Already defined" import error when reusing a value name in different enums

2 participants