diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index ce85d997b6..21f9adcade 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1014,9 +1014,7 @@ template using is_std_char_type = any_of< template struct type_caster::value && !is_std_char_type::value>> { - using _py_type_0 = conditional_t; - using _py_type_1 = conditional_t::value, _py_type_0, typename std::make_unsigned<_py_type_0>::type>; - using py_type = conditional_t::value, double, _py_type_1>; + using py_type = conditional_t::value, double, py_int_type_for>; public: bool load(handle src, bool convert) { @@ -1042,12 +1040,8 @@ struct type_caster::value && !is_std_char_t return false; } else if (!convert && !index_check(src.ptr()) && !PYBIND11_LONG_CHECK(src.ptr())) { return false; - } else if (std::is_unsigned::value) { - py_value = as_unsigned(src.ptr()); - } else { // signed integer: - py_value = sizeof(T) <= sizeof(long) - ? (py_type) PyLong_AsLong(src.ptr()) - : (py_type) PYBIND11_LONG_AS_LONGLONG(src.ptr()); + } else { + py_value = as_integer(src.ptr()); } // Python API reported an error diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index cb05bd14d1..35d0c0ba83 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -175,6 +175,7 @@ #define PYBIND11_BYTES_SIZE PyBytes_Size #define PYBIND11_LONG_CHECK(o) PyLong_Check(o) #define PYBIND11_LONG_AS_LONGLONG(o) PyLong_AsLongLong(o) +#define PYBIND11_LONG_AS_UNSIGNED_LONGLONG(o) PyLong_AsUnsignedLongLong(o) #define PYBIND11_LONG_FROM_SIGNED(o) PyLong_FromSsize_t((ssize_t) o) #define PYBIND11_LONG_FROM_UNSIGNED(o) PyLong_FromSize_t((size_t) o) #define PYBIND11_BYTES_NAME "bytes" @@ -203,6 +204,7 @@ #define PYBIND11_BYTES_SIZE PyString_Size #define PYBIND11_LONG_CHECK(o) (PyInt_Check(o) || PyLong_Check(o)) #define PYBIND11_LONG_AS_LONGLONG(o) (PyInt_Check(o) ? (long long) PyLong_AsLong(o) : PyLong_AsLongLong(o)) +#define PYBIND11_LONG_AS_UNSIGNED_LONGLONG(o) (PyInt_Check(o) ? (unsigned long long) PyLong_AsUnsignedLong(o) : PyLong_AsUnsignedLongLong(o)) #define PYBIND11_LONG_FROM_SIGNED(o) PyInt_FromSsize_t((ssize_t) o) // Returns long if needed. #define PYBIND11_LONG_FROM_UNSIGNED(o) PyInt_FromSize_t((size_t) o) // Returns long if needed. #define PYBIND11_BYTES_NAME "str" diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index d8d58b1d56..83264759a6 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -10,6 +10,7 @@ #pragma once #include "detail/common.h" +#include "detail/typeid.h" #include "buffer_info.h" #include #include @@ -1091,23 +1092,37 @@ class bool_ : public object { }; PYBIND11_NAMESPACE_BEGIN(detail) +template +using py_signed_int_type_for = conditional_t; + +template +using py_unsigned_int_type_for = typename std::make_unsigned>::type; + +template +using py_int_type_for = conditional_t::value, py_signed_int_type_for, py_unsigned_int_type_for>; + // Converts a value to the given unsigned type. If an error occurs, you get back (Unsigned) -1; // otherwise you get back the unsigned long or unsigned long long value cast to (Unsigned). // (The distinction is critically important when casting a returned -1 error value to some other // unsigned type: (A)-1 != (B)-1 when A and B are unsigned types of different sizes). template Unsigned as_unsigned(PyObject *o) { - if (sizeof(Unsigned) <= sizeof(unsigned long) -#if PY_VERSION_HEX < 0x03000000 - || PyInt_Check(o) -#endif - ) { - unsigned long v = PyLong_AsUnsignedLong(o); - return v == (unsigned long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v; + using py_type = py_unsigned_int_type_for; + py_type v = (sizeof(Unsigned) <= sizeof(unsigned long)) + ? (py_type) PyLong_AsUnsignedLong(o) + : (py_type) PYBIND11_LONG_AS_UNSIGNED_LONGLONG(o); + return v == (py_unsigned_int_type_for) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v; +} + +template +Int as_integer(PyObject *o) { + if (std::is_unsigned::value) { + return as_unsigned(o); } else { - unsigned long long v = PyLong_AsUnsignedLongLong(o); - return v == (unsigned long long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v; + return sizeof(Int) <= sizeof(long) + ? (Int) PyLong_AsLong(o) + : (Int) PYBIND11_LONG_AS_LONGLONG(o); } } PYBIND11_NAMESPACE_END(detail) @@ -1137,11 +1152,18 @@ class int_ : public object { template ::value, int> = 0> operator T() const { - return std::is_unsigned::value - ? detail::as_unsigned(m_ptr) - : sizeof(T) <= sizeof(long) - ? (T) PyLong_AsLong(m_ptr) - : (T) PYBIND11_LONG_AS_LONGLONG(m_ptr); + using py_type = detail::py_int_type_for; + auto value = detail::as_integer(m_ptr); + if (PyErr_Occurred()) + throw error_already_set(); + if (sizeof(py_type) != sizeof(T) && value != (py_type) (T) value) +#if defined(NDEBUG) + throw cast_error("Unable to cast Python instance to C++ type (compile in debug mode for details)"); +#else + throw cast_error("Unable to cast Python instance of type " + + (std::string) pybind11::str(type::handle_of(*this)) + " to C++ type '" + type_id() + "'"); +#endif + return (T) value; } }; diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 709611d222..21453f5763 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -270,6 +270,7 @@ TEST_SUBMODULE(pytypes, m) { throw std::runtime_error("Invalid type"); }); + // test_implicit_casting m.def("get_implicit_casting", []() { py::dict d; d["char*_i1"] = "abc"; @@ -307,6 +308,12 @@ TEST_SUBMODULE(pytypes, m) { ); }); + // (see also test_builtin_casters) + m.def("implicitly_cast_to_int32", [](py::int_ value) { return int32_t{value}; }); + m.def("implicitly_cast_to_uint32", [](py::int_ value) { return uint32_t{value}; }); + m.def("implicitly_cast_to_int64", [](py::int_ value) { return int64_t{value}; }); + m.def("implicitly_cast_to_uint64", [](py::int_ value) { return uint64_t{value}; }); + // test_print m.def("print_function", []() { py::print("Hello, World!"); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index b1509a0200..e6b57219d4 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -331,6 +331,26 @@ def test_implicit_casting(): } assert z["l"] == [3, 6, 9, 12, 15] + assert m.implicitly_cast_to_int32(42) == 42 + assert m.implicitly_cast_to_int32(2 ** 31 - 1) == 2 ** 31 - 1 + with pytest.raises(RuntimeError, match="Unable to cast Python instance"): + m.implicitly_cast_to_int32(2 ** 31) + + assert m.implicitly_cast_to_uint32(42) == 42 + assert m.implicitly_cast_to_uint32(2 ** 32 - 1) == 2 ** 32 - 1 + with pytest.raises(RuntimeError, match="Unable to cast Python instance"): + m.implicitly_cast_to_uint32(2 ** 32) + + assert m.implicitly_cast_to_int64(42) == 42 + assert m.implicitly_cast_to_int64(2 ** 63 - 1) == 2 ** 63 - 1 + with pytest.raises(RuntimeError, match="Unable to cast Python instance"): + m.implicitly_cast_to_int64(2 ** 63) + + assert m.implicitly_cast_to_uint64(42) == 42 + assert m.implicitly_cast_to_uint64(2 ** 64 - 1) == 2 ** 64 - 1 + with pytest.raises(RuntimeError, match="Unable to cast Python instance"): + m.implicitly_cast_to_uint64(2 ** 64) + def test_print(capture): with capture: