diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp index 1eba534dd..6e06db9fc 100644 --- a/tests/test_virtual_functions.cpp +++ b/tests/test_virtual_functions.cpp @@ -174,6 +174,25 @@ struct DispatchIssue : Base { } }; +// An abstract adder class that uses visitor pattern to add two data +// objects and send the result to the visitor functor +struct AdderBase { + struct Data {}; + using DataVisitor = std::function; + + virtual void operator()(const Data& first, const Data& second, const DataVisitor& visitor) const = 0; + virtual ~AdderBase() = default; + AdderBase() = default; + AdderBase(const AdderBase&) = delete; +}; + +struct Adder : AdderBase { + void operator()(const Data& first, const Data& second, const DataVisitor& visitor) const override { + PYBIND11_OVERRIDE_PURE_NAME(void, AdderBase, "__call__", operator(), first, second, visitor); + } +}; + + static void test_gil() { { py::gil_scoped_acquire lock; @@ -295,6 +314,27 @@ TEST_SUBMODULE(virtual_functions, m) { m.def("dispatch_issue_go", [](const Base * b) { return b->dispatch(); }); + // test_recursive_dispatch_issue + // #3357: Recursive dispatch fails to find python function override + pybind11::class_(m, "Adder") + .def(pybind11::init<>()) + .def("__call__", &AdderBase::operator()); + + pybind11::class_(m, "Data") + .def(pybind11::init<>()); + + m.def("add2", [](const AdderBase::Data& first, const AdderBase::Data& second, + const AdderBase& adder, const AdderBase::DataVisitor& visitor) { + adder(first, second, visitor); + }); + + m.def("add3", [](const AdderBase::Data& first, const AdderBase::Data& second, const AdderBase::Data& third, + const AdderBase& adder, const AdderBase::DataVisitor& visitor) { + adder(first, second, [&] (const AdderBase::Data& first_plus_second) { + adder(first_plus_second, third, visitor); + }); + }); + // test_override_ref // #392/397: overriding reference-returning functions class OverrideTest { diff --git a/tests/test_virtual_functions.py b/tests/test_virtual_functions.py index f7d3bd1e4..0b550992f 100644 --- a/tests/test_virtual_functions.py +++ b/tests/test_virtual_functions.py @@ -257,6 +257,39 @@ def test_dispatch_issue(msg): assert m.dispatch_issue_go(b) == "Yay.." +def test_recursive_dispatch_issue(msg): + """#3357: Recursive dispatch fails to find python function override""" + + class Data(m.Data): + def __init__(self, value): + super(Data, self).__init__() + self.value = value + + class Adder(m.Adder): + def __call__(self, first, second, visitor): + # lambda is a workaround, which adds extra frame to the + # current CPython thread. Removing lambda reveals the bug + # [https://github.com/pybind/pybind11/issues/3357] + (lambda: visitor(Data(first.value + second.value)))() + + class StoreResultVisitor: + def __init__(self): + self.result = None + + def __call__(self, data): + self.result = data.value + + store = StoreResultVisitor() + + m.add2(Data(1), Data(2), Adder(), store) + assert store.result == 3 + + # without lambda in Adder class, this function fails with + # RuntimeError: Tried to call pure virtual function "AdderBase::__call__" + m.add3(Data(1), Data(2), Data(3), Adder(), store) + assert store.result == 6 + + def test_override_ref(): """#392/397: overriding reference-returning functions""" o = m.OverrideTest("asdf")