Skip to content

Commit aef70cb

Browse files
committed
Always display python type information in cast errors
1 parent a500f43 commit aef70cb

File tree

5 files changed

+35
-11
lines changed

5 files changed

+35
-11
lines changed

include/pybind11/cast.h

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,8 +1017,10 @@ type_caster<T, SFINAE> &load_type(type_caster<T, SFINAE> &conv, const handle &ha
10171017
"Internal error: type_caster should only be used for C++ types");
10181018
if (!conv.load(handle, true)) {
10191019
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
1020-
throw cast_error("Unable to cast Python instance to C++ type (#define "
1021-
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
1020+
throw cast_error(
1021+
"Unable to cast Python instance of type " + static_cast<std::string>(str(type::handle_of(handle)))
1022+
+ " to C++ type '?' (#define "
1023+
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
10221024
#else
10231025
throw cast_error("Unable to cast Python instance of type "
10241026
+ (std::string) str(type::handle_of(handle)) + " to C++ type '"
@@ -1085,8 +1087,9 @@ detail::enable_if_t<!detail::move_never<T>::value, T> move(object &&obj) {
10851087
if (obj.ref_count() > 1) {
10861088
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
10871089
throw cast_error(
1088-
"Unable to cast Python instance to C++ rvalue: instance has multiple references"
1089-
" (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
1090+
"Unable to cast Python " + (std::string) str(type::handle_of(obj))
1091+
+ " instance to C++ rvalue: instance has multiple references"
1092+
" (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
10901093
#else
10911094
throw cast_error("Unable to move from Python " + (std::string) str(type::handle_of(obj))
10921095
+ " instance to C++ " + type_id<T>()
@@ -1195,9 +1198,10 @@ PYBIND11_NAMESPACE_END(detail)
11951198
// The overloads could coexist, i.e. the #if is not strictly speaking needed,
11961199
// but it is an easy minor optimization.
11971200
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
1198-
inline cast_error cast_error_unable_to_convert_call_arg() {
1199-
return cast_error("Unable to convert call argument to Python object (#define "
1200-
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
1201+
inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name) {
1202+
return cast_error("Unable to convert call argument '" + name
1203+
+ "' to Python object (#define "
1204+
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
12011205
}
12021206
#else
12031207
inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name,
@@ -1220,7 +1224,7 @@ tuple make_tuple(Args &&...args_) {
12201224
for (size_t i = 0; i < args.size(); i++) {
12211225
if (!args[i]) {
12221226
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
1223-
throw cast_error_unable_to_convert_call_arg();
1227+
throw cast_error_unable_to_convert_call_arg(std::to_string(i));
12241228
#else
12251229
std::array<std::string, size> argtypes{{type_id<Args>()...}};
12261230
throw cast_error_unable_to_convert_call_arg(std::to_string(i), argtypes[i]);
@@ -1510,7 +1514,7 @@ class unpacking_collector {
15101514
detail::make_caster<T>::cast(std::forward<T>(x), policy, {}));
15111515
if (!o) {
15121516
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
1513-
throw cast_error_unable_to_convert_call_arg();
1517+
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()));
15141518
#else
15151519
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()),
15161520
type_id<T>());
@@ -1542,7 +1546,7 @@ class unpacking_collector {
15421546
}
15431547
if (!a.value) {
15441548
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
1545-
throw cast_error_unable_to_convert_call_arg();
1549+
throw cast_error_unable_to_convert_call_arg(a.name);
15461550
#else
15471551
throw cast_error_unable_to_convert_call_arg(a.name, a.type);
15481552
#endif

include/pybind11/detail/common.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,11 @@ constexpr
12271227
// Pybind offers detailed error messages by default for all builts that are debug (through the
12281228
// negation of ndebug). This can also be manually enabled by users, for any builds, through
12291229
// defining PYBIND11_DETAILED_ERROR_MESSAGES.
1230+
//
1231+
// Pybind attempts to provide useful error messages by default. Specifically, this macro will
1232+
// enable two categories of behavior:
1233+
// - Embed C++ type information which isn't going to be available in a release build
1234+
// - Aid those who are writing (as opposed to merely using) libraries that use pybind11
12301235
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) && !defined(NDEBUG)
12311236
# define PYBIND11_DETAILED_ERROR_MESSAGES
12321237
#endif

tests/test_exceptions.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,4 +339,9 @@ TEST_SUBMODULE(exceptions, m) {
339339
}
340340
return py::str("UNEXPECTED");
341341
});
342+
343+
m.def("test_hidden_error", [](const py::function &fn) {
344+
// function returns none instead of int, should give a useful error message
345+
fn().cast<int>();
346+
});
342347
}

tests/test_exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,13 @@ def test_pypy_oserror_normalization():
381381
# https://github.com/pybind/pybind11/issues/4075
382382
what = m.test_pypy_oserror_normalization()
383383
assert "this_filename_must_not_exist" in what
384+
385+
386+
def test_sane_hidden_exception():
387+
with pytest.raises(RuntimeError) as excinfo:
388+
m.test_hidden_error(lambda: None)
389+
390+
# Always display the python type even if the C++ type isn't available
391+
assert str(excinfo.value).startswith(
392+
"Unable to cast Python instance of type <class 'NoneType'> to C++ type"
393+
)

tests/test_pytypes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ def test_print(capture):
536536
assert str(excinfo.value) == "Unable to convert call argument " + (
537537
"'1' of type 'UnregisteredType' to Python object"
538538
if detailed_error_messages_enabled
539-
else "to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
539+
else "'1' to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
540540
)
541541

542542

0 commit comments

Comments
 (0)