Skip to content

Commit 8dcced2

Browse files
authored
Always display python type information in cast errors (pybind#4463)
* Always display python type information in cast errors * Address comments * Update comment
1 parent 531144d commit 8dcced2

File tree

6 files changed

+49
-19
lines changed

6 files changed

+49
-19
lines changed

Diff for: include/pybind11/cast.h

+19-14
Original file line numberDiff line numberDiff line change
@@ -1017,11 +1017,14 @@ 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 "
1022+
+ str(type::handle_of(handle)).cast<std::string>()
1023+
+ " to C++ type '?' (#define "
1024+
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
10221025
#else
10231026
throw cast_error("Unable to cast Python instance of type "
1024-
+ (std::string) str(type::handle_of(handle)) + " to C++ type '"
1027+
+ str(type::handle_of(handle)).cast<std::string>() + " to C++ type '"
10251028
+ type_id<T>() + "'");
10261029
#endif
10271030
}
@@ -1085,12 +1088,13 @@ detail::enable_if_t<!detail::move_never<T>::value, T> move(object &&obj) {
10851088
if (obj.ref_count() > 1) {
10861089
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
10871090
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)");
1091+
"Unable to cast Python " + str(type::handle_of(obj)).cast<std::string>()
1092+
+ " instance to C++ rvalue: instance has multiple references"
1093+
" (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
10901094
#else
1091-
throw cast_error("Unable to move from Python " + (std::string) str(type::handle_of(obj))
1092-
+ " instance to C++ " + type_id<T>()
1093-
+ " instance: instance has multiple references");
1095+
throw cast_error("Unable to move from Python "
1096+
+ str(type::handle_of(obj)).cast<std::string>() + " instance to C++ "
1097+
+ type_id<T>() + " instance: instance has multiple references");
10941098
#endif
10951099
}
10961100

@@ -1195,9 +1199,10 @@ PYBIND11_NAMESPACE_END(detail)
11951199
// The overloads could coexist, i.e. the #if is not strictly speaking needed,
11961200
// but it is an easy minor optimization.
11971201
#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)");
1202+
inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name) {
1203+
return cast_error("Unable to convert call argument '" + name
1204+
+ "' to Python object (#define "
1205+
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
12011206
}
12021207
#else
12031208
inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name,
@@ -1220,7 +1225,7 @@ tuple make_tuple(Args &&...args_) {
12201225
for (size_t i = 0; i < args.size(); i++) {
12211226
if (!args[i]) {
12221227
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
1223-
throw cast_error_unable_to_convert_call_arg();
1228+
throw cast_error_unable_to_convert_call_arg(std::to_string(i));
12241229
#else
12251230
std::array<std::string, size> argtypes{{type_id<Args>()...}};
12261231
throw cast_error_unable_to_convert_call_arg(std::to_string(i), argtypes[i]);
@@ -1510,7 +1515,7 @@ class unpacking_collector {
15101515
detail::make_caster<T>::cast(std::forward<T>(x), policy, {}));
15111516
if (!o) {
15121517
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
1513-
throw cast_error_unable_to_convert_call_arg();
1518+
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()));
15141519
#else
15151520
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()),
15161521
type_id<T>());
@@ -1542,7 +1547,7 @@ class unpacking_collector {
15421547
}
15431548
if (!a.value) {
15441549
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
1545-
throw cast_error_unable_to_convert_call_arg();
1550+
throw cast_error_unable_to_convert_call_arg(a.name);
15461551
#else
15471552
throw cast_error_unable_to_convert_call_arg(a.name, a.type);
15481553
#endif

Diff for: include/pybind11/detail/common.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -1225,8 +1225,9 @@ constexpr
12251225
#endif
12261226

12271227
// Pybind offers detailed error messages by default for all builts that are debug (through the
1228-
// negation of ndebug). This can also be manually enabled by users, for any builds, through
1229-
// defining PYBIND11_DETAILED_ERROR_MESSAGES.
1228+
// negation of NDEBUG). This can also be manually enabled by users, for any builds, through
1229+
// defining PYBIND11_DETAILED_ERROR_MESSAGES. This information is primarily useful for those
1230+
// who are writing (as opposed to merely using) libraries that use pybind11.
12301231
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) && !defined(NDEBUG)
12311232
# define PYBIND11_DETAILED_ERROR_MESSAGES
12321233
#endif

Diff for: tests/test_callbacks.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import env # noqa: F401
77
from pybind11_tests import callbacks as m
8+
from pybind11_tests import detailed_error_messages_enabled
89

910

1011
def test_callbacks():
@@ -70,11 +71,20 @@ def f(*args, **kwargs):
7071

7172
with pytest.raises(RuntimeError) as excinfo:
7273
m.test_arg_conversion_error1(f)
73-
assert "Unable to convert call argument" in str(excinfo.value)
74+
assert str(excinfo.value) == "Unable to convert call argument " + (
75+
"'1' of type 'UnregisteredType' to Python object"
76+
if detailed_error_messages_enabled
77+
else "'1' to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
78+
)
7479

7580
with pytest.raises(RuntimeError) as excinfo:
7681
m.test_arg_conversion_error2(f)
77-
assert "Unable to convert call argument" in str(excinfo.value)
82+
assert str(excinfo.value) == "Unable to convert call argument " + (
83+
"'expected_name' of type 'UnregisteredType' to Python object"
84+
if detailed_error_messages_enabled
85+
else "'expected_name' to Python object "
86+
"(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
87+
)
7888

7989

8090
def test_lambda_closure_cleanup():

Diff for: tests/test_exceptions.cpp

+5
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_fn_cast_int", [](const py::function &fn) {
344+
// function returns None instead of int, should give a useful error message
345+
fn().cast<int>();
346+
});
342347
}

Diff for: tests/test_exceptions.py

+9
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,12 @@ 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_fn_cast_int_exception():
387+
with pytest.raises(RuntimeError) as excinfo:
388+
m.test_fn_cast_int(lambda: None)
389+
390+
assert str(excinfo.value).startswith(
391+
"Unable to cast Python instance of type <class 'NoneType'> to C++ type"
392+
)

Diff for: tests/test_pytypes.py

+1-1
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)