Skip to content

Commit 2da6779

Browse files
committed
Add pybind11/native_enum.h, building the native enum type. Not used by any type_caster yet.
1 parent f0a398e commit 2da6779

File tree

6 files changed

+86
-2
lines changed

6 files changed

+86
-2
lines changed

include/pybind11/detail/internals.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
/// further ABI-incompatible changes may be made before the ABI is officially
3535
/// changed to the new version.
3636
#ifndef PYBIND11_INTERNALS_VERSION
37-
# define PYBIND11_INTERNALS_VERSION 4
37+
# define PYBIND11_INTERNALS_VERSION 5
3838
#endif
3939

4040
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
@@ -209,6 +209,9 @@ struct internals {
209209
PYBIND11_TLS_FREE(tstate);
210210
}
211211
#endif
212+
#if PYBIND11_INTERNALS_VERSION > 4
213+
type_map<PyObject *> native_enum_types;
214+
#endif
212215
};
213216

214217
/// Additional type information which does not fit into the PyTypeObject.

include/pybind11/native_enum.h

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) 2022 The pybind Community.
2+
// All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
#pragma once
6+
7+
#include "pybind11.h"
8+
9+
#include <type_traits>
10+
11+
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
12+
13+
/// Conversions between Python's native (stdlib) enum types and C++ enums.
14+
template <typename Type>
15+
class native_enum {
16+
public:
17+
using Underlying = typename std::underlying_type<Type>::type;
18+
// Scalar is the integer representation of underlying type
19+
using Scalar = detail::conditional_t<detail::any_of<detail::is_std_char_type<Underlying>,
20+
std::is_same<Underlying, bool>>::value,
21+
detail::equivalent_integer_t<Underlying>,
22+
Underlying>;
23+
24+
template <typename... Extra>
25+
native_enum(const handle &scope, const char *name, const Extra &.../*extra*/)
26+
: m_scope(reinterpret_borrow<object>(scope)), m_name(name) {
27+
constexpr bool is_arithmetic = detail::any_of<std::is_same<arithmetic, Extra>...>::value;
28+
constexpr bool is_convertible = std::is_convertible<Type, Underlying>::value;
29+
if (is_arithmetic || is_convertible) {
30+
// IGNORED.
31+
}
32+
}
33+
34+
/// Export enumeration entries into the parent scope
35+
native_enum &export_values() { return *this; }
36+
37+
/// Add an enumeration entry
38+
native_enum &value(char const *name, Type value, const char *doc = nullptr) {
39+
if (doc) {
40+
// IGNORED.
41+
}
42+
m_members.append(make_tuple(name, static_cast<Scalar>(value)));
43+
return *this;
44+
}
45+
46+
native_enum(const native_enum &) = delete;
47+
native_enum &operator=(const native_enum &) = delete;
48+
49+
~native_enum() {
50+
// Any exception here will terminate the process.
51+
auto enum_module = module_::import("enum");
52+
auto int_enum = enum_module.attr("IntEnum");
53+
auto int_enum_color = int_enum(m_name, m_members);
54+
int_enum_color.attr("__module__") = m_scope;
55+
m_scope.attr(m_name) = int_enum_color;
56+
// Intentionally leak Python reference.
57+
detail::get_internals().native_enum_types[std::type_index(typeid(Type))]
58+
= int_enum_color.release().ptr();
59+
}
60+
61+
private:
62+
object m_scope;
63+
str m_name;
64+
list m_members;
65+
};
66+
67+
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

tests/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ set(PYBIND11_TEST_FILES
142142
test_methods_and_attributes
143143
test_modules
144144
test_multiple_inheritance
145+
test_native_enum
145146
test_numpy_array
146147
test_numpy_dtypes
147148
test_numpy_vectorize

tests/extra_python_package/test_files.py

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"include/pybind11/functional.h",
3737
"include/pybind11/gil.h",
3838
"include/pybind11/iostream.h",
39+
"include/pybind11/native_enum.h",
3940
"include/pybind11/numpy.h",
4041
"include/pybind11/operators.h",
4142
"include/pybind11/options.h",

tests/test_native_enum.cpp

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// #include <pybind11/native_enum.h>
1+
#include <pybind11/native_enum.h>
22

33
#include "pybind11_tests.h"
44

@@ -77,4 +77,10 @@ TEST_SUBMODULE(native_enum, m) {
7777

7878
m.def("pass_color", pass_color);
7979
m.def("return_color", return_color);
80+
81+
py::native_enum<color>(m, "WIPcolor")
82+
.value("red", color::red)
83+
.value("yellow", color::yellow)
84+
.value("green", color::green)
85+
.value("blue", color::blue);
8086
}

tests/test_native_enum.py

+6
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,9 @@ def test_return_color_fail():
4343
with pytest.raises(ValueError) as excinfo_cast:
4444
m.return_color(2)
4545
assert str(excinfo_cast.value) == str(excinfo_direct.value)
46+
47+
48+
def test_wip_color():
49+
assert isinstance(m.WIPcolor, enum.EnumMeta)
50+
for name, value in COLOR_MEMBERS:
51+
assert m.WIPcolor[name] == value

0 commit comments

Comments
 (0)