Skip to content

Test type_caster bare interface #2834

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final nitpick: test_type_caster_interface also works, if we want to have this file show off the interface?

test_union.cpp
test_virtual_functions.cpp)

Expand Down
126 changes: 126 additions & 0 deletions tests/test_type_caster_bare_interface.cpp
Original file line number Diff line number Diff line change
@@ -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 <string>

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<atyp> {
static constexpr auto name = _<atyp>();

// 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 <typename T_>
using cast_op_type = conditional_t<
std::is_same<remove_reference_t<T_>, atyp const *>::value,
atyp const *,
conditional_t<
std::is_same<remove_reference_t<T_>, atyp *>::value,
atyp *,
conditional_t<
std::is_same<T_, atyp const &>::value,
atyp const &,
conditional_t<std::is_same<T_, atyp &>::value,
atyp &,
conditional_t<std::is_same<T_, atyp &&>::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);
}
36 changes: 36 additions & 0 deletions tests/test_type_caster_bare_interface.py
Original file line number Diff line number Diff line change
@@ -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))