Skip to content

Commit c8d36b3

Browse files
committed
Merge branch 'master' into sh_merge_master
2 parents d6c174c + 7e7c558 commit c8d36b3

File tree

5 files changed

+132
-14
lines changed

5 files changed

+132
-14
lines changed

.github/CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ The valid options are:
120120
* `-DPYBIND11_NOPYTHON=ON`: Disable all Python searching (disables tests)
121121
* `-DBUILD_TESTING=ON`: Enable the tests
122122
* `-DDOWNLOAD_CATCH=ON`: Download catch to build the C++ tests
123-
* `-DOWNLOAD_EIGEN=ON`: Download Eigen for the NumPy tests
123+
* `-DDOWNLOAD_EIGEN=ON`: Download Eigen for the NumPy tests
124124
* `-DPYBIND11_INSTALL=ON/OFF`: Enable the install target (on by default for the
125125
master project)
126126
* `-DUSE_PYTHON_INSTALL_DIR=ON`: Try to install into the python dir

include/pybind11/detail/common.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
# define PYBIND11_CPP14
3737
# if __cplusplus >= 201703L
3838
# define PYBIND11_CPP17
39+
# if __cplusplus >= 202002L
40+
# define PYBIND11_CPP20
41+
# endif
3942
# endif
4043
# endif
4144
#elif defined(_MSC_VER) && __cplusplus == 199711L
@@ -45,6 +48,9 @@
4548
# define PYBIND11_CPP14
4649
# if _MSVC_LANG > 201402L && _MSC_VER >= 1910
4750
# define PYBIND11_CPP17
51+
# if _MSVC_LANG >= 202002L
52+
# define PYBIND11_CPP20
53+
# endif
4854
# endif
4955
# endif
5056
#endif
@@ -612,6 +618,18 @@ template <typename T> using remove_cv_t = typename std::remove_cv<T>::type;
612618
template <typename T> using remove_reference_t = typename std::remove_reference<T>::type;
613619
#endif
614620

621+
#if defined(PYBIND11_CPP20)
622+
using std::remove_cvref;
623+
using std::remove_cvref_t;
624+
#else
625+
template <class T>
626+
struct remove_cvref {
627+
using type = remove_cv_t<remove_reference_t<T>>;
628+
};
629+
template <class T>
630+
using remove_cvref_t = typename remove_cvref<T>::type;
631+
#endif
632+
615633
/// Index sequences
616634
#if defined(PYBIND11_CPP14)
617635
using std::index_sequence;

include/pybind11/detail/internals.h

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "../pytypes.h"
1313
#include "smart_holder_sfinae_hooks_only.h"
14+
#include <exception>
1415

1516
/// Tracks the `internals` and `type_info` ABI version independent of the main library version.
1617
///
@@ -291,21 +292,104 @@ inline internals **&get_internals_pp() {
291292
return internals_pp;
292293
}
293294

295+
#if PY_VERSION_HEX >= 0x03030000
296+
// forward decl
297+
inline void translate_exception(std::exception_ptr);
298+
299+
template <class T,
300+
enable_if_t<std::is_same<std::nested_exception, remove_cvref_t<T>>::value, int> = 0>
301+
bool handle_nested_exception(const T &exc, const std::exception_ptr &p) {
302+
std::exception_ptr nested = exc.nested_ptr();
303+
if (nested != nullptr && nested != p) {
304+
translate_exception(nested);
305+
return true;
306+
}
307+
return false;
308+
}
309+
310+
template <class T,
311+
enable_if_t<!std::is_same<std::nested_exception, remove_cvref_t<T>>::value, int> = 0>
312+
bool handle_nested_exception(const T &exc, const std::exception_ptr &p) {
313+
if (auto *nep = dynamic_cast<const std::nested_exception *>(std::addressof(exc))) {
314+
return handle_nested_exception(*nep, p);
315+
}
316+
return false;
317+
}
318+
319+
#else
320+
321+
template <class T>
322+
bool handle_nested_exception(const T &, std::exception_ptr &) {
323+
return false;
324+
}
325+
#endif
326+
327+
inline bool raise_err(PyObject *exc_type, const char *msg) {
328+
#if PY_VERSION_HEX >= 0x03030000
329+
if (PyErr_Occurred()) {
330+
raise_from(exc_type, msg);
331+
return true;
332+
}
333+
#endif
334+
PyErr_SetString(exc_type, msg);
335+
return false;
336+
};
337+
294338
inline void translate_exception(std::exception_ptr p) {
339+
if (!p) {
340+
return;
341+
}
295342
try {
296-
if (p) std::rethrow_exception(p);
297-
} catch (error_already_set &e) { e.restore(); return;
298-
} catch (const builtin_exception &e) { e.set_error(); return;
299-
} catch (const std::bad_alloc &e) { PyErr_SetString(PyExc_MemoryError, e.what()); return;
300-
} catch (const std::domain_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return;
301-
} catch (const std::invalid_argument &e) { PyErr_SetString(PyExc_ValueError, e.what()); return;
302-
} catch (const std::length_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return;
303-
} catch (const std::out_of_range &e) { PyErr_SetString(PyExc_IndexError, e.what()); return;
304-
} catch (const std::range_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return;
305-
} catch (const std::overflow_error &e) { PyErr_SetString(PyExc_OverflowError, e.what()); return;
306-
} catch (const std::exception &e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return;
343+
std::rethrow_exception(p);
344+
} catch (error_already_set &e) {
345+
handle_nested_exception(e, p);
346+
e.restore();
347+
return;
348+
} catch (const builtin_exception &e) {
349+
// Could not use template since it's an abstract class.
350+
if (auto *nep = dynamic_cast<const std::nested_exception *>(std::addressof(e))) {
351+
handle_nested_exception(*nep, p);
352+
}
353+
e.set_error();
354+
return;
355+
} catch (const std::bad_alloc &e) {
356+
handle_nested_exception(e, p);
357+
raise_err(PyExc_MemoryError, e.what());
358+
return;
359+
} catch (const std::domain_error &e) {
360+
handle_nested_exception(e, p);
361+
raise_err(PyExc_ValueError, e.what());
362+
return;
363+
} catch (const std::invalid_argument &e) {
364+
handle_nested_exception(e, p);
365+
raise_err(PyExc_ValueError, e.what());
366+
return;
367+
} catch (const std::length_error &e) {
368+
handle_nested_exception(e, p);
369+
raise_err(PyExc_ValueError, e.what());
370+
return;
371+
} catch (const std::out_of_range &e) {
372+
handle_nested_exception(e, p);
373+
raise_err(PyExc_IndexError, e.what());
374+
return;
375+
} catch (const std::range_error &e) {
376+
handle_nested_exception(e, p);
377+
raise_err(PyExc_ValueError, e.what());
378+
return;
379+
} catch (const std::overflow_error &e) {
380+
handle_nested_exception(e, p);
381+
raise_err(PyExc_OverflowError, e.what());
382+
return;
383+
} catch (const std::exception &e) {
384+
handle_nested_exception(e, p);
385+
raise_err(PyExc_RuntimeError, e.what());
386+
return;
387+
} catch (const std::nested_exception &e) {
388+
handle_nested_exception(e, p);
389+
raise_err(PyExc_RuntimeError, "Caught an unknown nested exception!");
390+
return;
307391
} catch (...) {
308-
PyErr_SetString(PyExc_RuntimeError, "Caught an unknown exception!");
392+
raise_err(PyExc_RuntimeError, "Caught an unknown exception!");
309393
return;
310394
}
311395
}

tests/test_exceptions.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "local_bindings.h"
1212

1313
#include "pybind11_tests.h"
14+
#include <exception>
15+
#include <stdexcept>
1416
#include <utility>
1517

1618
// A type that should be raised as an exception in Python
@@ -105,7 +107,6 @@ struct PythonAlreadySetInDestructor {
105107
py::str s;
106108
};
107109

108-
109110
TEST_SUBMODULE(exceptions, m) {
110111
m.def("throw_std_exception", []() {
111112
throw std::runtime_error("This exception was intentionally thrown.");
@@ -281,5 +282,12 @@ TEST_SUBMODULE(exceptions, m) {
281282
}
282283
});
283284

285+
m.def("throw_nested_exception", []() {
286+
try {
287+
throw std::runtime_error("Inner Exception");
288+
} catch (const std::runtime_error &) {
289+
std::throw_with_nested(std::runtime_error("Outer Exception"));
290+
}
291+
});
284292
#endif
285293
}

tests/test_exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,14 @@ def pycatch(exctype, f, *args):
239239
assert str(excinfo.value) == "this is a helper-defined translated exception"
240240

241241

242+
@pytest.mark.skipif("env.PY2")
243+
def test_throw_nested_exception():
244+
with pytest.raises(RuntimeError) as excinfo:
245+
m.throw_nested_exception()
246+
assert str(excinfo.value) == "Outer Exception"
247+
assert str(excinfo.value.__cause__) == "Inner Exception"
248+
249+
242250
# This can often happen if you wrap a pybind11 class in a Python wrapper
243251
def test_invalid_repr():
244252
class MyRepr(object):

0 commit comments

Comments
 (0)