Skip to content

Commit d708836

Browse files
committed
Add is_redundant_value_and_holder() and use to avoid forcing __init__ overrides when they are not needed.
1 parent 7c7d78d commit d708836

File tree

3 files changed

+26
-14
lines changed

3 files changed

+26
-14
lines changed

include/pybind11/detail/class.h

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,17 @@ extern "C" inline PyObject *pybind11_meta_getattro(PyObject *obj, PyObject *name
180180
return PyType_Type.tp_getattro(obj, name);
181181
}
182182

183+
// Band-aid workaround to fix a subtle but serious bug in a minimalistic fashion. See PR #4762.
184+
inline bool is_redundant_value_and_holder(const std::vector<type_info *> &bases,
185+
std::size_t ix_base) {
186+
for (std::size_t i = 0; i < ix_base; i++) {
187+
if (PyType_IsSubtype(bases[i]->type, bases[ix_base]->type) != 0) {
188+
return true;
189+
}
190+
}
191+
return false;
192+
}
193+
183194
/// metaclass `__call__` function that is used to create all pybind11 objects.
184195
extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, PyObject *kwargs) {
185196

@@ -189,18 +200,20 @@ extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, P
189200
return nullptr;
190201
}
191202

192-
// This must be a pybind11 instance
193-
auto *instance = reinterpret_cast<detail::instance *>(self);
194-
195203
// Ensure that the base __init__ function(s) were called
196-
for (const auto &vh : values_and_holders(instance)) {
197-
if (!vh.holder_constructed()) {
204+
const auto &bases = all_type_info((PyTypeObject *) type);
205+
values_and_holders vhs(reinterpret_cast<detail::instance *>(self));
206+
assert(bases.size() == vhs.size());
207+
std::size_t ix_base = 0;
208+
for (const auto &vh : vhs) {
209+
if (!vh.holder_constructed() && !is_redundant_value_and_holder(bases, ix_base)) {
198210
PyErr_Format(PyExc_TypeError,
199211
"%.200s.__init__() must be called when overriding __init__",
200212
get_fully_qualified_tp_name(vh.type->type).c_str());
201213
Py_DECREF(self);
202214
return nullptr;
203215
}
216+
ix_base++;
204217
}
205218

206219
return self;

include/pybind11/detail/type_caster_base.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class loader_life_support {
102102
inline std::pair<decltype(internals::registered_types_py)::iterator, bool>
103103
all_type_info_get_cache(PyTypeObject *type);
104104

105+
// Band-aid workaround to fix a subtle but serious bug in a minimalistic fashion. See PR #4762.
105106
inline void all_type_info_add_base_most_derived_first(std::vector<type_info *> &bases,
106107
type_info *addl_base) {
107108
for (auto it = bases.begin(); it != bases.end(); it++) {

tests/test_python_multiple_inheritance.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ class PC(m.CppBase):
88
pass
99

1010

11-
class PPCCInit(PC, m.CppDrvd):
12-
def __init__(self, value):
13-
PC.__init__(self, value)
14-
m.CppDrvd.__init__(self, value + 1)
11+
class PPCC(PC, m.CppDrvd):
12+
pass
1513

1614

1715
def test_PC():
@@ -21,14 +19,14 @@ def test_PC():
2119
assert d.get_base_value() == 13
2220

2321

24-
def test_PPCCInit():
25-
d = PPCCInit(11)
26-
assert d.get_drvd_value() == 36
22+
def test_PPCC():
23+
d = PPCC(11)
24+
assert d.get_drvd_value() == 33
2725
d.reset_drvd_value(55)
2826
assert d.get_drvd_value() == 55
2927

30-
assert d.get_base_value() == 12
31-
assert d.get_base_value_from_drvd() == 12
28+
assert d.get_base_value() == 11
29+
assert d.get_base_value_from_drvd() == 11
3230
d.reset_base_value(20)
3331
assert d.get_base_value() == 20
3432
assert d.get_base_value_from_drvd() == 20

0 commit comments

Comments
 (0)