diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 47b042147..89f9cbd9b 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1814,6 +1814,19 @@ struct enum_base { handle m_parent; }; +template struct equivalent_integer {}; +template <> struct equivalent_integer { using type = int8_t; }; +template <> struct equivalent_integer { using type = uint8_t; }; +template <> struct equivalent_integer { using type = int16_t; }; +template <> struct equivalent_integer { using type = uint16_t; }; +template <> struct equivalent_integer { using type = int32_t; }; +template <> struct equivalent_integer { using type = uint32_t; }; +template <> struct equivalent_integer { using type = int64_t; }; +template <> struct equivalent_integer { using type = uint64_t; }; + +template +using equivalent_integer_t = typename equivalent_integer::value, sizeof(IntLike)>::type; + PYBIND11_NAMESPACE_END(detail) /// Binds C++ enumerations and enumeration classes to Python @@ -1824,13 +1837,17 @@ public: using Base::attr; using Base::def_property_readonly; using Base::def_property_readonly_static; - using Scalar = typename std::underlying_type::type; + using Underlying = typename std::underlying_type::type; + // Scalar is the integer representation of underlying type + using Scalar = detail::conditional_t, std::is_same + >::value, detail::equivalent_integer_t, Underlying>; template enum_(const handle &scope, const char *name, const Extra&... extra) : class_(scope, name, extra...), m_base(*this, scope) { constexpr bool is_arithmetic = detail::any_of...>::value; - constexpr bool is_convertible = std::is_convertible::value; + constexpr bool is_convertible = std::is_convertible::value; m_base.init(is_arithmetic, is_convertible); def(init([](Scalar i) { return static_cast(i); }), arg("value")); diff --git a/tests/test_enum.cpp b/tests/test_enum.cpp index 315308920..40c48d412 100644 --- a/tests/test_enum.cpp +++ b/tests/test_enum.cpp @@ -84,4 +84,65 @@ TEST_SUBMODULE(enums, m) { .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::Scalar, unsigned char>, + std::is_same::Scalar, short>, + std::is_same::Scalar, long>, + std::is_same::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::Scalar, signed char>, // e.g. gcc on x86 + std::is_same::Scalar, unsigned char> // e.g. arm linux + >::value, "char should be cast to either signed char or unsigned char"); + static_assert( + sizeof(py::enum_::Scalar) == 2 || + sizeof(py::enum_::Scalar) == 4 + , "wchar_t should be either 16 bits (Windows) or 32 (everywhere else)"); + static_assert(py::detail::all_of< + std::is_same::Scalar, std::uint_least32_t>, + std::is_same::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::Scalar, unsigned char>::value); +#endif + + // test_char_underlying_enum + py::enum_(m, "ScopedCharEnum") + .value("Zero", ScopedCharEnum::Zero) + .value("Positive", ScopedCharEnum::Positive); + py::enum_(m, "ScopedWCharEnum") + .value("Zero", ScopedWCharEnum::Zero) + .value("Positive", ScopedWCharEnum::Positive); + py::enum_(m, "ScopedChar32Enum") + .value("Zero", ScopedChar32Enum::Zero) + .value("Positive", ScopedChar32Enum::Positive); + py::enum_(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::Scalar, std::uint8_t>::value, ""); + + py::enum_(m, "ScopedBoolEnum") + .value("FALSE", ScopedBoolEnum::FALSE) + .value("TRUE", ScopedBoolEnum::TRUE); } diff --git a/tests/test_enum.py b/tests/test_enum.py index 62f9426ee..11cab6ddf 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -218,10 +218,16 @@ def test_binary_operators(): def test_enum_to_int(): m.test_enum_to_int(m.Flags.Read) m.test_enum_to_int(m.ClassWithUnscopedEnum.EMode.EFirstMode) + m.test_enum_to_int(m.ScopedCharEnum.Positive) + m.test_enum_to_int(m.ScopedBoolEnum.TRUE) m.test_enum_to_uint(m.Flags.Read) m.test_enum_to_uint(m.ClassWithUnscopedEnum.EMode.EFirstMode) + m.test_enum_to_uint(m.ScopedCharEnum.Positive) + m.test_enum_to_uint(m.ScopedBoolEnum.TRUE) m.test_enum_to_long_long(m.Flags.Read) m.test_enum_to_long_long(m.ClassWithUnscopedEnum.EMode.EFirstMode) + m.test_enum_to_long_long(m.ScopedCharEnum.Positive) + m.test_enum_to_long_long(m.ScopedBoolEnum.TRUE) def test_duplicate_enum_name(): @@ -230,6 +236,28 @@ def test_duplicate_enum_name(): assert str(excinfo.value) == 'SimpleEnum: element "ONE" already exists!' +def test_char_underlying_enum(): # Issue #1331/PR #1334: + assert type(m.ScopedCharEnum.Positive.__int__()) is int + assert int(m.ScopedChar16Enum.Zero) == 0 # int() call should successfully return + assert hash(m.ScopedChar32Enum.Positive) == 1 + assert m.ScopedCharEnum.Positive.__getstate__() == 1 # return type is long in py2.x + assert m.ScopedWCharEnum(1) == m.ScopedWCharEnum.Positive + with pytest.raises(TypeError): + # Enum should construct with a int, even with char underlying type + m.ScopedWCharEnum("0") + + +def test_bool_underlying_enum(): + assert type(m.ScopedBoolEnum.TRUE.__int__()) is int + assert int(m.ScopedBoolEnum.FALSE) == 0 + assert hash(m.ScopedBoolEnum.TRUE) == 1 + assert m.ScopedBoolEnum.TRUE.__getstate__() == 1 + assert m.ScopedBoolEnum(1) == m.ScopedBoolEnum.TRUE + # Enum could construct with a bool + # (bool is a strict subclass of int, and False will be converted to 0) + assert m.ScopedBoolEnum(False) == m.ScopedBoolEnum.FALSE + + def test_docstring_signatures(): for enum_type in [m.ScopedEnum, m.UnscopedEnum]: for attr in enum_type.__dict__.values():