mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-11 08:03:55 +00:00
Make TypeErrors more informative when an optional header is missing
E.g. trying to convert a `list` to a `std::vector<int>` without including <pybind11/stl.h> will now raise an error with a note that suggests checking the headers. The note is only appended if `std::` is found in the function signature. This should only be the case when a header is missing. E.g. when stl.h is included, the signature would contain `List[int]` instead of `std::vector<int>` while using stl_bind.h would produce something like `MyVector`. Similarly for `std::map`/`Dict`, `complex`, `std::function`/`Callable`, etc. There's a possibility for false positives, but it's pretty low.
This commit is contained in:
parent
c64e6b1670
commit
2b4477eb65
@ -35,6 +35,10 @@ v2.2.1 (Not yet released)
|
|||||||
that's only registered in an external module.
|
that's only registered in an external module.
|
||||||
`#1058 <https://github.com/pybind/pybind11/pull/1058>`_.
|
`#1058 <https://github.com/pybind/pybind11/pull/1058>`_.
|
||||||
|
|
||||||
|
* Conversion errors now try to be more informative when it's likely that
|
||||||
|
a missing header is the cause (e.g. forgetting ``<pybind11/stl.h>``).
|
||||||
|
`#1077 <https://github.com/pybind/pybind11/pull/1077>`_.
|
||||||
|
|
||||||
v2.2.0 (August 31, 2017)
|
v2.2.0 (August 31, 2017)
|
||||||
-----------------------------------------------------
|
-----------------------------------------------------
|
||||||
|
|
||||||
|
@ -694,6 +694,16 @@ protected:
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto append_note_if_missing_header_is_suspected = [](std::string &msg) {
|
||||||
|
if (msg.find("std::") != std::string::npos) {
|
||||||
|
msg += "\n\n"
|
||||||
|
"Did you forget to `#include <pybind11/stl.h>`? Or <pybind11/complex.h>,\n"
|
||||||
|
"<pybind11/functional.h>, <pybind11/chrono.h>, etc. Some automatic\n"
|
||||||
|
"conversions are optional and require extra headers to be included\n"
|
||||||
|
"when compiling your pybind11 module.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (result.ptr() == PYBIND11_TRY_NEXT_OVERLOAD) {
|
if (result.ptr() == PYBIND11_TRY_NEXT_OVERLOAD) {
|
||||||
if (overloads->is_operator)
|
if (overloads->is_operator)
|
||||||
return handle(Py_NotImplemented).inc_ref().ptr();
|
return handle(Py_NotImplemented).inc_ref().ptr();
|
||||||
@ -751,12 +761,14 @@ protected:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
append_note_if_missing_header_is_suspected(msg);
|
||||||
PyErr_SetString(PyExc_TypeError, msg.c_str());
|
PyErr_SetString(PyExc_TypeError, msg.c_str());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
} else if (!result) {
|
} else if (!result) {
|
||||||
std::string msg = "Unable to convert function return value to a "
|
std::string msg = "Unable to convert function return value to a "
|
||||||
"Python type! The signature was\n\t";
|
"Python type! The signature was\n\t";
|
||||||
msg += it->signature;
|
msg += it->signature;
|
||||||
|
append_note_if_missing_header_is_suspected(msg);
|
||||||
PyErr_SetString(PyExc_TypeError, msg.c_str());
|
PyErr_SetString(PyExc_TypeError, msg.c_str());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
} else {
|
} else {
|
||||||
|
@ -76,6 +76,7 @@ string(REPLACE ".cpp" ".py" PYBIND11_PYTEST_FILES "${PYBIND11_TEST_FILES}")
|
|||||||
set(PYBIND11_CROSS_MODULE_TESTS
|
set(PYBIND11_CROSS_MODULE_TESTS
|
||||||
test_exceptions.py
|
test_exceptions.py
|
||||||
test_local_bindings.py
|
test_local_bindings.py
|
||||||
|
test_stl.py
|
||||||
test_stl_binders.py
|
test_stl_binders.py
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -114,4 +114,10 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
|
|||||||
// the same module (it would be an ODR violation). Therefore `bind_vector` of `bool`
|
// the same module (it would be an ODR violation). Therefore `bind_vector` of `bool`
|
||||||
// is defined here and tested in `test_stl_binders.py`.
|
// is defined here and tested in `test_stl_binders.py`.
|
||||||
py::bind_vector<std::vector<bool>>(m, "VectorBool");
|
py::bind_vector<std::vector<bool>>(m, "VectorBool");
|
||||||
|
|
||||||
|
// test_missing_header_message
|
||||||
|
// The main module already includes stl.h, but we need to test the error message
|
||||||
|
// which appears when this header is missing.
|
||||||
|
m.def("missing_header_arg", [](std::vector<float>) { });
|
||||||
|
m.def("missing_header_return", []() { return std::vector<float>(); });
|
||||||
}
|
}
|
||||||
|
@ -179,3 +179,22 @@ def test_stl_pass_by_pointer(msg):
|
|||||||
""" # noqa: E501 line too long
|
""" # noqa: E501 line too long
|
||||||
|
|
||||||
assert m.stl_pass_by_pointer([1, 2, 3]) == [1, 2, 3]
|
assert m.stl_pass_by_pointer([1, 2, 3]) == [1, 2, 3]
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_header_message():
|
||||||
|
"""Trying convert `list` to a `std::vector`, or vice versa, without including
|
||||||
|
<pybind11/stl.h> should result in a helpful suggestion in the error message"""
|
||||||
|
import pybind11_cross_module_tests as cm
|
||||||
|
|
||||||
|
expected_message = ("Did you forget to `#include <pybind11/stl.h>`? Or <pybind11/complex.h>,\n"
|
||||||
|
"<pybind11/functional.h>, <pybind11/chrono.h>, etc. Some automatic\n"
|
||||||
|
"conversions are optional and require extra headers to be included\n"
|
||||||
|
"when compiling your pybind11 module.")
|
||||||
|
|
||||||
|
with pytest.raises(TypeError) as excinfo:
|
||||||
|
cm.missing_header_arg([1.0, 2.0, 3.0])
|
||||||
|
assert expected_message in str(excinfo.value)
|
||||||
|
|
||||||
|
with pytest.raises(TypeError) as excinfo:
|
||||||
|
cm.missing_header_return()
|
||||||
|
assert expected_message in str(excinfo.value)
|
||||||
|
Loading…
Reference in New Issue
Block a user