From f27bd9a3bb4db8c04f70fb72dea31cb6ae5adf66 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 7 Apr 2023 18:24:25 -0700 Subject: [PATCH 1/7] Alternative approach to #3807 that supports an important PyCLIF use case: `Base` not specified in `classh` statement, but passing Derived as Base works anyway. --- .../detail/smart_holder_type_casters.h | 9 ++--- tests/test_class_sh_void_ptr_capsule.cpp | 40 ++++++++++++++----- tests/test_class_sh_void_ptr_capsule.py | 4 ++ 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/include/pybind11/detail/smart_holder_type_casters.h b/include/pybind11/detail/smart_holder_type_casters.h index 2b96512b1f..428c3c3e1a 100644 --- a/include/pybind11/detail/smart_holder_type_casters.h +++ b/include/pybind11/detail/smart_holder_type_casters.h @@ -60,7 +60,7 @@ inline void *try_as_void_ptr_capsule_get_pointer(handle src, const char *typeid_ std::string as_void_ptr_function_name("as_"); as_void_ptr_function_name += type_name; - if (hasattr(src, as_void_ptr_function_name.c_str())) { + if (hasattr(src, as_void_ptr_function_name.c_str()) && !hasattr(src, "__getattr__")) { auto as_void_ptr_function = function(src.attr(as_void_ptr_function_name.c_str())); auto void_ptr_capsule = as_void_ptr_function(); if (isinstance(void_ptr_capsule)) { @@ -304,11 +304,8 @@ class modified_type_caster_generic_load_impl { loaded_v_h = value_and_holder(); return true; } - if (convert && cpptype) { - const auto &bases = all_type_info(srctype); - if (bases.empty() && try_as_void_ptr_capsule(src)) { - return true; - } + if (convert && cpptype && try_as_void_ptr_capsule(src)) { + return true; } return false; } diff --git a/tests/test_class_sh_void_ptr_capsule.cpp b/tests/test_class_sh_void_ptr_capsule.cpp index 45b8d2f642..ee89932075 100644 --- a/tests/test_class_sh_void_ptr_capsule.cpp +++ b/tests/test_class_sh_void_ptr_capsule.cpp @@ -58,20 +58,33 @@ struct Derived2 : Base12 { int bar() const { return 2; } }; +struct UnspecBase { + virtual ~UnspecBase() = default; + virtual int Get() const { return 100; } +}; + +inline int PassUnspecBase(const UnspecBase &sb) { return sb.Get() + 30; } + +struct UnspecDerived : UnspecBase { + int Get() const override { return 200; } +}; + } // namespace class_sh_void_ptr_capsule } // namespace pybind11_tests -PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_void_ptr_capsule::Valid) -PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_void_ptr_capsule::TypeWithGetattr) -PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_void_ptr_capsule::Base1) -PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_void_ptr_capsule::Base2) -PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_void_ptr_capsule::Base12) -PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_void_ptr_capsule::Derived1) -PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_void_ptr_capsule::Derived2) +using namespace pybind11_tests::class_sh_void_ptr_capsule; -TEST_SUBMODULE(class_sh_void_ptr_capsule, m) { - using namespace pybind11_tests::class_sh_void_ptr_capsule; +PYBIND11_SMART_HOLDER_TYPE_CASTERS(Valid) +PYBIND11_SMART_HOLDER_TYPE_CASTERS(TypeWithGetattr) +PYBIND11_SMART_HOLDER_TYPE_CASTERS(Base1) +PYBIND11_SMART_HOLDER_TYPE_CASTERS(Base2) +PYBIND11_SMART_HOLDER_TYPE_CASTERS(Base12) +PYBIND11_SMART_HOLDER_TYPE_CASTERS(Derived1) +PYBIND11_SMART_HOLDER_TYPE_CASTERS(Derived2) +PYBIND11_SMART_HOLDER_TYPE_CASTERS(UnspecBase) +PYBIND11_SMART_HOLDER_TYPE_CASTERS(UnspecDerived) +TEST_SUBMODULE(class_sh_void_ptr_capsule, m) { py::classh(m, "Valid"); m.def("get_from_valid_capsule", &get_from_valid_capsule); @@ -102,4 +115,13 @@ TEST_SUBMODULE(class_sh_void_ptr_capsule, m) { py::classh(m, "Derived1").def(py::init<>()).def("bar", &Derived1::bar); py::classh(m, "Derived2").def(py::init<>()).def("bar", &Derived2::bar); + + py::classh(m, "UnspecBase"); + m.def("PassUnspecBase", PassUnspecBase); + py::classh(m, "UnspecDerived") // UnspecBase NOT specified as base here. + .def(py::init<>()) + .def("as_pybind11_tests_class_sh_void_ptr_capsule_UnspecBase", [](UnspecDerived *self) { + return py::reinterpret_steal( + PyCapsule_New(static_cast(self), nullptr, nullptr)); + }); } diff --git a/tests/test_class_sh_void_ptr_capsule.py b/tests/test_class_sh_void_ptr_capsule.py index 1ca254d95a..7c9824d86e 100644 --- a/tests/test_class_sh_void_ptr_capsule.py +++ b/tests/test_class_sh_void_ptr_capsule.py @@ -94,3 +94,7 @@ def test_multiple_inheritance_getattr(): assert d2.foo() == 0 assert d2.bar() == 2 assert d2.prop2 == "Base GetAttr: prop2" + + +def test_pass_unspecified_base(): + assert m.PassUnspecBase(m.UnspecDerived()) == 230 From e8f0749b895ad941221316a5fa43d873a2b00397 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 7 Apr 2023 22:24:22 -0700 Subject: [PATCH 2/7] NOtest_multiple_inheritance_getattr in test_class_sh_void_ptr_capsule.py (quick experiment) --- tests/test_class_sh_void_ptr_capsule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_class_sh_void_ptr_capsule.py b/tests/test_class_sh_void_ptr_capsule.py index 7c9824d86e..5dcdbb20a7 100644 --- a/tests/test_class_sh_void_ptr_capsule.py +++ b/tests/test_class_sh_void_ptr_capsule.py @@ -84,7 +84,7 @@ def test_type_with_getattr(): assert obj.something == "GetAttr: something" -def test_multiple_inheritance_getattr(): +def NOtest_multiple_inheritance_getattr(): d1 = m.Derived1() assert d1.foo() == 0 assert d1.bar() == 1 From c7cde137dc6c5fa20c5efe83019f56f81962b270 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sat, 8 Apr 2023 05:09:06 -0700 Subject: [PATCH 3/7] Revert "NOtest_multiple_inheritance_getattr in test_class_sh_void_ptr_capsule.py (quick experiment)" This reverts commit e8f0749b895ad941221316a5fa43d873a2b00397. --- tests/test_class_sh_void_ptr_capsule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_class_sh_void_ptr_capsule.py b/tests/test_class_sh_void_ptr_capsule.py index 5dcdbb20a7..7c9824d86e 100644 --- a/tests/test_class_sh_void_ptr_capsule.py +++ b/tests/test_class_sh_void_ptr_capsule.py @@ -84,7 +84,7 @@ def test_type_with_getattr(): assert obj.something == "GetAttr: something" -def NOtest_multiple_inheritance_getattr(): +def test_multiple_inheritance_getattr(): d1 = m.Derived1() assert d1.foo() == 0 assert d1.bar() == 1 From e3efd380cd5e37bfa8e76a4008e8ddc395957634 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sat, 8 Apr 2023 07:43:58 -0700 Subject: [PATCH 4/7] Special handling of pybind11 objects to side-step try_as_void_ptr_capsule_get_pointer __getattr__ issues. --- .../detail/smart_holder_type_casters.h | 41 ++++++++++++++++++- tests/test_class_sh_void_ptr_capsule.cpp | 21 +++++++++- tests/test_class_sh_void_ptr_capsule.py | 23 +++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/include/pybind11/detail/smart_holder_type_casters.h b/include/pybind11/detail/smart_holder_type_casters.h index 428c3c3e1a..1ed0457b56 100644 --- a/include/pybind11/detail/smart_holder_type_casters.h +++ b/include/pybind11/detail/smart_holder_type_casters.h @@ -49,6 +49,43 @@ inline void replace_all(std::string &str, const std::string &from, const std::st } } +inline bool is_instance_method(PyObject *obj, PyObject *name) { + PyTypeObject *type_obj = (PyTypeObject *) obj; + if (!PyType_Check(obj)) { + type_obj = Py_TYPE(obj); + } + PyObject *descr = _PyType_Lookup(type_obj, name); + if (descr) { + if (PyInstanceMethod_Check(descr)) { + return true; + } + } + return false; +} + +extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *); + +inline bool type_is_pybind11_class_(PyObject *obj) { + PyTypeObject *type_obj = (PyTypeObject *) obj; + if (!PyType_Check(obj)) { + type_obj = Py_TYPE(obj); + } + if (type_obj->tp_new == pybind11_object_new) { + return true; + } + return false; +} + +inline bool enable_try_as_void_ptr_capsule_get_pointer(PyObject *obj, PyObject *name) { + if (PyType_Check(obj)) { + return false; + } + if (type_is_pybind11_class_(obj)) { + return is_instance_method(obj, name); + } + return hasattr(obj, name); +} + inline void *try_as_void_ptr_capsule_get_pointer(handle src, const char *typeid_name) { std::string type_name = typeid_name; detail::clean_type_id(type_name); @@ -60,7 +97,9 @@ inline void *try_as_void_ptr_capsule_get_pointer(handle src, const char *typeid_ std::string as_void_ptr_function_name("as_"); as_void_ptr_function_name += type_name; - if (hasattr(src, as_void_ptr_function_name.c_str()) && !hasattr(src, "__getattr__")) { + str as_void_ptr_function_name_pyobj(as_void_ptr_function_name); + if (enable_try_as_void_ptr_capsule_get_pointer(src.ptr(), + as_void_ptr_function_name_pyobj.ptr())) { auto as_void_ptr_function = function(src.attr(as_void_ptr_function_name.c_str())); auto void_ptr_capsule = as_void_ptr_function(); if (isinstance(void_ptr_capsule)) { diff --git a/tests/test_class_sh_void_ptr_capsule.cpp b/tests/test_class_sh_void_ptr_capsule.cpp index ee89932075..1b3cd72c5d 100644 --- a/tests/test_class_sh_void_ptr_capsule.cpp +++ b/tests/test_class_sh_void_ptr_capsule.cpp @@ -63,12 +63,16 @@ struct UnspecBase { virtual int Get() const { return 100; } }; -inline int PassUnspecBase(const UnspecBase &sb) { return sb.Get() + 30; } +int PassUnspecBase(const UnspecBase &sb) { return sb.Get() + 30; } struct UnspecDerived : UnspecBase { int Get() const override { return 200; } }; +struct ExploreType { + int plain_mfun() const { return 42; } +}; + } // namespace class_sh_void_ptr_capsule } // namespace pybind11_tests @@ -83,6 +87,7 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(Derived1) PYBIND11_SMART_HOLDER_TYPE_CASTERS(Derived2) PYBIND11_SMART_HOLDER_TYPE_CASTERS(UnspecBase) PYBIND11_SMART_HOLDER_TYPE_CASTERS(UnspecDerived) +PYBIND11_SMART_HOLDER_TYPE_CASTERS(ExploreType) TEST_SUBMODULE(class_sh_void_ptr_capsule, m) { py::classh(m, "Valid"); @@ -124,4 +129,18 @@ TEST_SUBMODULE(class_sh_void_ptr_capsule, m) { return py::reinterpret_steal( PyCapsule_New(static_cast(self), nullptr, nullptr)); }); + + py::classh(m, "ExploreType") + .def(py::init<>()) + .def("plain_mfun", &ExploreType::plain_mfun) + .def("__getattr__", + [](ExploreType &, const std::string &key) { return "GetAttr: " + key; }); + + m.def("is_instance_method", [](py::handle obj, const std::string &name) { + py::str name_pyobj(name); + return py::detail::is_instance_method(obj.ptr(), name_pyobj.ptr()); + }); + + m.def("type_is_pybind11_class_", + [](py::handle obj) { return py::detail::type_is_pybind11_class_(obj.ptr()); }); } diff --git a/tests/test_class_sh_void_ptr_capsule.py b/tests/test_class_sh_void_ptr_capsule.py index 7c9824d86e..a50a16c319 100644 --- a/tests/test_class_sh_void_ptr_capsule.py +++ b/tests/test_class_sh_void_ptr_capsule.py @@ -98,3 +98,26 @@ def test_multiple_inheritance_getattr(): def test_pass_unspecified_base(): assert m.PassUnspecBase(m.UnspecDerived()) == 230 + + +def test_explore(): + obj = m.ExploreType() + assert obj.plain_mfun() == 42 + assert obj.something == "GetAttr: something" + ii = obj.isinstance_method + assert ii == "GetAttr: isinstance_method" + assert m.is_instance_method(m.ExploreType, "plain_mfun") + assert not m.is_instance_method(m.ExploreType, "something") + assert m.is_instance_method(obj, "plain_mfun") + assert not m.is_instance_method(obj, "something") + + +def test_type_is_pybind11_class_(): + assert m.type_is_pybind11_class_(m.ExploreType) + assert not m.type_is_pybind11_class_(Valid) + + +def test_valid_debug(): + obj = Valid() + assert m.get_from_valid_capsule(obj) == 1 + assert obj.capsule_generated From 46ca11c3a4bcfb7ba238e7f92112d9cbb646595a Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sat, 8 Apr 2023 08:30:03 -0700 Subject: [PATCH 5/7] Inspect `internals.registered_types_py` in `type_is_pybind11_class_()` --- include/pybind11/detail/smart_holder_type_casters.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/include/pybind11/detail/smart_holder_type_casters.h b/include/pybind11/detail/smart_holder_type_casters.h index 1ed0457b56..759890b546 100644 --- a/include/pybind11/detail/smart_holder_type_casters.h +++ b/include/pybind11/detail/smart_holder_type_casters.h @@ -50,7 +50,7 @@ inline void replace_all(std::string &str, const std::string &from, const std::st } inline bool is_instance_method(PyObject *obj, PyObject *name) { - PyTypeObject *type_obj = (PyTypeObject *) obj; + auto *type_obj = (PyTypeObject *) obj; if (!PyType_Check(obj)) { type_obj = Py_TYPE(obj); } @@ -66,13 +66,18 @@ inline bool is_instance_method(PyObject *obj, PyObject *name) { extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *); inline bool type_is_pybind11_class_(PyObject *obj) { - PyTypeObject *type_obj = (PyTypeObject *) obj; + auto *type_obj = (PyTypeObject *) obj; if (!PyType_Check(obj)) { type_obj = Py_TYPE(obj); } if (type_obj->tp_new == pybind11_object_new) { return true; } + // EXPERIMENT: Does this help PyPy? (Other platforms do not need this.) + auto &internals = get_internals(); + if (internals.registered_types_py.find(type_obj) != internals.registered_types_py.end()) { + return true; + } return false; } From cd1849f70afadf98e45ade6375f1b3576434afdc Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sat, 8 Apr 2023 09:26:51 -0700 Subject: [PATCH 6/7] Remove debug code in tests subdir. --- tests/test_class_sh_void_ptr_capsule.cpp | 19 ------------------- tests/test_class_sh_void_ptr_capsule.py | 23 ----------------------- 2 files changed, 42 deletions(-) diff --git a/tests/test_class_sh_void_ptr_capsule.cpp b/tests/test_class_sh_void_ptr_capsule.cpp index 1b3cd72c5d..db47e3c25b 100644 --- a/tests/test_class_sh_void_ptr_capsule.cpp +++ b/tests/test_class_sh_void_ptr_capsule.cpp @@ -69,10 +69,6 @@ struct UnspecDerived : UnspecBase { int Get() const override { return 200; } }; -struct ExploreType { - int plain_mfun() const { return 42; } -}; - } // namespace class_sh_void_ptr_capsule } // namespace pybind11_tests @@ -87,7 +83,6 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(Derived1) PYBIND11_SMART_HOLDER_TYPE_CASTERS(Derived2) PYBIND11_SMART_HOLDER_TYPE_CASTERS(UnspecBase) PYBIND11_SMART_HOLDER_TYPE_CASTERS(UnspecDerived) -PYBIND11_SMART_HOLDER_TYPE_CASTERS(ExploreType) TEST_SUBMODULE(class_sh_void_ptr_capsule, m) { py::classh(m, "Valid"); @@ -129,18 +124,4 @@ TEST_SUBMODULE(class_sh_void_ptr_capsule, m) { return py::reinterpret_steal( PyCapsule_New(static_cast(self), nullptr, nullptr)); }); - - py::classh(m, "ExploreType") - .def(py::init<>()) - .def("plain_mfun", &ExploreType::plain_mfun) - .def("__getattr__", - [](ExploreType &, const std::string &key) { return "GetAttr: " + key; }); - - m.def("is_instance_method", [](py::handle obj, const std::string &name) { - py::str name_pyobj(name); - return py::detail::is_instance_method(obj.ptr(), name_pyobj.ptr()); - }); - - m.def("type_is_pybind11_class_", - [](py::handle obj) { return py::detail::type_is_pybind11_class_(obj.ptr()); }); } diff --git a/tests/test_class_sh_void_ptr_capsule.py b/tests/test_class_sh_void_ptr_capsule.py index a50a16c319..7c9824d86e 100644 --- a/tests/test_class_sh_void_ptr_capsule.py +++ b/tests/test_class_sh_void_ptr_capsule.py @@ -98,26 +98,3 @@ def test_multiple_inheritance_getattr(): def test_pass_unspecified_base(): assert m.PassUnspecBase(m.UnspecDerived()) == 230 - - -def test_explore(): - obj = m.ExploreType() - assert obj.plain_mfun() == 42 - assert obj.something == "GetAttr: something" - ii = obj.isinstance_method - assert ii == "GetAttr: isinstance_method" - assert m.is_instance_method(m.ExploreType, "plain_mfun") - assert not m.is_instance_method(m.ExploreType, "something") - assert m.is_instance_method(obj, "plain_mfun") - assert not m.is_instance_method(obj, "something") - - -def test_type_is_pybind11_class_(): - assert m.type_is_pybind11_class_(m.ExploreType) - assert not m.type_is_pybind11_class_(Valid) - - -def test_valid_debug(): - obj = Valid() - assert m.get_from_valid_capsule(obj) == 1 - assert obj.capsule_generated From 749ca42687c4b381ea2b05a3b9226bad07091e18 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sat, 8 Apr 2023 11:19:20 -0700 Subject: [PATCH 7/7] Clean up the modified `try_as_void_ptr_capsule_get_pointer()` implementation and new helper functions. --- .../detail/smart_holder_type_casters.h | 85 ++++++++----------- 1 file changed, 37 insertions(+), 48 deletions(-) diff --git a/include/pybind11/detail/smart_holder_type_casters.h b/include/pybind11/detail/smart_holder_type_casters.h index 759890b546..2d036e3e64 100644 --- a/include/pybind11/detail/smart_holder_type_casters.h +++ b/include/pybind11/detail/smart_holder_type_casters.h @@ -36,6 +36,7 @@ struct is_smart_holder_type : std::true_type {}; // SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code. inline void register_instance(instance *self, void *valptr, const type_info *tinfo); inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); +extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *); // Replace all occurrences of substrings in a string. inline void replace_all(std::string &str, const std::string &from, const std::string &to) { @@ -49,64 +50,52 @@ inline void replace_all(std::string &str, const std::string &from, const std::st } } -inline bool is_instance_method(PyObject *obj, PyObject *name) { - auto *type_obj = (PyTypeObject *) obj; - if (!PyType_Check(obj)) { - type_obj = Py_TYPE(obj); - } - PyObject *descr = _PyType_Lookup(type_obj, name); - if (descr) { - if (PyInstanceMethod_Check(descr)) { - return true; - } - } - return false; +inline bool type_is_pybind11_class_(PyTypeObject *type_obj) { +#if defined(PYPY_VERSION) + auto &internals = get_internals(); + return bool(internals.registered_types_py.find(type_obj) + != internals.registered_types_py.end()); +#else + return bool(type_obj->tp_new == pybind11_object_new); +#endif } -extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *); - -inline bool type_is_pybind11_class_(PyObject *obj) { - auto *type_obj = (PyTypeObject *) obj; - if (!PyType_Check(obj)) { - type_obj = Py_TYPE(obj); - } - if (type_obj->tp_new == pybind11_object_new) { - return true; - } - // EXPERIMENT: Does this help PyPy? (Other platforms do not need this.) - auto &internals = get_internals(); - if (internals.registered_types_py.find(type_obj) != internals.registered_types_py.end()) { - return true; - } - return false; +inline bool is_instance_method_of_type(PyTypeObject *type_obj, PyObject *attr_name) { + PyObject *descr = _PyType_Lookup(type_obj, attr_name); + return bool((descr != nullptr) && PyInstanceMethod_Check(descr)); } -inline bool enable_try_as_void_ptr_capsule_get_pointer(PyObject *obj, PyObject *name) { +inline object try_get_as_capsule_method(PyObject *obj, PyObject *attr_name) { if (PyType_Check(obj)) { - return false; + return object(); + } + PyTypeObject *type_obj = Py_TYPE(obj); + bool known_callable = false; + if (type_is_pybind11_class_(type_obj)) { + if (!is_instance_method_of_type(type_obj, attr_name)) { + return object(); + } + known_callable = true; + } + PyObject *method = PyObject_GetAttr(obj, attr_name); + if (method == nullptr) { + PyErr_Clear(); + return object(); } - if (type_is_pybind11_class_(obj)) { - return is_instance_method(obj, name); + if (!known_callable && PyCallable_Check(method) == 0) { + Py_DECREF(method); + return object(); } - return hasattr(obj, name); + return reinterpret_steal(method); } inline void *try_as_void_ptr_capsule_get_pointer(handle src, const char *typeid_name) { - std::string type_name = typeid_name; - detail::clean_type_id(type_name); - - // Convert `a::b::c` to `a_b_c`. - replace_all(type_name, "::", "_"); - // Remove all `*` in the type name. - replace_all(type_name, "*", ""); - - std::string as_void_ptr_function_name("as_"); - as_void_ptr_function_name += type_name; - str as_void_ptr_function_name_pyobj(as_void_ptr_function_name); - if (enable_try_as_void_ptr_capsule_get_pointer(src.ptr(), - as_void_ptr_function_name_pyobj.ptr())) { - auto as_void_ptr_function = function(src.attr(as_void_ptr_function_name.c_str())); - auto void_ptr_capsule = as_void_ptr_function(); + std::string suffix = clean_type_id(typeid_name); + replace_all(suffix, "::", "_"); // Convert `a::b::c` to `a_b_c`. + replace_all(suffix, "*", ""); + object as_capsule_method = try_get_as_capsule_method(src.ptr(), str("as_" + suffix).ptr()); + if (as_capsule_method) { + object void_ptr_capsule = as_capsule_method(); if (isinstance(void_ptr_capsule)) { return reinterpret_borrow(void_ptr_capsule).get_pointer(); }