Skip to content

Commit 0a9599f

Browse files
committed
Add all_type_info_check_for_divergence() and some tests.
1 parent f3bb31e commit 0a9599f

File tree

3 files changed

+90
-0
lines changed

3 files changed

+90
-0
lines changed

include/pybind11/detail/type_caster_base.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,40 @@ inline void all_type_info_add_base_most_derived_first(std::vector<type_info *> &
115115
bases.push_back(addl_base);
116116
}
117117

118+
inline void all_type_info_check_for_divergence(const std::vector<type_info *> &bases) {
119+
using sz_t = std::size_t;
120+
sz_t n = bases.size();
121+
if (n < 3) {
122+
return;
123+
}
124+
std::vector<sz_t> cluster_ids;
125+
cluster_ids.reserve(n);
126+
for (sz_t ci = 0; ci < n; ci++) {
127+
cluster_ids.push_back(ci);
128+
}
129+
for (sz_t i = 0; i < n - 1; i++) {
130+
if (cluster_ids[i] != i) {
131+
continue;
132+
}
133+
for (sz_t j = i + 1; j < n; j++) {
134+
if (PyType_IsSubtype(bases[i]->type, bases[j]->type) != 0) {
135+
sz_t k = cluster_ids[j];
136+
if (k == j) {
137+
cluster_ids[j] = i;
138+
} else {
139+
PyErr_Format(
140+
PyExc_TypeError,
141+
"bases include diverging derived types: base=%s, derived1=%s, derived2=%s",
142+
bases[j]->type->tp_name,
143+
bases[k]->type->tp_name,
144+
bases[i]->type->tp_name);
145+
throw error_already_set();
146+
}
147+
}
148+
}
149+
}
150+
}
151+
118152
// Populates a just-created cache entry.
119153
PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_info *> &bases) {
120154
assert(bases.empty());
@@ -168,6 +202,7 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_
168202
}
169203
}
170204
}
205+
all_type_info_check_for_divergence(bases);
171206
}
172207

173208
/**

tests/test_python_multiple_inheritance.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ struct CppDrvd : CppBase {
2626
int drvd_value;
2727
};
2828

29+
struct CppDrvd2 : CppBase {
30+
explicit CppDrvd2(int value) : CppBase(value), drvd2_value(value * 5) {}
31+
int get_drvd2_value() const { return drvd2_value; }
32+
void reset_drvd2_value(int new_value) { drvd2_value = new_value; }
33+
34+
int get_base_value_from_drvd2() const { return get_base_value(); }
35+
void reset_base_value_from_drvd2(int new_value) { reset_base_value(new_value); }
36+
37+
private:
38+
int drvd2_value;
39+
};
40+
2941
} // namespace test_python_multiple_inheritance
3042

3143
TEST_SUBMODULE(python_multiple_inheritance, m) {
@@ -42,4 +54,11 @@ TEST_SUBMODULE(python_multiple_inheritance, m) {
4254
.def("reset_drvd_value", &CppDrvd::reset_drvd_value)
4355
.def("get_base_value_from_drvd", &CppDrvd::get_base_value_from_drvd)
4456
.def("reset_base_value_from_drvd", &CppDrvd::reset_base_value_from_drvd);
57+
58+
py::class_<CppDrvd2, CppBase>(m, "CppDrvd2")
59+
.def(py::init<int>())
60+
.def("get_drvd2_value", &CppDrvd2::get_drvd2_value)
61+
.def("reset_drvd2_value", &CppDrvd2::reset_drvd2_value)
62+
.def("get_base_value_from_drvd2", &CppDrvd2::get_base_value_from_drvd2)
63+
.def("reset_base_value_from_drvd2", &CppDrvd2::reset_base_value_from_drvd2);
4564
}

tests/test_python_multiple_inheritance.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Adapted from:
22
# https://github.com/google/clif/blob/5718e4d0807fd3b6a8187dde140069120b81ecef/clif/testing/python/python_multiple_inheritance_test.py
33

4+
import pytest
5+
46
from pybind11_tests import python_multiple_inheritance as m
57

68

@@ -12,6 +14,22 @@ class PPCC(PC, m.CppDrvd):
1214
pass
1315

1416

17+
class PPPCCC(PPCC, m.CppDrvd2):
18+
pass
19+
20+
21+
class PC1(m.CppDrvd):
22+
pass
23+
24+
25+
class PC2(m.CppDrvd2):
26+
pass
27+
28+
29+
class PCD(PC1, PC2):
30+
pass
31+
32+
1533
def test_PC():
1634
d = PC(11)
1735
assert d.get_base_value() == 11
@@ -33,3 +51,21 @@ def test_PPCC():
3351
d.reset_base_value_from_drvd(30)
3452
assert d.get_base_value() == 30
3553
assert d.get_base_value_from_drvd() == 30
54+
55+
56+
def NOtest_PPPCCC():
57+
# terminate called after throwing an instance of 'pybind11::error_already_set'
58+
# what(): TypeError: bases include diverging derived types:
59+
# base=pybind11_tests.python_multiple_inheritance.CppBase,
60+
# derived1=pybind11_tests.python_multiple_inheritance.CppDrvd,
61+
# derived2=pybind11_tests.python_multiple_inheritance.CppDrvd2
62+
PPPCCC(11)
63+
64+
65+
def test_PCD():
66+
# This escapes all_type_info_check_for_divergence() because CppBase does not appear in bases.
67+
with pytest.raises(
68+
TypeError,
69+
match=r"CppDrvd2\.__init__\(\) must be called when overriding __init__$",
70+
):
71+
PCD(11)

0 commit comments

Comments
 (0)