diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c08bb173f..e8294c83c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -120,7 +120,7 @@ The valid options are: * `-DPYBIND11_NOPYTHON=ON`: Disable all Python searching (disables tests) * `-DBUILD_TESTING=ON`: Enable the tests * `-DDOWNLOAD_CATCH=ON`: Download catch to build the C++ tests -* `-DOWNLOAD_EIGEN=ON`: Download Eigen for the NumPy tests +* `-DDOWNLOAD_EIGEN=ON`: Download Eigen for the NumPy tests * `-DPYBIND11_INSTALL=ON/OFF`: Enable the install target (on by default for the master project) * `-DUSE_PYTHON_INSTALL_DIR=ON`: Try to install into the python dir diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 7930fb994..165102443 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -765,10 +765,12 @@ template struct is_holder_type struct handle_type_name { static constexpr auto name = const_name(); }; +template <> struct handle_type_name { static constexpr auto name = const_name("bool"); }; template <> struct handle_type_name { static constexpr auto name = const_name(PYBIND11_BYTES_NAME); }; template <> struct handle_type_name { static constexpr auto name = const_name("int"); }; template <> struct handle_type_name { static constexpr auto name = const_name("Iterable"); }; template <> struct handle_type_name { static constexpr auto name = const_name("Iterator"); }; +template <> struct handle_type_name { static constexpr auto name = const_name("float"); }; template <> struct handle_type_name { static constexpr auto name = const_name("None"); }; template <> struct handle_type_name { static constexpr auto name = const_name("*args"); }; template <> struct handle_type_name { static constexpr auto name = const_name("**kwargs"); }; diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index b08bbc559..b3513da83 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -36,6 +36,9 @@ # define PYBIND11_CPP14 # if __cplusplus >= 201703L # define PYBIND11_CPP17 +# if __cplusplus >= 202002L +# define PYBIND11_CPP20 +# endif # endif # endif #elif defined(_MSC_VER) && __cplusplus == 199711L @@ -45,6 +48,9 @@ # define PYBIND11_CPP14 # if _MSVC_LANG > 201402L && _MSC_VER >= 1910 # define PYBIND11_CPP17 +# if _MSVC_LANG >= 202002L +# define PYBIND11_CPP20 +# endif # endif # endif #endif @@ -612,6 +618,18 @@ template using remove_cv_t = typename std::remove_cv::type; template using remove_reference_t = typename std::remove_reference::type; #endif +#if defined(PYBIND11_CPP20) +using std::remove_cvref; +using std::remove_cvref_t; +#else +template +struct remove_cvref { + using type = remove_cv_t>; +}; +template +using remove_cvref_t = typename remove_cvref::type; +#endif + /// Index sequences #if defined(PYBIND11_CPP14) using std::index_sequence; diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 98d21eb98..462d32474 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -10,6 +10,7 @@ #pragma once #include "../pytypes.h" +#include /// Tracks the `internals` and `type_info` ABI version independent of the main library version. /// @@ -280,21 +281,104 @@ inline internals **&get_internals_pp() { return internals_pp; } +#if PY_VERSION_HEX >= 0x03030000 +// forward decl +inline void translate_exception(std::exception_ptr); + +template >::value, int> = 0> +bool handle_nested_exception(const T &exc, const std::exception_ptr &p) { + std::exception_ptr nested = exc.nested_ptr(); + if (nested != nullptr && nested != p) { + translate_exception(nested); + return true; + } + return false; +} + +template >::value, int> = 0> +bool handle_nested_exception(const T &exc, const std::exception_ptr &p) { + if (auto *nep = dynamic_cast(std::addressof(exc))) { + return handle_nested_exception(*nep, p); + } + return false; +} + +#else + +template +bool handle_nested_exception(const T &, std::exception_ptr &) { + return false; +} +#endif + +inline bool raise_err(PyObject *exc_type, const char *msg) { +#if PY_VERSION_HEX >= 0x03030000 + if (PyErr_Occurred()) { + raise_from(exc_type, msg); + return true; + } +#endif + PyErr_SetString(exc_type, msg); + return false; +}; + inline void translate_exception(std::exception_ptr p) { + if (!p) { + return; + } try { - if (p) std::rethrow_exception(p); - } catch (error_already_set &e) { e.restore(); return; - } catch (const builtin_exception &e) { e.set_error(); return; - } catch (const std::bad_alloc &e) { PyErr_SetString(PyExc_MemoryError, e.what()); return; - } catch (const std::domain_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; - } catch (const std::invalid_argument &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; - } catch (const std::length_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; - } catch (const std::out_of_range &e) { PyErr_SetString(PyExc_IndexError, e.what()); return; - } catch (const std::range_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; - } catch (const std::overflow_error &e) { PyErr_SetString(PyExc_OverflowError, e.what()); return; - } catch (const std::exception &e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return; + std::rethrow_exception(p); + } catch (error_already_set &e) { + handle_nested_exception(e, p); + e.restore(); + return; + } catch (const builtin_exception &e) { + // Could not use template since it's an abstract class. + if (auto *nep = dynamic_cast(std::addressof(e))) { + handle_nested_exception(*nep, p); + } + e.set_error(); + return; + } catch (const std::bad_alloc &e) { + handle_nested_exception(e, p); + raise_err(PyExc_MemoryError, e.what()); + return; + } catch (const std::domain_error &e) { + handle_nested_exception(e, p); + raise_err(PyExc_ValueError, e.what()); + return; + } catch (const std::invalid_argument &e) { + handle_nested_exception(e, p); + raise_err(PyExc_ValueError, e.what()); + return; + } catch (const std::length_error &e) { + handle_nested_exception(e, p); + raise_err(PyExc_ValueError, e.what()); + return; + } catch (const std::out_of_range &e) { + handle_nested_exception(e, p); + raise_err(PyExc_IndexError, e.what()); + return; + } catch (const std::range_error &e) { + handle_nested_exception(e, p); + raise_err(PyExc_ValueError, e.what()); + return; + } catch (const std::overflow_error &e) { + handle_nested_exception(e, p); + raise_err(PyExc_OverflowError, e.what()); + return; + } catch (const std::exception &e) { + handle_nested_exception(e, p); + raise_err(PyExc_RuntimeError, e.what()); + return; + } catch (const std::nested_exception &e) { + handle_nested_exception(e, p); + raise_err(PyExc_RuntimeError, "Caught an unknown nested exception!"); + return; } catch (...) { - PyErr_SetString(PyExc_RuntimeError, "Caught an unknown exception!"); + raise_err(PyExc_RuntimeError, "Caught an unknown exception!"); return; } } diff --git a/tests/test_exceptions.cpp b/tests/test_exceptions.cpp index 25adb32ed..3aa967382 100644 --- a/tests/test_exceptions.cpp +++ b/tests/test_exceptions.cpp @@ -11,6 +11,8 @@ #include "local_bindings.h" #include "pybind11_tests.h" +#include +#include #include // A type that should be raised as an exception in Python @@ -105,7 +107,6 @@ struct PythonAlreadySetInDestructor { py::str s; }; - TEST_SUBMODULE(exceptions, m) { m.def("throw_std_exception", []() { throw std::runtime_error("This exception was intentionally thrown."); @@ -281,5 +282,12 @@ TEST_SUBMODULE(exceptions, m) { } }); + m.def("throw_nested_exception", []() { + try { + throw std::runtime_error("Inner Exception"); + } catch (const std::runtime_error &) { + std::throw_with_nested(std::runtime_error("Outer Exception")); + } + }); #endif } diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 56201a81c..d698b1312 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -239,6 +239,14 @@ def test_nested_throws(capture): assert str(excinfo.value) == "this is a helper-defined translated exception" +@pytest.mark.skipif("env.PY2") +def test_throw_nested_exception(): + with pytest.raises(RuntimeError) as excinfo: + m.throw_nested_exception() + assert str(excinfo.value) == "Outer Exception" + assert str(excinfo.value.__cause__) == "Inner Exception" + + # This can often happen if you wrap a pybind11 class in a Python wrapper def test_invalid_repr(): class MyRepr(object): diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 9a1e91881..85cb98fcb 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -13,12 +13,16 @@ TEST_SUBMODULE(pytypes, m) { + // test_bool + m.def("get_bool", []{return py::bool_(false);}); // test_int m.def("get_int", []{return py::int_(0);}); // test_iterator m.def("get_iterator", []{return py::iterator();}); // test_iterable m.def("get_iterable", []{return py::iterable();}); + // test_float + m.def("get_float", []{return py::float_(0.0f);}); // test_list m.def("list_no_args", []() { return py::list{}; }); m.def("list_ssize_t", []() { return py::list{(py::ssize_t) 0}; }); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 5215b79bc..2cd6c3f03 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -10,6 +10,10 @@ from pybind11_tests import debug_enabled from pybind11_tests import pytypes as m +def test_bool(doc): + assert doc(m.get_bool) == "get_bool() -> bool" + + def test_int(doc): assert doc(m.get_int) == "get_int() -> int" @@ -22,6 +26,10 @@ def test_iterable(doc): assert doc(m.get_iterable) == "get_iterable() -> Iterable" +def test_float(doc): + assert doc(m.get_float) == "get_float() -> float" + + def test_list(capture, doc): assert m.list_no_args() == [] assert m.list_ssize_t() == []