[skip ci] test: reject shared_ptr casts for custom holders (#6069)

Lock down the current behavior discussed in issue #6064: py::cast() from std::shared_ptr<T> remains rejected for custom holder bindings that are not std::shared_ptr or py::smart_holder.

This extracts the incompatible-holder coverage from PR #6068 and adds a test shaped like issue #6064, while PR #6065 and PR #6066 explore alternative support paths.
This commit is contained in:
Ralf W. Grosse-Kunstleve
2026-05-18 21:36:16 -07:00
committed by GitHub
parent 3160c82392
commit 28bbda9ddf
2 changed files with 59 additions and 0 deletions

View File

@@ -83,6 +83,24 @@ private:
// for demonstration purpose only, this imitates smart pointer with a const-only pointer
};
template <typename T>
class shared_ptr_as_custom_holder {
std::shared_ptr<T> ptr_;
public:
using element_type = T;
shared_ptr_as_custom_holder() = default;
explicit shared_ptr_as_custom_holder(T *) {
throw std::runtime_error("invalid shared_ptr_as_custom_holder constructor call");
}
explicit shared_ptr_as_custom_holder(std::shared_ptr<T> ptr) : ptr_(std::move(ptr)) {}
T *get() const { return ptr_.get(); }
};
// Custom object with builtin reference counting (see 'object.h' for the implementation)
class MyObject1 : public Object {
public:
@@ -239,6 +257,17 @@ struct SharedFromThisRef {
std::shared_ptr<B> shared = std::make_shared<B>();
};
class Issue6064UnsafePath {
public:
static std::shared_ptr<Issue6064UnsafePath> create() {
return {new Issue6064UnsafePath(), [](Issue6064UnsafePath *ptr) { delete ptr; }};
}
private:
Issue6064UnsafePath() = default;
~Issue6064UnsafePath() = default;
};
// Issue #865: shared_from_this doesn't work with virtual inheritance
struct SharedFromThisVBase : std::enable_shared_from_this<SharedFromThisVBase> {
SharedFromThisVBase() = default;
@@ -341,6 +370,7 @@ struct holder_helper<ref<T>> {
// Make pybind aware of the ref-counted wrapper type (s):
PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>, true)
PYBIND11_DECLARE_HOLDER_TYPE(T, const_only_shared_ptr<T>, true)
PYBIND11_DECLARE_HOLDER_TYPE(T, shared_ptr_as_custom_holder<T>)
// The following is not required anymore for std::shared_ptr, but it should compile without error:
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>)
PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr<T>)
@@ -501,6 +531,10 @@ TEST_SUBMODULE(smart_ptr, m) {
.def(py::init([](const std::string &value) { return MyObject6::createObject(value); }))
.def_property_readonly("value", &MyObject6::value);
py::class_<Issue6064UnsafePath, shared_ptr_as_custom_holder<Issue6064UnsafePath>>(
m, "Issue6064UnsafePath");
m.def("get_issue6064_unsafe_path_shared_ptr", []() { return Issue6064UnsafePath::create(); });
// test_shared_ptr_and_references
using A = SharedPtrRef::A;
py::class_<A, std::shared_ptr<A>>(m, "A");
@@ -559,6 +593,7 @@ TEST_SUBMODULE(smart_ptr, m) {
py::class_<C, custom_unique_ptr<C>>(m, "TypeWithMoveOnlyHolder")
.def_static("make", []() { return custom_unique_ptr<C>(new C); })
.def_static("make_as_object", []() { return py::cast(custom_unique_ptr<C>(new C)); });
m.def("get_type_with_move_only_holder_shared_ptr", []() { return std::make_shared<C>(); });
// test_holder_with_addressof_operator
using HolderWithAddressOf = shared_ptr_with_addressof_operator<TypeForHolderWithAddressOf>;

View File

@@ -368,3 +368,27 @@ def test_move_only_holder_caster_shared_ptr_with_smart_holder_support_enabled():
def test_const_only_holder():
o = m.MyObject6("my_data")
assert o.value == "my_data"
@pytest.mark.parametrize(
"cast_shared_ptr",
[
pytest.param(
m.get_type_with_move_only_holder_shared_ptr,
id="incompatible-custom-holder",
),
pytest.param(
m.get_issue6064_unsafe_path_shared_ptr,
id="issue6064-unsafe-path",
),
],
)
def test_shared_ptr_cast_for_custom_holder_throws(cast_shared_ptr):
with pytest.raises(
RuntimeError,
match=(
"Unable to convert std::shared_ptr<T> to Python when the bound type does not use"
" std::shared_ptr or py::smart_holder as its holder type"
),
):
cast_shared_ptr()