Skip to content

Commit 2b4477e

Browse files
committed
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.
1 parent c64e6b1 commit 2b4477e

File tree

5 files changed

+42
-0
lines changed

5 files changed

+42
-0
lines changed

docs/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ v2.2.1 (Not yet released)
3535
that's only registered in an external module.
3636
`#1058 <https://github.com/pybind/pybind11/pull/1058>`_.
3737

38+
* Conversion errors now try to be more informative when it's likely that
39+
a missing header is the cause (e.g. forgetting ``<pybind11/stl.h>``).
40+
`#1077 <https://github.com/pybind/pybind11/pull/1077>`_.
41+
3842
v2.2.0 (August 31, 2017)
3943
-----------------------------------------------------
4044

include/pybind11/pybind11.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,16 @@ class cpp_function : public function {
694694
return nullptr;
695695
}
696696

697+
auto append_note_if_missing_header_is_suspected = [](std::string &msg) {
698+
if (msg.find("std::") != std::string::npos) {
699+
msg += "\n\n"
700+
"Did you forget to `#include <pybind11/stl.h>`? Or <pybind11/complex.h>,\n"
701+
"<pybind11/functional.h>, <pybind11/chrono.h>, etc. Some automatic\n"
702+
"conversions are optional and require extra headers to be included\n"
703+
"when compiling your pybind11 module.";
704+
}
705+
};
706+
697707
if (result.ptr() == PYBIND11_TRY_NEXT_OVERLOAD) {
698708
if (overloads->is_operator)
699709
return handle(Py_NotImplemented).inc_ref().ptr();
@@ -751,12 +761,14 @@ class cpp_function : public function {
751761
}
752762
}
753763

764+
append_note_if_missing_header_is_suspected(msg);
754765
PyErr_SetString(PyExc_TypeError, msg.c_str());
755766
return nullptr;
756767
} else if (!result) {
757768
std::string msg = "Unable to convert function return value to a "
758769
"Python type! The signature was\n\t";
759770
msg += it->signature;
771+
append_note_if_missing_header_is_suspected(msg);
760772
PyErr_SetString(PyExc_TypeError, msg.c_str());
761773
return nullptr;
762774
} else {

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ string(REPLACE ".cpp" ".py" PYBIND11_PYTEST_FILES "${PYBIND11_TEST_FILES}")
7676
set(PYBIND11_CROSS_MODULE_TESTS
7777
test_exceptions.py
7878
test_local_bindings.py
79+
test_stl.py
7980
test_stl_binders.py
8081
)
8182

tests/pybind11_cross_module_tests.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,10 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
114114
// the same module (it would be an ODR violation). Therefore `bind_vector` of `bool`
115115
// is defined here and tested in `test_stl_binders.py`.
116116
py::bind_vector<std::vector<bool>>(m, "VectorBool");
117+
118+
// test_missing_header_message
119+
// The main module already includes stl.h, but we need to test the error message
120+
// which appears when this header is missing.
121+
m.def("missing_header_arg", [](std::vector<float>) { });
122+
m.def("missing_header_return", []() { return std::vector<float>(); });
117123
}

tests/test_stl.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,22 @@ def test_stl_pass_by_pointer(msg):
179179
""" # noqa: E501 line too long
180180

181181
assert m.stl_pass_by_pointer([1, 2, 3]) == [1, 2, 3]
182+
183+
184+
def test_missing_header_message():
185+
"""Trying convert `list` to a `std::vector`, or vice versa, without including
186+
<pybind11/stl.h> should result in a helpful suggestion in the error message"""
187+
import pybind11_cross_module_tests as cm
188+
189+
expected_message = ("Did you forget to `#include <pybind11/stl.h>`? Or <pybind11/complex.h>,\n"
190+
"<pybind11/functional.h>, <pybind11/chrono.h>, etc. Some automatic\n"
191+
"conversions are optional and require extra headers to be included\n"
192+
"when compiling your pybind11 module.")
193+
194+
with pytest.raises(TypeError) as excinfo:
195+
cm.missing_header_arg([1.0, 2.0, 3.0])
196+
assert expected_message in str(excinfo.value)
197+
198+
with pytest.raises(TypeError) as excinfo:
199+
cm.missing_header_return()
200+
assert expected_message in str(excinfo.value)

0 commit comments

Comments
 (0)