Skip to content

Commit

Permalink
Introduce py::native_enum_kind (mandatory argument without a defaul…
Browse files Browse the repository at this point in the history
…t). (#30118)

* Fix `native_enum` `__module__` attribute: it needs to be a Python `str`.

* Exercise `enum_type.__module__`

* Consolidate `ENUM_TYPES_AND_MEMBERS`, `ENUM_TYPES` in test.

* Add test_pickle_roundtrip

* clang-tidy auto-fix

* Warning suppression for Clang 19 (unrelated to `native_enum` changes).

🐍 3 • Clang dev • C++11 • x64

```
-- The CXX compiler identification is Clang 19.0.0
```

```
/__w/pybind11k/pybind11k/include/pybind11/detail/function_record_pyobject.h:38:26: error: cast from 'PyObject *(*)(PyObject *, PyObject *, PyObject *)' (aka '_object *(*)(_object *, _object *, _object *)') to 'PyCFunction' (aka '_object *(*)(_object *, _object *)') converts to incompatible function type [-Werror,-Wcast-function-type-mismatch]
   38 |     = {{"__reduce_ex__", (PyCFunction) reduce_ex_impl, METH_VARARGS | METH_KEYWORDS, nullptr},
```

* Introduce `py::native_enum_kind` (mandatory argument without a default).

* Change `was_not_added_error_message()` based on feedback from @metzen
  • Loading branch information
Ralf W. Grosse-Kunstleve committed Apr 19, 2024
1 parent e10cb7e commit 9f58994
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 25 deletions.
2 changes: 1 addition & 1 deletion include/pybind11/detail/native_enum_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class native_enum_data {
std::string was_not_added_error_message() const {
return "`native_enum` was not added to any module."
" Use e.g. `m += native_enum<...>(\""
+ enum_name_encoded + "\")` to fix.";
+ enum_name_encoded + "\", ...)` to fix.";
}

#if !defined(NDEBUG)
Expand Down
11 changes: 5 additions & 6 deletions include/pybind11/native_enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,17 @@

PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)

enum class native_enum_kind { Enum, IntEnum };

/// Conversions between Python's native (stdlib) enum types and C++ enums.
template <typename Type>
class native_enum : public detail::native_enum_data {
public:
using Underlying = typename std::underlying_type<Type>::type;

explicit native_enum(const char *name)
: detail::native_enum_data(name,
std::type_index(typeid(Type)),
std::numeric_limits<Underlying>::is_integer
&& !std::is_same<Underlying, bool>::value
&& !detail::is_std_char_type<Underlying>::value) {
explicit native_enum(const char *name, native_enum_kind kind)
: detail::native_enum_data(
name, std::type_index(typeid(Type)), kind == native_enum_kind::IntEnum) {
if (detail::get_local_type_info(typeid(Type)) != nullptr
|| detail::get_global_type_info(typeid(Type)) != nullptr) {
pybind11_fail(
Expand Down
44 changes: 27 additions & 17 deletions tests/test_native_enum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,35 +74,36 @@ TEST_SUBMODULE(native_enum, m) {
m.attr("native_enum_type_map_abi_id_c_str")
= py::cross_extension_shared_states::native_enum_type_map::abi_id();

m += py::native_enum<smallenum>("smallenum")
m += py::native_enum<smallenum>("smallenum", py::native_enum_kind::IntEnum)
.value("a", smallenum::a)
.value("b", smallenum::b)
.value("c", smallenum::c);

m += py::native_enum<color>("color")
m += py::native_enum<color>("color", py::native_enum_kind::IntEnum)
.value("red", color::red)
.value("yellow", color::yellow)
.value("green", color::green)
.value("blue", color::blue);

m += py::native_enum<altitude>("altitude")
m += py::native_enum<altitude>("altitude", py::native_enum_kind::Enum)
.value("high", altitude::high)
.value("low", altitude::low);

m += py::native_enum<export_values>("export_values")
m += py::native_enum<export_values>("export_values", py::native_enum_kind::IntEnum)
.value("exv0", export_values::exv0)
.value("exv1", export_values::exv1)
.export_values();

m += py::native_enum<member_doc>("member_doc")
m += py::native_enum<member_doc>("member_doc", py::native_enum_kind::IntEnum)
.value("mem0", member_doc::mem0, "docA")
.value("mem1", member_doc::mem1)
.value("mem2", member_doc::mem2, "docC");

py::class_<class_with_enum> py_class_with_enum(m, "class_with_enum");
py_class_with_enum += py::native_enum<class_with_enum::in_class>("in_class")
.value("one", class_with_enum::in_class::one)
.value("two", class_with_enum::in_class::two);
py_class_with_enum
+= py::native_enum<class_with_enum::in_class>("in_class", py::native_enum_kind::IntEnum)
.value("one", class_with_enum::in_class::one)
.value("two", class_with_enum::in_class::two);

m.def("isinstance_color", [](const py::object &obj) { return py::isinstance<color>(obj); });

Expand Down Expand Up @@ -136,48 +137,57 @@ TEST_SUBMODULE(native_enum, m) {

m.def("native_enum_ctor_malformed_utf8", [](const char *malformed_utf8) {
enum fake { x };
py::native_enum<fake>{malformed_utf8};
py::native_enum<fake>{malformed_utf8, py::native_enum_kind::IntEnum};
});

m.def("native_enum_value_malformed_utf8", [](const char *malformed_utf8) {
enum fake { x };
py::native_enum<fake>("fake").value(malformed_utf8, fake::x);
py::native_enum<fake>("fake", py::native_enum_kind::IntEnum)
.value(malformed_utf8, fake::x);
});

m.def("double_registration_native_enum", [](py::module_ m) {
enum fake { x };
m += py::native_enum<fake>("fake_double_registration_native_enum").value("x", fake::x);
py::native_enum<fake>("fake_double_registration_native_enum");
m += py::native_enum<fake>("fake_double_registration_native_enum",
py::native_enum_kind::IntEnum)
.value("x", fake::x);
py::native_enum<fake>("fake_double_registration_native_enum", py::native_enum_kind::Enum);
});

m.def("native_enum_name_clash", [](py::module_ m) {
enum fake { x };
m += py::native_enum<fake>("fake_native_enum_name_clash").value("x", fake::x);
m += py::native_enum<fake>("fake_native_enum_name_clash", py::native_enum_kind::IntEnum)
.value("x", fake::x);
});

m.def("native_enum_value_name_clash", [](py::module_ m) {
enum fake { x };
m += py::native_enum<fake>("fake_native_enum_value_name_clash")
m += py::native_enum<fake>("fake_native_enum_value_name_clash",
py::native_enum_kind::IntEnum)
.value("fake_native_enum_value_name_clash_x", fake::x)
.export_values();
});

m.def("double_registration_enum_before_native_enum", [](const py::module_ &m) {
enum fake { x };
py::enum_<fake>(m, "fake_enum_first").value("x", fake::x);
py::native_enum<fake>("fake_enum_first").value("x", fake::x);
py::native_enum<fake>("fake_enum_first", py::native_enum_kind::IntEnum)
.value("x", fake::x);
});

m.def("double_registration_native_enum_before_enum", [](py::module_ m) {
enum fake { x };
m += py::native_enum<fake>("fake_native_enum_first").value("x", fake::x);
m += py::native_enum<fake>("fake_native_enum_first", py::native_enum_kind::IntEnum)
.value("x", fake::x);
py::enum_<fake>(m, "name_must_be_different_to_reach_desired_code_path");
});

#if defined(PYBIND11_NEGATE_THIS_CONDITION_FOR_LOCAL_TESTING) && !defined(NDEBUG)
m.def("native_enum_correct_use_failure", []() {
enum fake { x };
py::native_enum<fake>("fake_native_enum_correct_use_failure").value("x", fake::x);
py::native_enum<fake>("fake_native_enum_correct_use_failure",
py::native_enum_kind::IntEnum)
.value("x", fake::x);
});
#else
m.attr("native_enum_correct_use_failure") = "For local testing only: terminates process";
Expand Down
2 changes: 1 addition & 1 deletion tests/test_native_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def test_native_enum_data_was_not_added_error_message():
msg = m.native_enum_data_was_not_added_error_message("Fake")
assert msg == (
"`native_enum` was not added to any module."
' Use e.g. `m += native_enum<...>("Fake")` to fix.'
' Use e.g. `m += native_enum<...>("Fake", ...)` to fix.'
)


Expand Down

0 comments on commit 9f58994

Please sign in to comment.