mirror of
https://github.com/pybind/pybind11.git
synced 2026-03-14 20:27:47 +00:00
* Apply smart_holder-branch-based PR #5280 on top of master. * Add pytest.skip("GraalPy does not raise UnicodeDecodeError") * Add `parent_scope` as first argument to `py::native_enum` ctor. * Replace `operator+=` API with `.finalize()` API. The error messages still need cleanup. * Resolve clang-tidy performance-unnecessary-value-param errors * Rename (effectively) native_enum_add_to_parent() -> finalize() * Update error message: pybind11::native_enum<...>("Fake", ...): MISSING .finalize() * Pass py::module_ by reference to resolve clang-tidy errors (this is entirely inconsequential otherwise for all practical purposes). * test_native_enum_correct_use_failure -> test_native_enum_missing_finalize_failure * Add test_native_enum_double_finalize(), test_native_enum_value_after_finalize() * Clean up public/protected API. * [ci skip] Update the Enumerations section in classes.rst * Rename `py::native_enum_kind` → `py::enum_kind` as suggested by gh-henryiii: https://github.com/pybind/pybind11/pull/5555#issuecomment-2711672335 * Experiment: StrEnum enum.StrEnum does not map to C++ enum: * https://chatgpt.com/share/67d5e965-ccb0-8008-95b7-0df2502309b3 ``` ============================= test session starts ============================== platform linux -- Python 3.12.3, pytest-8.3.3, pluggy-1.5.0 C++ Info: 13.3.0 C++20 __pybind11_internals_v10000000_system_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_1__ PYBIND11_SIMPLE_GIL_MANAGEMENT=False PYBIND11_NUMPY_1_ONLY=False configfile: pytest.ini plugins: parallel-0.1.1, xdist-3.6.1 collected 40 items / 39 deselected / 1 selected test_native_enum.py F [100%] =================================== FAILURES =================================== ________________________ test_native_enum_StrEnum_greek ________________________ def test_native_enum_StrEnum_greek(): assert not hasattr(m, "greek") > m.native_enum_StrEnum_greek(m) test_native_enum.py:150: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /usr/lib/python3.12/enum.py:764: in __call__ return cls._create_( boundary = None cls = <enum 'StrEnum'> module = None names = [('Alpha', 10), ('Omega', 20)] qualname = None start = 1 type = None value = 'greek' values = () /usr/lib/python3.12/enum.py:917: in _create_ return metacls.__new__(metacls, class_name, bases, classdict, boundary=boundary) _ = <class 'str'> bases = (<enum 'StrEnum'>,) boundary = None class_name = 'greek' classdict = {'_generate_next_value_': <function StrEnum._generate_next_value_ at 0x701ec1711e40>, 'Alpha': 10, 'Omega': 20, '__module__': 'test_native_enum'} cls = <enum 'StrEnum'> first_enum = <enum 'StrEnum'> item = ('Omega', 20) member_name = 'Omega' member_value = 20 metacls = <class 'enum.EnumType'> module = 'test_native_enum' names = [('Alpha', 10), ('Omega', 20)] qualname = None start = 1 type = None /usr/lib/python3.12/enum.py:606: in __new__ raise exc.with_traceback(tb) __class__ = <class 'enum.EnumType'> __new__ = <function StrEnum.__new__ at 0x701ec1711da0> _gnv = <staticmethod(<function StrEnum._generate_next_value_ at 0x701ec1711e40>)> _order_ = None _simple = False bases = (<enum 'StrEnum'>,) boundary = None classdict = {'Alpha': <enum._proto_member object at 0x701ebc74f9b0>, 'Omega': <enum._proto_member object at 0x701ebc74cce0>, '__module__': 'test_native_enum', '_all_bits_': 0, ...} cls = 'greek' exc = TypeError('10 is not a string') first_enum = <enum 'StrEnum'> ignore = ['_ignore_'] invalid_names = set() key = '_ignore_' kwds = {} member_names = {'Alpha': None, 'Omega': None} member_type = <class 'str'> metacls = <class 'enum.EnumType'> name = 'Omega' save_new = False tb = <traceback object at 0x701ebc7a6cc0> use_args = True value = 20 /usr/lib/python3.12/enum.py:596: in __new__ enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) __class__ = <class 'enum.EnumType'> __new__ = <function StrEnum.__new__ at 0x701ec1711da0> _gnv = <staticmethod(<function StrEnum._generate_next_value_ at 0x701ec1711e40>)> _order_ = None _simple = False bases = (<enum 'StrEnum'>,) boundary = None classdict = {'Alpha': <enum._proto_member object at 0x701ebc74f9b0>, 'Omega': <enum._proto_member object at 0x701ebc74cce0>, '__module__': 'test_native_enum', '_all_bits_': 0, ...} cls = 'greek' exc = TypeError('10 is not a string') first_enum = <enum 'StrEnum'> ignore = ['_ignore_'] invalid_names = set() key = '_ignore_' kwds = {} member_names = {'Alpha': None, 'Omega': None} member_type = <class 'str'> metacls = <class 'enum.EnumType'> name = 'Omega' save_new = False tb = <traceback object at 0x701ebc7a6cc0> use_args = True value = 20 /usr/lib/python3.12/enum.py:271: in __set_name__ enum_member = enum_class._new_member_(enum_class, *args) args = (10,) enum_class = <enum 'greek'> member_name = 'Alpha' self = <enum._proto_member object at 0x701ebc74f9b0> value = 10 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ cls = <enum 'greek'>, values = (10,) def __new__(cls, *values): "values must already be of type `str`" if len(values) > 3: raise TypeError('too many arguments for str(): %r' % (values, )) if len(values) == 1: # it must be a string if not isinstance(values[0], str): > raise TypeError('%r is not a string' % (values[0], )) E TypeError: 10 is not a string cls = <enum 'greek'> values = (10,) /usr/lib/python3.12/enum.py:1322: TypeError =========================== short test summary info ============================ FAILED test_native_enum.py::test_native_enum_StrEnum_greek - TypeError: 10 is... ======================= 1 failed, 39 deselected in 0.07s ======================= ERROR: completed_process.returncode=1 ``` * Remove StrEnum code. * Make enum_kind::Enum the default kind. * Catch redundant .export_values() calls. * [ci skip] Add back original documentation for `py::enum_` under new advanced/deprecated.rst * [ci skip] Add documentation for `py::enum_kind` and `py::detail::type_caster_enum_type_enabled` * Rename `Type` to `EnumType` for readability. * Eliminate py::enum_kind, use "enum.Enum", "enum.IntEnum" directly. This is still WIP. * EXPERIMENTAL StrEnum code. To be removed. * Remove experimental StrEnum code: My judgement: Supporting StrEnum is maybe nice, but not very valuable. I don't think it is worth the extra C++ code. A level of indirection would need to be managed, e.g. RED ↔ Python "r" ↔ C++ 0 Green ↔ Python "g" ↔ C++ 1 These mappings would need to be stored and processed. * Add test with enum.IntFlag (no production code changes required). * First import_or_getattr() implementation (dedicated tests are still missing). * Fix import_or_getattr() implementation, add tests, fix clang-tidy errors. * [ci skip] Update classes.rst: replace `py::enum_kind` with `native_type_name` * For "constructor similar to that of enum.Enum" point to https://docs.python.org/3/howto/enum.html#functional-api, as suggested by gh-timohl (https://github.com/pybind/pybind11/pull/5555#discussion_r2009277507). * Advertise Enum, IntEnum, Flag, IntFlags are compatible stdlib enum types in the documentation (as suggested by gh-timohl, https://github.com/pybind/pybind11/pull/5555#pullrequestreview-2708832587); add test for enum.Flag to ensure that is actually true.
150 lines
6.2 KiB
C++
150 lines
6.2 KiB
C++
/*
|
|
tests/test_enums.cpp -- enumerations
|
|
|
|
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
|
|
|
|
All rights reserved. Use of this source code is governed by a
|
|
BSD-style license that can be found in the LICENSE file.
|
|
*/
|
|
|
|
#include "pybind11_tests.h"
|
|
|
|
TEST_SUBMODULE(enums, m) {
|
|
// test_unscoped_enum
|
|
enum UnscopedEnum { EOne = 1, ETwo, EThree };
|
|
py::enum_<UnscopedEnum>(m, "UnscopedEnum", py::arithmetic(), "An unscoped enumeration")
|
|
.value("EOne", EOne, "Docstring for EOne")
|
|
.value("ETwo", ETwo, "Docstring for ETwo")
|
|
.value("EThree", EThree, "Docstring for EThree")
|
|
.export_values();
|
|
|
|
// test_scoped_enum
|
|
enum class ScopedEnum { Two = 2, Three };
|
|
py::enum_<ScopedEnum>(m, "ScopedEnum", py::arithmetic())
|
|
.value("Two", ScopedEnum::Two)
|
|
.value("Three", ScopedEnum::Three);
|
|
|
|
m.def("test_scoped_enum", [](ScopedEnum z) {
|
|
return "ScopedEnum::" + std::string(z == ScopedEnum::Two ? "Two" : "Three");
|
|
});
|
|
|
|
// test_binary_operators
|
|
enum Flags { Read = 4, Write = 2, Execute = 1 };
|
|
py::enum_<Flags>(m, "Flags", py::arithmetic())
|
|
.value("Read", Flags::Read)
|
|
.value("Write", Flags::Write)
|
|
.value("Execute", Flags::Execute)
|
|
.export_values();
|
|
|
|
// test_implicit_conversion
|
|
class ClassWithUnscopedEnum {
|
|
public:
|
|
enum EMode { EFirstMode = 1, ESecondMode };
|
|
|
|
static EMode test_function(EMode mode) { return mode; }
|
|
};
|
|
py::class_<ClassWithUnscopedEnum> exenum_class(m, "ClassWithUnscopedEnum");
|
|
exenum_class.def_static("test_function", &ClassWithUnscopedEnum::test_function);
|
|
py::enum_<ClassWithUnscopedEnum::EMode>(exenum_class, "EMode")
|
|
.value("EFirstMode", ClassWithUnscopedEnum::EFirstMode)
|
|
.value("ESecondMode", ClassWithUnscopedEnum::ESecondMode)
|
|
.export_values();
|
|
|
|
// test_enum_to_int
|
|
m.def("test_enum_to_int", [](int) {});
|
|
m.def("test_enum_to_uint", [](uint32_t) {});
|
|
m.def("test_enum_to_long_long", [](long long) {});
|
|
|
|
// test_duplicate_enum_name
|
|
enum SimpleEnum { ONE, TWO, THREE };
|
|
|
|
m.def("register_bad_enum", [m]() {
|
|
py::enum_<SimpleEnum>(m, "SimpleEnum")
|
|
.value("ONE", SimpleEnum::ONE) // NOTE: all value function calls are called with the
|
|
// same first parameter value
|
|
.value("ONE", SimpleEnum::TWO)
|
|
.value("ONE", SimpleEnum::THREE)
|
|
.export_values();
|
|
});
|
|
|
|
// test_enum_scalar
|
|
enum UnscopedUCharEnum : unsigned char {};
|
|
enum class ScopedShortEnum : short {};
|
|
enum class ScopedLongEnum : long {};
|
|
enum UnscopedUInt64Enum : std::uint64_t {};
|
|
static_assert(
|
|
py::detail::all_of<
|
|
std::is_same<py::enum_<UnscopedUCharEnum>::Scalar, unsigned char>,
|
|
std::is_same<py::enum_<ScopedShortEnum>::Scalar, short>,
|
|
std::is_same<py::enum_<ScopedLongEnum>::Scalar, long>,
|
|
std::is_same<py::enum_<UnscopedUInt64Enum>::Scalar, std::uint64_t>>::value,
|
|
"Error during the deduction of enum's scalar type with normal integer underlying");
|
|
|
|
// test_enum_scalar_with_char_underlying
|
|
enum class ScopedCharEnum : char { Zero, Positive };
|
|
enum class ScopedWCharEnum : wchar_t { Zero, Positive };
|
|
enum class ScopedChar32Enum : char32_t { Zero, Positive };
|
|
enum class ScopedChar16Enum : char16_t { Zero, Positive };
|
|
|
|
// test the scalar of char type enums according to chapter 'Character types'
|
|
// from https://en.cppreference.com/w/cpp/language/types
|
|
static_assert(
|
|
py::detail::any_of<
|
|
std::is_same<py::enum_<ScopedCharEnum>::Scalar, signed char>, // e.g. gcc on x86
|
|
std::is_same<py::enum_<ScopedCharEnum>::Scalar, unsigned char> // e.g. arm linux
|
|
>::value,
|
|
"char should be cast to either signed char or unsigned char");
|
|
static_assert(sizeof(py::enum_<ScopedWCharEnum>::Scalar) == 2
|
|
|| sizeof(py::enum_<ScopedWCharEnum>::Scalar) == 4,
|
|
"wchar_t should be either 16 bits (Windows) or 32 (everywhere else)");
|
|
static_assert(
|
|
py::detail::all_of<
|
|
std::is_same<py::enum_<ScopedChar32Enum>::Scalar, std::uint_least32_t>,
|
|
std::is_same<py::enum_<ScopedChar16Enum>::Scalar, std::uint_least16_t>>::value,
|
|
"char32_t, char16_t (and char8_t)'s size, signedness, and alignment is determined");
|
|
#if defined(PYBIND11_HAS_U8STRING)
|
|
enum class ScopedChar8Enum : char8_t { Zero, Positive };
|
|
static_assert(std::is_same<py::enum_<ScopedChar8Enum>::Scalar, unsigned char>::value);
|
|
#endif
|
|
|
|
// test_char_underlying_enum
|
|
py::enum_<ScopedCharEnum>(m, "ScopedCharEnum")
|
|
.value("Zero", ScopedCharEnum::Zero)
|
|
.value("Positive", ScopedCharEnum::Positive);
|
|
py::enum_<ScopedWCharEnum>(m, "ScopedWCharEnum")
|
|
.value("Zero", ScopedWCharEnum::Zero)
|
|
.value("Positive", ScopedWCharEnum::Positive);
|
|
py::enum_<ScopedChar32Enum>(m, "ScopedChar32Enum")
|
|
.value("Zero", ScopedChar32Enum::Zero)
|
|
.value("Positive", ScopedChar32Enum::Positive);
|
|
py::enum_<ScopedChar16Enum>(m, "ScopedChar16Enum")
|
|
.value("Zero", ScopedChar16Enum::Zero)
|
|
.value("Positive", ScopedChar16Enum::Positive);
|
|
|
|
// test_bool_underlying_enum
|
|
enum class ScopedBoolEnum : bool { FALSE, TRUE };
|
|
|
|
// bool is unsigned (std::is_signed returns false) and 1-byte long, so represented with u8
|
|
static_assert(std::is_same<py::enum_<ScopedBoolEnum>::Scalar, std::uint8_t>::value, "");
|
|
|
|
py::enum_<ScopedBoolEnum>(m, "ScopedBoolEnum")
|
|
.value("FALSE", ScopedBoolEnum::FALSE)
|
|
.value("TRUE", ScopedBoolEnum::TRUE);
|
|
|
|
#if defined(__MINGW32__)
|
|
m.attr("obj_cast_UnscopedEnum_ptr") = "MinGW: dangling pointer to an unnamed temporary may be "
|
|
"used [-Werror=dangling-pointer=]";
|
|
#else
|
|
m.def("obj_cast_UnscopedEnum_ptr", [](const py::object &obj) {
|
|
// https://github.com/OpenImageIO/oiio/blob/30ea4ebdfab11aec291befbaff446f2a7d24835b/src/python/py_oiio.h#L300
|
|
if (py::isinstance<UnscopedEnum>(obj)) {
|
|
if (*obj.cast<UnscopedEnum *>() == UnscopedEnum::ETwo) {
|
|
return 2;
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
#endif
|
|
}
|