Skip to content

Conversions between Python's native (stdlib) enum types and C++ enums. #30005

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
04557ca
Transfer PR #4329 from master to smart_holder branch, STEP 1.
rwgk Nov 19, 2022
a45bb61
Make `smart_holder` code compatible with `type_caster_enum_type`
rwgk Nov 20, 2022
3ec457a
Fix `if` condition guarding `Unable to cast native enum type to refer…
rwgk Nov 20, 2022
9e940e7
WIP native_enum_add_to_parent
rwgk Nov 20, 2022
f344bfa
PYBIND11_SILENCE_MSVC_C4127
rwgk Nov 21, 2022
41cf4ce
Merge branch 'smart_holder' into native_enum_sh
rwgk Dec 5, 2022
b5f8cde
Merge branch 'smart_holder' into native_enum_sh
rwgk Dec 6, 2022
78bfc94
Merge branch 'smart_holder' into native_enum_sh
rwgk Jan 4, 2023
10253a1
Transfer upstream.yml/python312.yml changes from PR #4397
rwgk Jan 4, 2023
8690a88
clang-tidy (clang 15) auto-fix
rwgk Jan 4, 2023
613b46e
Merge branch 'smart_holder' into native_enum_sh
rwgk Feb 17, 2023
822f988
Remove python312.yml: this is handled separately under PR #30006
rwgk Mar 9, 2023
5f23bee
Merge branch 'google_pywrapcc_main' into native_enum_pywrapcc
rwgk Mar 9, 2023
38559c3
Fixes for ruff
rwgk Mar 9, 2023
630ba5f
Merge branch 'google_pywrapcc_main' into native_enum_pywrapcc
rwgk Mar 9, 2023
3104a35
Replace `PyEval_GetBuiltins()` in `finalize_interpreter()` with `get_…
rwgk Mar 14, 2023
498c1d2
Bug fix: Ensure `state_dict` is destroyed before `Py_Finalize()`
rwgk Mar 14, 2023
4f5a125
Restore tests/test_embed/test_interpreter.cpp from google_pywrapcc_main
rwgk Mar 15, 2023
0a59ad2
Restore include/pybind11/embed.h from google_pywrapcc_main
rwgk Mar 15, 2023
4312d6e
Merge branch 'google_pywrapcc_main' into native_enum_pywrapcc_wrk
rwgk Mar 15, 2023
fdfb78a
Undo unrelated one-line change (from `auto` to `internals`).
rwgk Mar 15, 2023
8f24fc9
Merge branch 'google_pywrapcc_main' into native_enum_pywrapcc
rwgk Mar 15, 2023
531992c
Merge branch 'google_pywrapcc_main' into native_enum_pywrapcc
rwgk Mar 15, 2023
1dde266
Add missing `test_class_with_enum`
rwgk Mar 16, 2023
0a9c6fb
Add note to docs/classes.rst pointing to PR #30005
rwgk Mar 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ set(PYBIND11_HEADERS
include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h
include/pybind11/detail/init.h
include/pybind11/detail/internals.h
include/pybind11/detail/native_enum_data.h
include/pybind11/detail/smart_holder_poc.h
include/pybind11/detail/smart_holder_sfinae_hooks_only.h
include/pybind11/detail/smart_holder_type_casters.h
Expand All @@ -141,6 +142,7 @@ set(PYBIND11_HEADERS
include/pybind11/gil.h
include/pybind11/iostream.h
include/pybind11/functional.h
include/pybind11/native_enum.h
include/pybind11/numpy.h
include/pybind11/operators.h
include/pybind11/pybind11.h
Expand Down
5 changes: 5 additions & 0 deletions docs/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -539,3 +539,8 @@ The ``name`` property returns the name of the enum value as a unicode string.
...

By default, these are omitted to conserve space.

.. note::

``py::native_enum`` was added as an alternative to ``py::enum_``
with http://github.com/google/pywrapcc/pull/30005
149 changes: 137 additions & 12 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "detail/common.h"
#include "detail/descr.h"
#include "detail/native_enum_data.h"
#include "detail/smart_holder_sfinae_hooks_only.h"
#include "detail/type_caster_base.h"
#include "detail/type_caster_odr_guard.h"
Expand Down Expand Up @@ -63,12 +64,6 @@ using make_caster_for_intrinsic = type_caster<type>;
template <typename type>
using make_caster = make_caster_for_intrinsic<intrinsic_t<type>>;

template <typename T>
struct type_uses_smart_holder_type_caster {
static constexpr bool value
= std::is_base_of<smart_holder_type_caster_base_tag, make_caster<T>>::value;
};

// Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T
template <typename T>
typename make_caster<T>::template cast_op_type<T> cast_op(make_caster<T> &caster) {
Expand All @@ -81,6 +76,126 @@ cast_op(make_caster<T> &&caster) {
template cast_op_type<typename std::add_rvalue_reference<T>::type>();
}

template <typename EnumType>
class type_caster_enum_type {
private:
using Underlying = typename std::underlying_type<EnumType>::type;

public:
static constexpr auto name = const_name<EnumType>();

template <typename SrcType>
static handle cast(SrcType &&src, return_value_policy, handle parent) {
auto const &natives = cross_extension_shared_states::native_enum_type_map::get();
auto found = natives.find(std::type_index(typeid(EnumType)));
if (found != natives.end()) {
return handle(found->second)(static_cast<Underlying>(src)).release();
}
return type_caster_for_class_<EnumType>::cast(
std::forward<SrcType>(src),
// Fixes https://github.com/pybind/pybind11/pull/3643#issuecomment-1022987818:
return_value_policy::copy,
parent);
}

bool load(handle src, bool convert) {
auto const &natives = cross_extension_shared_states::native_enum_type_map::get();
auto found = natives.find(std::type_index(typeid(EnumType)));
if (found != natives.end()) {
if (!isinstance(src, found->second)) {
return false;
}
type_caster<Underlying> underlying_caster;
if (!underlying_caster.load(src.attr("value"), convert)) {
pybind11_fail("native_enum internal consistency failure.");
}
value = static_cast<EnumType>(static_cast<Underlying>(underlying_caster));
return true;
}
if (!pybind11_enum_) {
pybind11_enum_.reset(new type_caster_for_class_<EnumType>());
}
return pybind11_enum_->load(src, convert);
}

template <typename T>
using cast_op_type = detail::cast_op_type<T>;

// NOLINTNEXTLINE(google-explicit-constructor)
operator EnumType *() {
if (!pybind11_enum_) {
return &value;
}
return pybind11_enum_->operator EnumType *();
}

// NOLINTNEXTLINE(google-explicit-constructor)
operator EnumType &() {
if (!pybind11_enum_) {
return value;
}
return pybind11_enum_->operator EnumType &();
}

private:
std::unique_ptr<type_caster_for_class_<EnumType>> pybind11_enum_;
EnumType value;
};

template <typename EnumType, typename SFINAE = void>
struct type_caster_enum_type_enabled : std::true_type {};

template <typename T>
struct type_uses_type_caster_enum_type {
static constexpr bool value
= std::is_enum<T>::value && type_caster_enum_type_enabled<T>::value;
};

template <typename EnumType>
class type_caster<EnumType, detail::enable_if_t<type_uses_type_caster_enum_type<EnumType>::value>>
: public type_caster_enum_type<EnumType> {};

template <typename T>
struct type_uses_smart_holder_type_caster {
static constexpr bool value
= std::is_base_of<smart_holder_type_caster_base_tag, make_caster<T>>::value
#ifdef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
|| type_uses_type_caster_enum_type<T>::value
#endif
;
};

template <typename T, typename SFINAE = void>
struct type_caster_classh_enum_aware : type_caster<T> {};

#ifdef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
template <typename EnumType>
struct type_caster_classh_enum_aware<
EnumType,
detail::enable_if_t<type_uses_type_caster_enum_type<EnumType>::value>>
: type_caster_for_class_<EnumType> {};
#endif

template <typename T, detail::enable_if_t<std::is_enum<T>::value, int> = 0>
bool isinstance_native_enum_impl(handle obj, const std::type_info &tp) {
auto const &natives = cross_extension_shared_states::native_enum_type_map::get();
auto found = natives.find(tp);
if (found == natives.end()) {
return false;
}
return isinstance(obj, found->second);
}

template <typename T, detail::enable_if_t<!std::is_enum<T>::value, int> = 0>
bool isinstance_native_enum_impl(handle, const std::type_info &) {
return false;
}

template <typename T>
bool isinstance_native_enum(handle obj, const std::type_info &tp) {
return isinstance_native_enum_impl<intrinsic_t<T>>(obj, tp);
}

template <typename type>
class type_caster<std::reference_wrapper<type>> {
private:
Expand Down Expand Up @@ -1024,11 +1139,11 @@ using move_never = none_of<move_always<T>, move_if_unreferenced<T>>;
// non-reference/pointer `type`s and reference/pointers from a type_caster_generic are safe;
// everything else returns a reference/pointer to a local variable.
template <typename type>
using cast_is_temporary_value_reference
= bool_constant<(std::is_reference<type>::value || std::is_pointer<type>::value)
&& !std::is_base_of<type_caster_generic, make_caster<type>>::value
&& !type_uses_smart_holder_type_caster<intrinsic_t<type>>::value
&& !std::is_same<intrinsic_t<type>, void>::value>;
using cast_is_temporary_value_reference = bool_constant<
(std::is_reference<type>::value || std::is_pointer<type>::value)
&& !std::is_base_of<type_caster_generic, make_caster<type>>::value
&& !std::is_base_of<smart_holder_type_caster_base_tag, make_caster<type>>::value
&& !std::is_same<intrinsic_t<type>, void>::value>;

// When a value returned from a C++ function is being cast back to Python, we almost always want to
// force `policy = move`, regardless of the return value policy the function/method was declared
Expand Down Expand Up @@ -1086,8 +1201,18 @@ PYBIND11_NAMESPACE_END(detail)
template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::value, int> = 0>
T cast(const handle &handle) {
using namespace detail;
static_assert(!cast_is_temporary_value_reference<T>::value,
constexpr bool is_enum_cast = type_uses_type_caster_enum_type<intrinsic_t<T>>::value;
static_assert(!cast_is_temporary_value_reference<T>::value || is_enum_cast,
"Unable to cast type to reference: value is local to type caster");
#ifndef NDEBUG
if (is_enum_cast && cast_is_temporary_value_reference<T>::value) {
if (cross_extension_shared_states::native_enum_type_map::get().count(
std::type_index(typeid(intrinsic_t<T>)))
!= 0) {
pybind11_fail("Unable to cast native enum type to reference");
}
}
#endif
return cast_op<T>(load_type<T>(handle));
}

Expand Down
129 changes: 129 additions & 0 deletions include/pybind11/detail/native_enum_data.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (c) 2022 The pybind Community.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#pragma once

#include "../pytypes.h"
#include "abi_platform_id.h"
#include "common.h"
#include "cross_extension_shared_state.h"
#include "type_map.h"

#include <string>
#include <typeindex>

PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)

class native_enum_data {
public:
native_enum_data(const char *enum_name,
const std::type_index &enum_type_index,
bool use_int_enum)
: enum_name_encoded{enum_name}, enum_type_index{enum_type_index},
use_int_enum{use_int_enum}, enum_name{enum_name} {}

native_enum_data(const native_enum_data &) = delete;
native_enum_data &operator=(const native_enum_data &) = delete;

void disarm_correct_use_check() const { correct_use_check = false; }
void arm_correct_use_check() const { correct_use_check = true; }

// This is a separate public function only to enable easy unit testing.
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.";
}

#if !defined(NDEBUG)
// This dtor cannot easily be unit tested because it terminates the process.
~native_enum_data() {
if (correct_use_check) {
pybind11_fail(was_not_added_error_message());
}
}
#endif

private:
mutable bool correct_use_check{false};

public:
std::string enum_name_encoded;
std::type_index enum_type_index;
bool use_int_enum;
bool export_values_flag{false};
str enum_name;
list members;
list docs;
};

PYBIND11_NAMESPACE_END(detail)

PYBIND11_NAMESPACE_BEGIN(cross_extension_shared_states)

struct native_enum_type_map_v1_adapter {
static constexpr const char *abi_id() {
return "__pybind11_native_enum_type_map_v1" PYBIND11_PLATFORM_ABI_ID_V4 "__";
}

using payload_type = detail::type_map<PyObject *>;

static void payload_clear(payload_type &payload) {
for (auto it : payload) {
Py_DECREF(it.second);
}
payload.clear();
}
};

using native_enum_type_map_v1
= detail::cross_extension_shared_state<native_enum_type_map_v1_adapter>;
using native_enum_type_map = native_enum_type_map_v1;

PYBIND11_NAMESPACE_END(cross_extension_shared_states)

PYBIND11_NAMESPACE_BEGIN(detail)

inline void native_enum_add_to_parent(object parent, const detail::native_enum_data &data) {
data.disarm_correct_use_check();
if (hasattr(parent, data.enum_name)) {
pybind11_fail("pybind11::native_enum<...>(\"" + data.enum_name_encoded
+ "\"): an object with that name is already defined");
}
auto enum_module = reinterpret_steal<object>(PyImport_ImportModule("enum"));
if (!enum_module) {
raise_from(PyExc_SystemError,
"`import enum` FAILED at " __FILE__ ":" PYBIND11_TOSTRING(__LINE__));
}
auto py_enum_type = enum_module.attr(data.use_int_enum ? "IntEnum" : "Enum");
auto py_enum = py_enum_type(data.enum_name, data.members);
if (hasattr(parent, "__module__")) {
// Enum nested in class:
py_enum.attr("__module__") = parent.attr("__module__");
} else {
py_enum.attr("__module__") = parent;
}
parent.attr(data.enum_name) = py_enum;
if (data.export_values_flag) {
for (auto member : data.members) {
auto member_name = member[int_(0)];
if (hasattr(parent, member_name)) {
pybind11_fail("pybind11::native_enum<...>(\"" + data.enum_name_encoded
+ "\").value(\"" + member_name.cast<std::string>()
+ "\"): an object with that name is already defined");
}
parent.attr(member_name) = py_enum[member_name];
}
}
for (auto doc : data.docs) {
py_enum[doc[int_(0)]].attr("__doc__") = doc[int_(1)];
}
cross_extension_shared_states::native_enum_type_map::get()[data.enum_type_index]
= py_enum.release().ptr();
}

PYBIND11_NAMESPACE_END(detail)

PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
7 changes: 7 additions & 0 deletions include/pybind11/detail/smart_holder_sfinae_hooks_only.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)

constexpr bool smart_holder_is_default_holder_type =
#ifdef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
true;
#else
false;
#endif

template <typename T>
struct is_smart_holder_type : std::false_type {};

Expand Down
2 changes: 2 additions & 0 deletions include/pybind11/embed.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ inline void initialize_interpreter(bool init_signal_handlers = true,

\endrst */
inline void finalize_interpreter() {
cross_extension_shared_states::native_enum_type_map::scoped_clear native_enum_type_map_clear;

// Get the internals pointer (without creating it if it doesn't exist). It's possible for the
// internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()`
// during destruction), so we get the pointer-pointer here and check it after Py_Finalize().
Expand Down
Loading