Skip to content

Commit 6c0f03b

Browse files
committed
Fail on passing py::object with wrong Python type to py::object subclass using PYBIND11_OBJECT macro
1 parent 1d553e4 commit 6c0f03b

File tree

3 files changed

+32
-2
lines changed

3 files changed

+32
-2
lines changed

include/pybind11/pytypes.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -794,11 +794,16 @@ PYBIND11_NAMESPACE_END(detail)
794794
: Parent(check_(o) ? o.release().ptr() : ConvertFun(o.ptr()), stolen_t{}) \
795795
{ if (!m_ptr) throw error_already_set(); }
796796

797+
#define PYBIND11_OBJECT_CHECK_FAILED(Name, o) \
798+
type_error("Object of type '" + std::string(Py_TYPE(o.ptr())->tp_name) + "' is not an instance of '" #Name "'")
799+
797800
#define PYBIND11_OBJECT(Name, Parent, CheckFun) \
798801
PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \
799802
/* This is deliberately not 'explicit' to allow implicit conversion from object: */ \
800-
Name(const object &o) : Parent(o) { } \
801-
Name(object &&o) : Parent(std::move(o)) { }
803+
Name(const object &o) : Parent(o) \
804+
{ if (o && !check_(o)) throw PYBIND11_OBJECT_CHECK_FAILED(Name, o); } \
805+
Name(object &&o) : Parent(std::move(o)) \
806+
{ if (o && !check_(o)) throw PYBIND11_OBJECT_CHECK_FAILED(Name, o); }
802807

803808
#define PYBIND11_OBJECT_DEFAULT(Name, Parent, CheckFun) \
804809
PYBIND11_OBJECT(Name, Parent, CheckFun) \

tests/test_pytypes.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,19 @@ TEST_SUBMODULE(pytypes, m) {
242242

243243
m.def("convert_to_pybind11_str", [](py::object o) { return py::str(o); });
244244

245+
m.def("nonconverting_constructor", [](std::string type, py::object value) -> py::object {
246+
if (type == "bytes") {
247+
return py::bytes(value);
248+
}
249+
else if (type == "none") {
250+
return py::none(value);
251+
}
252+
else if (type == "ellipsis") {
253+
return py::ellipsis(value);
254+
}
255+
throw std::runtime_error("Invalid type");
256+
});
257+
245258
m.def("get_implicit_casting", []() {
246259
py::dict d;
247260
d["char*_i1"] = "abc";

tests/test_pytypes.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,18 @@ def test_constructors():
230230
for k in noconv2:
231231
assert noconv2[k] is expected[k]
232232

233+
type_error_tests = [
234+
("bytes", range(10)),
235+
("none", 42),
236+
("ellipsis", 42),
237+
]
238+
for t, v in type_error_tests:
239+
with pytest.raises(TypeError) as excinfo:
240+
m.nonconverting_constructor(t, v)
241+
expected_error = "Object of type '{}' is not an instance of '{}'".format(
242+
type(v).__name__, t)
243+
assert str(excinfo.value) == expected_error
244+
233245

234246
def test_pybind11_str_raw_str():
235247
# specifically to exercise pybind11::str::raw_str

0 commit comments

Comments
 (0)