Skip to content

Commit 2e66b40

Browse files
committed
Fail on passing py::object with wrong Python type to py::object subclass using PYBIND11_OBJECT macro
1 parent d8a1dd0 commit 2e66b40

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
@@ -798,11 +798,16 @@ PYBIND11_NAMESPACE_END(detail)
798798
: Parent(check_(o) ? o.release().ptr() : ConvertFun(o.ptr()), stolen_t{}) \
799799
{ if (!m_ptr) throw error_already_set(); }
800800

801+
#define PYBIND11_OBJECT_CHECK_FAILED(Name, o) \
802+
type_error("Object of type '" + std::string(Py_TYPE(o.ptr())->tp_name) + "' is not an instance of '" #Name "'")
803+
801804
#define PYBIND11_OBJECT(Name, Parent, CheckFun) \
802805
PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \
803806
/* This is deliberately not 'explicit' to allow implicit conversion from object: */ \
804-
Name(const object &o) : Parent(o) { } \
805-
Name(object &&o) : Parent(std::move(o)) { }
807+
Name(const object &o) : Parent(o) \
808+
{ if (o && !check_(o)) throw PYBIND11_OBJECT_CHECK_FAILED(Name, o); } \
809+
Name(object &&o) : Parent(std::move(o)) \
810+
{ if (o && !check_(o)) throw PYBIND11_OBJECT_CHECK_FAILED(Name, o); }
806811

807812
#define PYBIND11_OBJECT_DEFAULT(Name, Parent, CheckFun) \
808813
PYBIND11_OBJECT(Name, Parent, CheckFun) \

tests/test_pytypes.cpp

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

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

tests/test_pytypes.py

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

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

229241
def test_implicit_casting():
230242
"""Tests implicit casting when assigning or appending to dicts and lists."""

0 commit comments

Comments
 (0)