-
Notifications
You must be signed in to change notification settings - Fork 2.2k
WIP: Conversions between Python's native (stdlib) enum and C++ enums. #4329
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
Closed
Closed
Changes from 2 commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
f0a398e
First approximation.
rwgk 2da6779
Add pybind11/native_enum.h, building the native enum type. Not used b…
rwgk 1324769
Remove unused code entirely, to not trigger MSVC C4127 with the place…
rwgk 4ab579e
Insert `type_caster<EnumType>`, simply forwarding to `type_caster_bas…
rwgk 4cb7da0
Fit `native_enum` functionality into `type_caster<EnumType>`
rwgk 03ed301
Add `type_caster_enum_type_enabled<ProtoEnumType>` with test.
rwgk 2c9d5a0
Additional tests based on global testing failures: `test_pybind11_isi…
rwgk 7084826
Add `py::native_enum<smallenum>`
rwgk 1a1112c
Shorten expected "Unable to cast" message for compatibility with non-…
rwgk 0a49b5b
Update `cast_is_temporary_value_reference` to exclude `type_caster_en…
rwgk fdbd560
Move cast ptr test from test_native_enum to test_enum, undo change to…
rwgk 1cec429
Fix oversight: forgot to undo unneeded change.
rwgk 75bb576
Bug fix and test for "Unable to cast native enum type to reference" e…
rwgk 1f3d94d
Add `isinstance_native_enum()` and plug into `isinstance(handle)`
rwgk b478a55
Make test_native_enum.py output less noisy.
rwgk ae953a7
Add `enum.Enum` support, for non-integer underlying types.
rwgk 90044fb
Disable `test_obj_cast_unscoped_enum_ptr` & `test_obj_cast_color_ptr`…
rwgk daa515a
Add `.export_values()` and member `__doc__` implementations with tests.
rwgk 962ebf9
Add missing `<limits>`, while at it, also `<typeindex>` for completen…
rwgk 703372c
Move code for building the native enum type from `native_enum<>` dtor…
rwgk 252524b
Change variable name to avoid MSVC C4458 warning.
rwgk e0a4eb6
Resolve clang-tidy error (missing `explicit`).
rwgk f3ece13
Add user-friendly correct-use check (activated in Debug builds only).
rwgk 50fae46
Resolve clang-tidy error (missing `const`).
rwgk 0344e90
Catch & test all double-registration or name clash situations. Fix up…
rwgk a0f43c9
Resolve clang-tidy error (missing `!= nullptr`).
rwgk c2e6b38
Modified version of PR #4293 by @wjakob
rwgk bb6c003
Snapshot of .github/workflows/python312.yml (PR #4342)
rwgk dd0147a
Update .github/workflows/python312.yml (PR #4342)
rwgk 8737bea
Split out `get_native_enum_type_map()`, leaving `struct internals` un…
rwgk 3debb51
Organize internals-like functionality as `struct native_enum_type_map…
rwgk 61925d2
Make the internals-like mechanism reusable as `cross_extension_shared…
rwgk 3a9ecef
Rearrange code slightly to resolve clang-tidy warning.
rwgk f29f6e2
Use `detail::native_enum_type_map::get().count()` to simplify code sl…
rwgk ad1d258
Factor out pybind11/detail/abi_platform_id.h, type_map.h, cross_exten…
rwgk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// 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 "pybind11.h" | ||
|
||
#include <type_traits> | ||
|
||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) | ||
|
||
/// Conversions between Python's native (stdlib) enum types and C++ enums. | ||
template <typename Type> | ||
class native_enum { | ||
public: | ||
using Underlying = typename std::underlying_type<Type>::type; | ||
// Scalar is the integer representation of underlying type | ||
using Scalar = detail::conditional_t<detail::any_of<detail::is_std_char_type<Underlying>, | ||
std::is_same<Underlying, bool>>::value, | ||
detail::equivalent_integer_t<Underlying>, | ||
Underlying>; | ||
|
||
template <typename... Extra> | ||
native_enum(const handle &scope, const char *name, const Extra &.../*extra*/) | ||
: m_scope(reinterpret_borrow<object>(scope)), m_name(name) { | ||
constexpr bool is_arithmetic = detail::any_of<std::is_same<arithmetic, Extra>...>::value; | ||
constexpr bool is_convertible = std::is_convertible<Type, Underlying>::value; | ||
if (is_arithmetic || is_convertible) { | ||
// IGNORED. | ||
} | ||
} | ||
|
||
/// Export enumeration entries into the parent scope | ||
native_enum &export_values() { return *this; } | ||
|
||
/// Add an enumeration entry | ||
native_enum &value(char const *name, Type value, const char *doc = nullptr) { | ||
if (doc) { | ||
// IGNORED. | ||
} | ||
m_members.append(make_tuple(name, static_cast<Scalar>(value))); | ||
return *this; | ||
} | ||
|
||
native_enum(const native_enum &) = delete; | ||
native_enum &operator=(const native_enum &) = delete; | ||
|
||
~native_enum() { | ||
// Any exception here will terminate the process. | ||
auto enum_module = module_::import("enum"); | ||
auto int_enum = enum_module.attr("IntEnum"); | ||
auto int_enum_color = int_enum(m_name, m_members); | ||
int_enum_color.attr("__module__") = m_scope; | ||
m_scope.attr(m_name) = int_enum_color; | ||
// Intentionally leak Python reference. | ||
detail::get_internals().native_enum_types[std::type_index(typeid(Type))] | ||
= int_enum_color.release().ptr(); | ||
} | ||
|
||
private: | ||
object m_scope; | ||
str m_name; | ||
list m_members; | ||
}; | ||
|
||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
#include <pybind11/native_enum.h> | ||
|
||
#include "pybind11_tests.h" | ||
|
||
namespace test_native_enum { | ||
|
||
// https://en.cppreference.com/w/cpp/language/enum | ||
|
||
// enum that takes 16 bits | ||
enum smallenum : std::int16_t { a, b, c }; | ||
|
||
// color may be red (value 0), yellow (value 1), green (value 20), or blue (value 21) | ||
enum color { red, yellow, green = 20, blue }; | ||
|
||
// altitude may be altitude::high or altitude::low | ||
enum class altitude : char { | ||
high = 'h', | ||
low = 'l', // trailing comma only allowed after CWG518 | ||
}; | ||
|
||
// the constant d is 0, the constant e is 1, the constant f is 3 | ||
enum { d, e, f = e + 2 }; | ||
|
||
int pass_color(color e) { return static_cast<int>(e); } | ||
color return_color(int i) { return static_cast<color>(i); } | ||
|
||
py::handle wrap_color(py::module_ m) { | ||
auto enum_module = py::module_::import("enum"); | ||
auto int_enum = enum_module.attr("IntEnum"); | ||
using u_t = std::underlying_type<color>::type; | ||
auto members = py::make_tuple(py::make_tuple("red", static_cast<u_t>(color::red)), | ||
py::make_tuple("yellow", static_cast<u_t>(color::yellow)), | ||
py::make_tuple("green", static_cast<u_t>(color::green)), | ||
py::make_tuple("blue", static_cast<u_t>(color::blue))); | ||
auto int_enum_color = int_enum("color", members); | ||
int_enum_color.attr("__module__") = m; | ||
m.attr("color") = int_enum_color; | ||
return int_enum_color.release(); // Intentionally leak Python reference. | ||
} | ||
|
||
} // namespace test_native_enum | ||
|
||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) | ||
PYBIND11_NAMESPACE_BEGIN(detail) | ||
|
||
using namespace test_native_enum; | ||
|
||
template <> | ||
struct type_caster<color> { | ||
static handle native_type; | ||
|
||
static handle cast(const color &src, return_value_policy /* policy */, handle /* parent */) { | ||
auto u_v = static_cast<std::underlying_type<color>::type>(src); | ||
return native_type(u_v).release(); | ||
} | ||
|
||
bool load(handle src, bool /* convert */) { | ||
if (!isinstance(src, native_type)) { | ||
return false; | ||
} | ||
value = static_cast<color>(py::cast<std::underlying_type<color>::type>(src.attr("value"))); | ||
return true; | ||
} | ||
|
||
PYBIND11_TYPE_CASTER(color, const_name("<enum 'color'>")); | ||
}; | ||
|
||
handle type_caster<color>::native_type = nullptr; | ||
|
||
PYBIND11_NAMESPACE_END(detail) | ||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) | ||
|
||
TEST_SUBMODULE(native_enum, m) { | ||
using namespace test_native_enum; | ||
|
||
py::detail::type_caster<color>::native_type = wrap_color(m); | ||
|
||
m.def("pass_color", pass_color); | ||
m.def("return_color", return_color); | ||
|
||
py::native_enum<color>(m, "WIPcolor") | ||
.value("red", color::red) | ||
.value("yellow", color::yellow) | ||
.value("green", color::green) | ||
.value("blue", color::blue); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import enum | ||
|
||
import pytest | ||
|
||
from pybind11_tests import native_enum as m | ||
|
||
COLOR_MEMBERS = ( | ||
("red", 0), | ||
("yellow", 1), | ||
("green", 20), | ||
("blue", 21), | ||
) | ||
|
||
|
||
def test_enum_color_type(): | ||
assert isinstance(m.color, enum.EnumMeta) | ||
|
||
|
||
@pytest.mark.parametrize("name,value", COLOR_MEMBERS) | ||
def test_enum_color_members(name, value): | ||
assert m.color[name] == value | ||
|
||
|
||
@pytest.mark.parametrize("name,value", COLOR_MEMBERS) | ||
def test_pass_color_success(name, value): | ||
assert m.pass_color(m.color[name]) == value | ||
|
||
|
||
def test_pass_color_fail(): | ||
with pytest.raises(TypeError) as excinfo: | ||
m.pass_color(None) | ||
assert "<enum 'color'>" in str(excinfo.value) | ||
|
||
|
||
@pytest.mark.parametrize("name,value", COLOR_MEMBERS) | ||
def test_return_color_success(name, value): | ||
assert m.return_color(value) == m.color[name] | ||
|
||
|
||
def test_return_color_fail(): | ||
with pytest.raises(ValueError) as excinfo_direct: | ||
m.color(2) | ||
with pytest.raises(ValueError) as excinfo_cast: | ||
m.return_color(2) | ||
assert str(excinfo_cast.value) == str(excinfo_direct.value) | ||
|
||
|
||
def test_wip_color(): | ||
assert isinstance(m.WIPcolor, enum.EnumMeta) | ||
for name, value in COLOR_MEMBERS: | ||
assert m.WIPcolor[name] == value |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.