Always display python type information in cast errors (#4463)

* Always display python type information in cast errors

* Address comments

* Update comment
This commit is contained in:
Dustin Spicuzza 2023-02-10 00:21:17 -05:00 committed by GitHub
parent 531144dddc
commit 8dcced29ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 49 additions and 19 deletions

View File

@ -1017,11 +1017,14 @@ type_caster<T, SFINAE> &load_type(type_caster<T, SFINAE> &conv, const handle &ha
"Internal error: type_caster should only be used for C++ types"); "Internal error: type_caster should only be used for C++ types");
if (!conv.load(handle, true)) { if (!conv.load(handle, true)) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error("Unable to cast Python instance to C++ type (#define " throw cast_error(
"Unable to cast Python instance of type "
+ str(type::handle_of(handle)).cast<std::string>()
+ " to C++ type '?' (#define "
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); "PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
#else #else
throw cast_error("Unable to cast Python instance of type " throw cast_error("Unable to cast Python instance of type "
+ (std::string) str(type::handle_of(handle)) + " to C++ type '" + str(type::handle_of(handle)).cast<std::string>() + " to C++ type '"
+ type_id<T>() + "'"); + type_id<T>() + "'");
#endif #endif
} }
@ -1085,12 +1088,13 @@ detail::enable_if_t<!detail::move_never<T>::value, T> move(object &&obj) {
if (obj.ref_count() > 1) { if (obj.ref_count() > 1) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error( throw cast_error(
"Unable to cast Python instance to C++ rvalue: instance has multiple references" "Unable to cast Python " + str(type::handle_of(obj)).cast<std::string>()
+ " instance to C++ rvalue: instance has multiple references"
" (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); " (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
#else #else
throw cast_error("Unable to move from Python " + (std::string) str(type::handle_of(obj)) throw cast_error("Unable to move from Python "
+ " instance to C++ " + type_id<T>() + str(type::handle_of(obj)).cast<std::string>() + " instance to C++ "
+ " instance: instance has multiple references"); + type_id<T>() + " instance: instance has multiple references");
#endif #endif
} }
@ -1195,8 +1199,9 @@ PYBIND11_NAMESPACE_END(detail)
// The overloads could coexist, i.e. the #if is not strictly speaking needed, // The overloads could coexist, i.e. the #if is not strictly speaking needed,
// but it is an easy minor optimization. // but it is an easy minor optimization.
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
inline cast_error cast_error_unable_to_convert_call_arg() { inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name) {
return cast_error("Unable to convert call argument to Python object (#define " return cast_error("Unable to convert call argument '" + name
+ "' to Python object (#define "
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); "PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
} }
#else #else
@ -1220,7 +1225,7 @@ tuple make_tuple(Args &&...args_) {
for (size_t i = 0; i < args.size(); i++) { for (size_t i = 0; i < args.size(); i++) {
if (!args[i]) { if (!args[i]) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error_unable_to_convert_call_arg(); throw cast_error_unable_to_convert_call_arg(std::to_string(i));
#else #else
std::array<std::string, size> argtypes{{type_id<Args>()...}}; std::array<std::string, size> argtypes{{type_id<Args>()...}};
throw cast_error_unable_to_convert_call_arg(std::to_string(i), argtypes[i]); throw cast_error_unable_to_convert_call_arg(std::to_string(i), argtypes[i]);
@ -1510,7 +1515,7 @@ private:
detail::make_caster<T>::cast(std::forward<T>(x), policy, {})); detail::make_caster<T>::cast(std::forward<T>(x), policy, {}));
if (!o) { if (!o) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error_unable_to_convert_call_arg(); throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()));
#else #else
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()), throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()),
type_id<T>()); type_id<T>());
@ -1542,7 +1547,7 @@ private:
} }
if (!a.value) { if (!a.value) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error_unable_to_convert_call_arg(); throw cast_error_unable_to_convert_call_arg(a.name);
#else #else
throw cast_error_unable_to_convert_call_arg(a.name, a.type); throw cast_error_unable_to_convert_call_arg(a.name, a.type);
#endif #endif

View File

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

View File

@ -5,6 +5,7 @@ import pytest
import env # noqa: F401 import env # noqa: F401
from pybind11_tests import callbacks as m from pybind11_tests import callbacks as m
from pybind11_tests import detailed_error_messages_enabled
def test_callbacks(): def test_callbacks():
@ -70,11 +71,20 @@ def test_keyword_args_and_generalized_unpacking():
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
m.test_arg_conversion_error1(f) m.test_arg_conversion_error1(f)
assert "Unable to convert call argument" in str(excinfo.value) assert str(excinfo.value) == "Unable to convert call argument " + (
"'1' of type 'UnregisteredType' to Python object"
if detailed_error_messages_enabled
else "'1' to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
)
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
m.test_arg_conversion_error2(f) m.test_arg_conversion_error2(f)
assert "Unable to convert call argument" in str(excinfo.value) assert str(excinfo.value) == "Unable to convert call argument " + (
"'expected_name' of type 'UnregisteredType' to Python object"
if detailed_error_messages_enabled
else "'expected_name' to Python object "
"(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
)
def test_lambda_closure_cleanup(): def test_lambda_closure_cleanup():

View File

@ -339,4 +339,9 @@ TEST_SUBMODULE(exceptions, m) {
} }
return py::str("UNEXPECTED"); return py::str("UNEXPECTED");
}); });
m.def("test_fn_cast_int", [](const py::function &fn) {
// function returns None instead of int, should give a useful error message
fn().cast<int>();
});
} }

View File

@ -381,3 +381,12 @@ def test_pypy_oserror_normalization():
# https://github.com/pybind/pybind11/issues/4075 # https://github.com/pybind/pybind11/issues/4075
what = m.test_pypy_oserror_normalization() what = m.test_pypy_oserror_normalization()
assert "this_filename_must_not_exist" in what assert "this_filename_must_not_exist" in what
def test_fn_cast_int_exception():
with pytest.raises(RuntimeError) as excinfo:
m.test_fn_cast_int(lambda: None)
assert str(excinfo.value).startswith(
"Unable to cast Python instance of type <class 'NoneType'> to C++ type"
)

View File

@ -536,7 +536,7 @@ def test_print(capture):
assert str(excinfo.value) == "Unable to convert call argument " + ( assert str(excinfo.value) == "Unable to convert call argument " + (
"'1' of type 'UnregisteredType' to Python object" "'1' of type 'UnregisteredType' to Python object"
if detailed_error_messages_enabled if detailed_error_messages_enabled
else "to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)" else "'1' to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
) )