From 0a9599f775bfd3ca196c5e23a3fcf2890cbf6e82 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 2 Nov 2023 17:03:20 -0700 Subject: [PATCH] Add `all_type_info_check_for_divergence()` and some tests. --- include/pybind11/detail/type_caster_base.h | 35 +++++++++++++++++++++ tests/test_python_multiple_inheritance.cpp | 19 ++++++++++++ tests/test_python_multiple_inheritance.py | 36 ++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 476646ee8..e305066db 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -115,6 +115,40 @@ inline void all_type_info_add_base_most_derived_first(std::vector & bases.push_back(addl_base); } +inline void all_type_info_check_for_divergence(const std::vector &bases) { + using sz_t = std::size_t; + sz_t n = bases.size(); + if (n < 3) { + return; + } + std::vector cluster_ids; + cluster_ids.reserve(n); + for (sz_t ci = 0; ci < n; ci++) { + cluster_ids.push_back(ci); + } + for (sz_t i = 0; i < n - 1; i++) { + if (cluster_ids[i] != i) { + continue; + } + for (sz_t j = i + 1; j < n; j++) { + if (PyType_IsSubtype(bases[i]->type, bases[j]->type) != 0) { + sz_t k = cluster_ids[j]; + if (k == j) { + cluster_ids[j] = i; + } else { + PyErr_Format( + PyExc_TypeError, + "bases include diverging derived types: base=%s, derived1=%s, derived2=%s", + bases[j]->type->tp_name, + bases[k]->type->tp_name, + bases[i]->type->tp_name); + throw error_already_set(); + } + } + } + } +} + // Populates a just-created cache entry. PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector &bases) { assert(bases.empty()); @@ -168,6 +202,7 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector(m, "CppDrvd2") + .def(py::init()) + .def("get_drvd2_value", &CppDrvd2::get_drvd2_value) + .def("reset_drvd2_value", &CppDrvd2::reset_drvd2_value) + .def("get_base_value_from_drvd2", &CppDrvd2::get_base_value_from_drvd2) + .def("reset_base_value_from_drvd2", &CppDrvd2::reset_base_value_from_drvd2); } diff --git a/tests/test_python_multiple_inheritance.py b/tests/test_python_multiple_inheritance.py index 3bddd67df..6669ac339 100644 --- a/tests/test_python_multiple_inheritance.py +++ b/tests/test_python_multiple_inheritance.py @@ -1,6 +1,8 @@ # Adapted from: # https://github.com/google/clif/blob/5718e4d0807fd3b6a8187dde140069120b81ecef/clif/testing/python/python_multiple_inheritance_test.py +import pytest + from pybind11_tests import python_multiple_inheritance as m @@ -12,6 +14,22 @@ class PPCC(PC, m.CppDrvd): pass +class PPPCCC(PPCC, m.CppDrvd2): + pass + + +class PC1(m.CppDrvd): + pass + + +class PC2(m.CppDrvd2): + pass + + +class PCD(PC1, PC2): + pass + + def test_PC(): d = PC(11) assert d.get_base_value() == 11 @@ -33,3 +51,21 @@ def test_PPCC(): d.reset_base_value_from_drvd(30) assert d.get_base_value() == 30 assert d.get_base_value_from_drvd() == 30 + + +def NOtest_PPPCCC(): + # terminate called after throwing an instance of 'pybind11::error_already_set' + # what(): TypeError: bases include diverging derived types: + # base=pybind11_tests.python_multiple_inheritance.CppBase, + # derived1=pybind11_tests.python_multiple_inheritance.CppDrvd, + # derived2=pybind11_tests.python_multiple_inheritance.CppDrvd2 + PPPCCC(11) + + +def test_PCD(): + # This escapes all_type_info_check_for_divergence() because CppBase does not appear in bases. + with pytest.raises( + TypeError, + match=r"CppDrvd2\.__init__\(\) must be called when overriding __init__$", + ): + PCD(11)