Skip to content

Commit fc1a278

Browse files
committed
Snapshot of pybind/pybind11#4601 (squashed).
1 parent 50bb0bf commit fc1a278

File tree

6 files changed

+196
-1
lines changed

6 files changed

+196
-1
lines changed

include/pybind11/cast.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1207,7 +1207,11 @@ make_caster<T> load_type(const handle &handle) {
12071207
PYBIND11_NAMESPACE_END(detail)
12081208

12091209
// pytype -> C++ type
1210-
template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::value, int> = 0>
1210+
template <typename T,
1211+
detail::enable_if_t<!detail::is_pyobject<T>::value
1212+
&& !detail::is_same_ignoring_cvref<T, PyObject *>::value,
1213+
int>
1214+
= 0>
12111215
T cast(const handle &handle) {
12121216
using namespace detail;
12131217
constexpr bool is_enum_cast = type_uses_type_caster_enum_type<intrinsic_t<T>>::value;
@@ -1231,6 +1235,12 @@ T cast(const handle &handle) {
12311235
return T(reinterpret_borrow<object>(handle));
12321236
}
12331237

1238+
template <typename T,
1239+
detail::enable_if_t<detail::is_same_ignoring_cvref<T, PyObject *>::value, int> = 0>
1240+
T cast(const handle &handle) {
1241+
return handle.ptr();
1242+
}
1243+
12341244
// C++ type -> py::object
12351245
template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::value, int> = 0>
12361246
object cast(T &&value,

include/pybind11/detail/common.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,10 @@ template <class T>
751751
using remove_cvref_t = typename remove_cvref<T>::type;
752752
#endif
753753

754+
/// Example usage: is_same_ignoring_cvref<T, PyObject *>
755+
template <typename T, typename U>
756+
using is_same_ignoring_cvref = std::is_same<detail::remove_cvref_t<T>, U>;
757+
754758
/// Index sequences
755759
#if defined(PYBIND11_CPP14)
756760
using std::index_sequence;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) 2023 The pybind Community.
2+
3+
#pragma once
4+
5+
#include "detail/common.h"
6+
#include "detail/descr.h"
7+
#include "cast.h"
8+
#include "pytypes.h"
9+
10+
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
11+
PYBIND11_NAMESPACE_BEGIN(detail)
12+
13+
template <>
14+
class type_caster<PyObject> {
15+
public:
16+
static constexpr auto name = const_name("PyObject *");
17+
18+
// This overload is purely to guard against accidents.
19+
template <typename T,
20+
detail::enable_if_t<!is_same_ignoring_cvref<T, PyObject *>::value, int> = 0>
21+
static handle cast(T &&, return_value_policy, handle /*parent*/) {
22+
static_assert(is_same_ignoring_cvref<T, PyObject *>::value,
23+
"Invalid C++ type T for to-Python conversion (type_caster<PyObject>).");
24+
return nullptr; // Unreachable.
25+
}
26+
27+
static handle cast(PyObject *src, return_value_policy policy, handle /*parent*/) {
28+
if (src == nullptr) {
29+
throw error_already_set();
30+
}
31+
if (PyErr_Occurred()) {
32+
raise_from(PyExc_SystemError, "src != nullptr but PyErr_Occurred()");
33+
throw error_already_set();
34+
}
35+
if (policy == return_value_policy::take_ownership) {
36+
return src;
37+
}
38+
Py_INCREF(src);
39+
return src;
40+
}
41+
42+
bool load(handle src, bool) {
43+
value = reinterpret_borrow<object>(src);
44+
return true;
45+
}
46+
47+
template <typename T>
48+
using cast_op_type = PyObject *;
49+
50+
explicit operator PyObject *() { return value.ptr(); }
51+
52+
private:
53+
object value;
54+
};
55+
56+
PYBIND11_NAMESPACE_END(detail)
57+
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ set(PYBIND11_TEST_FILES
180180
test_thread
181181
test_type_caster_odr_guard_1
182182
test_type_caster_odr_guard_2
183+
test_type_caster_pyobject_ptr
183184
test_union
184185
test_virtual_functions)
185186

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#include <pybind11/functional.h>
2+
#include <pybind11/type_caster_pyobject_ptr.h>
3+
4+
#include "pybind11_tests.h"
5+
6+
TEST_SUBMODULE(type_caster_pyobject_ptr, m) {
7+
m.def("cast_from_pyobject_ptr", []() {
8+
PyObject *ptr = PyLong_FromLongLong(6758L);
9+
py::object retval = py::cast(ptr, py::return_value_policy::take_ownership);
10+
return retval;
11+
});
12+
m.def("cast_to_pyobject_ptr", [](py::handle obj) {
13+
auto *ptr = py::cast<PyObject *>(obj);
14+
return bool(PyTuple_CheckExact(ptr));
15+
});
16+
17+
m.def(
18+
"return_pyobject_ptr",
19+
[]() { return PyLong_FromLongLong(2314L); },
20+
py::return_value_policy::take_ownership);
21+
m.def("pass_pyobject_ptr", [](PyObject *obj) { return bool(PyTuple_CheckExact(obj)); });
22+
23+
m.def("call_callback_with_object_return",
24+
[](const std::function<py::object(int mode)> &cb, int mode) { return cb(mode); });
25+
m.def("call_callback_with_handle_return",
26+
[](const std::function<py::handle(int mode)> &cb, int mode) { return cb(mode); });
27+
//
28+
m.def("call_callback_with_pyobject_ptr_return",
29+
[](const std::function<PyObject *(int mode)> &cb, int mode) { return cb(mode); });
30+
m.def("call_callback_with_pyobject_ptr_arg",
31+
[](const std::function<bool(PyObject *)> &cb, py::handle obj) { return cb(obj.ptr()); });
32+
33+
m.def("cast_to_pyobject_ptr_nullptr", [](bool set_error) {
34+
if (set_error) {
35+
PyErr_SetString(PyExc_RuntimeError, "Reflective of healthy error handling.");
36+
}
37+
PyObject *ptr = nullptr;
38+
py::cast(ptr);
39+
});
40+
41+
m.def("cast_to_pyobject_ptr_non_nullptr_with_error_set", []() {
42+
PyErr_SetString(PyExc_RuntimeError, "Reflective of unhealthy error handling.");
43+
py::cast(Py_None);
44+
});
45+
46+
#ifdef PYBIND11_NO_COMPILE_SECTION // Change to ifndef for manual testing.
47+
{
48+
PyObject *ptr = nullptr;
49+
(void) py::cast(*ptr);
50+
}
51+
#endif
52+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import pytest
2+
3+
from pybind11_tests import type_caster_pyobject_ptr as m
4+
5+
6+
def test_cast_from_pyobject_ptr():
7+
assert m.cast_from_pyobject_ptr() == 6758
8+
9+
10+
def test_cast_to_pyobject_ptr():
11+
assert m.cast_to_pyobject_ptr(())
12+
assert not m.cast_to_pyobject_ptr({})
13+
14+
15+
def test_return_pyobject_ptr():
16+
assert m.return_pyobject_ptr() == 2314
17+
18+
19+
def test_pass_pyobject_ptr():
20+
assert m.pass_pyobject_ptr(())
21+
assert not m.pass_pyobject_ptr({})
22+
23+
24+
@pytest.mark.parametrize(
25+
"call_callback",
26+
[
27+
m.call_callback_with_object_return,
28+
m.call_callback_with_handle_return,
29+
m.call_callback_with_pyobject_ptr_return,
30+
],
31+
)
32+
def test_call_callback_with_object_return(call_callback):
33+
def cb(mode):
34+
if mode == 0:
35+
return 10
36+
if mode == 1:
37+
return "One"
38+
raise NotImplementedError(f"Unknown mode: {mode}")
39+
40+
assert call_callback(cb, 0) == 10
41+
assert call_callback(cb, 1) == "One"
42+
with pytest.raises(NotImplementedError, match="Unknown mode: 2"):
43+
call_callback(cb, 2)
44+
45+
46+
def test_call_callback_with_pyobject_ptr_arg():
47+
def cb(obj):
48+
return isinstance(obj, tuple)
49+
50+
assert m.call_callback_with_pyobject_ptr_arg(cb, ())
51+
assert not m.call_callback_with_pyobject_ptr_arg(cb, {})
52+
53+
54+
@pytest.mark.parametrize("set_error", [True, False])
55+
def test_cast_to_python_nullptr(set_error):
56+
expected = {
57+
True: r"^Reflective of healthy error handling\.$",
58+
False: (
59+
r"^Internal error: pybind11::error_already_set called "
60+
r"while Python error indicator not set\.$"
61+
),
62+
}[set_error]
63+
with pytest.raises(RuntimeError, match=expected):
64+
m.cast_to_pyobject_ptr_nullptr(set_error)
65+
66+
67+
def test_cast_to_python_non_nullptr_with_error_set():
68+
with pytest.raises(SystemError) as excinfo:
69+
m.cast_to_pyobject_ptr_non_nullptr_with_error_set()
70+
assert str(excinfo.value) == "src != nullptr but PyErr_Occurred()"
71+
assert str(excinfo.value.__cause__) == "Reflective of unhealthy error handling."

0 commit comments

Comments
 (0)