diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9ff6c0dd9a..37026d82a6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -129,6 +129,7 @@ set(PYBIND11_TEST_FILES test_stl.cpp test_stl_binders.cpp test_tagbased_polymorphic.cpp + test_type_caster_bare_interface.cpp test_union.cpp test_virtual_functions.cpp) diff --git a/tests/test_type_caster_bare_interface.cpp b/tests/test_type_caster_bare_interface.cpp new file mode 100644 index 0000000000..18d6888801 --- /dev/null +++ b/tests/test_type_caster_bare_interface.cpp @@ -0,0 +1,126 @@ +// Systematically exercises the detail::type_caster<> interface. This is going a step in the +// direction of an integration test, to ensure multiple components of pybind11 work together +// correctly. It is also useful to show the type_caster<> interface virtually clutter-free. + +// The entire type_caster load logic is intentionally omitted. The only purpose of this test is to +// trace the behavior of the `static handle cast()` functions and the type_caster `operator`s. +// Variable names are intentionally terse, to not distract from the more important function +// signatures: valu(e), ref(erence), ptr (pointer), r = rvalue, m = mutable, c = const. + +#include "pybind11_tests.h" + +#include + +namespace pybind11_tests { +namespace type_caster_bare_interface { + +struct atyp { // Short for "any type". + std::string trace; + atyp() : trace("default") {} + atyp(atyp const &other) { trace = other.trace + "_CpCtor"; } + atyp(atyp &&other) { trace = other.trace + "_MvCtor"; } +}; + +// clang-format off + +atyp rtrn_valu() { static atyp obj; obj.trace = "valu"; return obj; } +atyp&& rtrn_rref() { static atyp obj; obj.trace = "rref"; return std::move(obj); } +atyp const& rtrn_cref() { static atyp obj; obj.trace = "cref"; return obj; } +atyp& rtrn_mref() { static atyp obj; obj.trace = "mref"; return obj; } +atyp const* rtrn_cptr() { static atyp obj; obj.trace = "cptr"; return &obj; } +atyp* rtrn_mptr() { static atyp obj; obj.trace = "mptr"; return &obj; } + +std::string pass_valu(atyp obj) { return "pass_valu:" + obj.trace; } +std::string pass_rref(atyp&& obj) { return "pass_rref:" + obj.trace; } +std::string pass_cref(atyp const& obj) { return "pass_cref:" + obj.trace; } +std::string pass_mref(atyp& obj) { return "pass_mref:" + obj.trace; } +std::string pass_cptr(atyp const* obj) { return "pass_cptr:" + obj->trace; } +std::string pass_mptr(atyp* obj) { return "pass_mptr:" + obj->trace; } + +// clang-format on + +} // namespace type_caster_bare_interface +} // namespace pybind11_tests + +namespace pybind11 { +namespace detail { + +using namespace pybind11_tests::type_caster_bare_interface; + +template <> +struct type_caster { + static constexpr auto name = _(); + + // static handle cast(atyp, ...) + // is redundant (leads to ambiguous overloads). + + static handle cast(atyp &&src, return_value_policy /*policy*/, handle /*parent*/) { + return str("cast_rref:" + src.trace).release(); + } + + static handle cast(atyp const &src, return_value_policy /*policy*/, handle /*parent*/) { + return str("cast_cref:" + src.trace).release(); + } + + static handle cast(atyp &src, return_value_policy /*policy*/, handle /*parent*/) { + return str("cast_mref:" + src.trace).release(); + } + + static handle cast(atyp const *src, return_value_policy /*policy*/, handle /*parent*/) { + return str("cast_cptr:" + src->trace).release(); + } + + static handle cast(atyp *src, return_value_policy /*policy*/, handle /*parent*/) { + return str("cast_mptr:" + src->trace).release(); + } + + template + using cast_op_type = conditional_t< + std::is_same, atyp const *>::value, + atyp const *, + conditional_t< + std::is_same, atyp *>::value, + atyp *, + conditional_t< + std::is_same::value, + atyp const &, + conditional_t::value, + atyp &, + conditional_t::value, atyp &&, atyp>>>>>; + + // clang-format off + + operator atyp() { static atyp obj; obj.trace = "valu"; return obj; } + operator atyp&&() { static atyp obj; obj.trace = "rref"; return std::move(obj); } + operator atyp const&() { static atyp obj; obj.trace = "cref"; return obj; } + operator atyp&() { static atyp obj; obj.trace = "mref"; return obj; } + operator atyp const*() { static atyp obj; obj.trace = "cptr"; return &obj; } + operator atyp*() { static atyp obj; obj.trace = "mptr"; return &obj; } + + // clang-format on + + // The entire load logic is intentionally omitted. + bool load(handle /*src*/, bool /*convert*/) { return true; } +}; + +} // namespace detail +} // namespace pybind11 + +TEST_SUBMODULE(type_caster_bare_interface, m) { + namespace py = pybind11; + using namespace pybind11_tests::type_caster_bare_interface; + + m.def("rtrn_valu", rtrn_valu); + m.def("rtrn_rref", rtrn_rref); + m.def("rtrn_cref", rtrn_cref); + m.def("rtrn_mref", rtrn_mref); + m.def("rtrn_cptr", rtrn_cptr); + m.def("rtrn_mptr", rtrn_mptr); + + m.def("pass_valu", pass_valu); + m.def("pass_rref", pass_rref); + m.def("pass_cref", pass_cref); + m.def("pass_mref", pass_mref); + m.def("pass_cptr", pass_cptr); + m.def("pass_mptr", pass_mptr); +} diff --git a/tests/test_type_caster_bare_interface.py b/tests/test_type_caster_bare_interface.py new file mode 100644 index 0000000000..fd27e1ae92 --- /dev/null +++ b/tests/test_type_caster_bare_interface.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +import re + +import pytest + +from pybind11_tests import type_caster_bare_interface as m + + +@pytest.mark.parametrize( + "rtrn_f, expected", + [ + (m.rtrn_valu, "cast_rref:valu_CpCtor"), + (m.rtrn_rref, "cast_rref:rref"), + (m.rtrn_cref, "cast_cref:cref"), + (m.rtrn_mref, "cast_mref:mref"), + (m.rtrn_cptr, "cast_cptr:cptr"), + (m.rtrn_mptr, "cast_mptr:mptr"), + ], +) +def test_cast(rtrn_f, expected): + assert re.match(expected, rtrn_f()) + + +@pytest.mark.parametrize( + "pass_f, expected", + [ + (m.pass_valu, "pass_valu:rref_MvCtor"), + (m.pass_rref, "pass_rref:rref"), + (m.pass_cref, "pass_cref:cref"), + (m.pass_mref, "pass_mref:mref"), + (m.pass_cptr, "pass_cptr:cptr"), + (m.pass_mptr, "pass_mptr:mptr"), + ], +) +def test_operator(pass_f, expected): + assert re.match(expected, pass_f(None))