Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
75f61be
Backport of https://github.com/google/pybind11clif/pull/30099
rwgk Mar 25, 2025
de67af6
Add back `PYBIND11_WARNING_DISABLE_CLANG("-Wcast-function-type-mismat…
rwgk Mar 25, 2025
e8e921d
Remove obsolete `PY_VERSION_HEX >= 0x03080000` (discovered by gh-henr…
rwgk Mar 26, 2025
0da8312
git merge --squash add_test_for_boost_histogram_situation
rwgk Mar 27, 2025
cb3b41e
Revert "git merge --squash add_test_for_boost_histogram_situation"
rwgk Mar 27, 2025
d97186f
git merge --squash add_test_for_boost_histogram_situation
rwgk Mar 27, 2025
8b500da
Add `.get_capsule_for_scipy_LowLevelCallable()` method in function_re…
rwgk Mar 27, 2025
0539b14
Resolve clang-tidy errors
rwgk Mar 28, 2025
e4e527f
Add test_scipy_low_level_callable in tests/CMakeLists.txt
rwgk Mar 28, 2025
8757b56
Import scipy.integrate explicitly, in a way that doesn't trigger a ru…
rwgk Mar 28, 2025
70d6d11
Resolve PyPy errors with a general workaround: 'builtin_function_or_m…
rwgk Mar 28, 2025
cf73fea
Remove `extern "C"`: we want the name mangling here, all we need is t…
rwgk Mar 28, 2025
0140934
Rename get_capsule_for_scipy_LowLevelCallable → get_capsule_for_scipy…
rwgk Mar 28, 2025
bb703fc
Use `PYBIND11_STD_LAUNDER` consistently in pybind11/pybind11.h, pybin…
rwgk Mar 28, 2025
00bcb06
Rename again: insert `ABI_OR` for completeness (new full name is `get…
rwgk Mar 28, 2025
f54e2c7
Introduce PYBIND11_ENSURE_PRECONDITION_FOR_FUNCTIONAL_H_PERFORMANCE_O…
rwgk Mar 28, 2025
be4a234
Add static read-only properties to Python function record type: PYBIN…
rwgk Mar 28, 2025
61c9c6d
[ci skip] Response to question by gh-henryiii (https://github.com/pyb…
rwgk Mar 28, 2025
23852ff
Merge branch 'master' into function_record_scipy_capsule
rwgk Mar 28, 2025
547a559
Merge branch 'master' into function_record_scipy_capsule
rwgk Mar 31, 2025
7ffc6c5
[skip ci] Merge branch 'master' into function_record_scipy_capsule
rwgk Mar 31, 2025
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
58 changes: 57 additions & 1 deletion include/pybind11/detail/function_record_pyobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ void tp_dealloc_impl(PyObject *self);
void tp_free_impl(void *self);

static PyObject *reduce_ex_impl(PyObject *self, PyObject *, PyObject *);
static PyObject *get_capsule_for_scipy_LowLevelCallable_NO_ABI_OR_TYPE_SAFETY_impl(PyObject *self,
PyObject *,
PyObject *);

PYBIND11_WARNING_PUSH
#if defined(__GNUC__) && __GNUC__ >= 8
Expand All @@ -41,6 +44,10 @@ PYBIND11_WARNING_DISABLE_CLANG("-Wcast-function-type-mismatch")
#endif
static PyMethodDef tp_methods_impl[]
= {{"__reduce_ex__", (PyCFunction) reduce_ex_impl, METH_VARARGS | METH_KEYWORDS, nullptr},
{"get_capsule_for_scipy_LowLevelCallable_NO_ABI_OR_TYPE_SAFETY",
(PyCFunction) get_capsule_for_scipy_LowLevelCallable_NO_ABI_OR_TYPE_SAFETY_impl,
METH_VARARGS | METH_KEYWORDS,
"for use with scipy.LowLevelCallable()"},
{nullptr, nullptr, 0, nullptr}};
PYBIND11_WARNING_POP

Expand All @@ -49,6 +56,23 @@ constexpr char tp_name_impl[]
= "pybind11_detail_function_record_" PYBIND11_DETAIL_FUNCTION_RECORD_ABI_ID
"_" PYBIND11_PLATFORM_ABI_ID;

inline PyObject *get_property_pybind11_detail_function_record_abi_id(PyObject *, void *) {
return PyUnicode_FromString(PYBIND11_DETAIL_FUNCTION_RECORD_ABI_ID);
}

inline PyObject *get_property_pybind11_platform_abi_id(PyObject *, void *) {
return PyUnicode_FromString(PYBIND11_PLATFORM_ABI_ID);
}

static PyGetSetDef tp_getset_impl[] = {
{"PYBIND11_DETAIL_FUNCTION_RECORD_ABI_ID",
get_property_pybind11_detail_function_record_abi_id,
nullptr,
nullptr,
nullptr},
{"PYBIND11_PLATFORM_ABI_ID", get_property_pybind11_platform_abi_id, nullptr, nullptr, nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr}};

PYBIND11_NAMESPACE_END(function_record_PyTypeObject_methods)

// Designated initializers are a C++20 feature:
Expand Down Expand Up @@ -89,7 +113,7 @@ static PyTypeObject function_record_PyTypeObject = {
/* iternextfunc tp_iternext */ nullptr,
/* struct PyMethodDef *tp_methods */ function_record_PyTypeObject_methods::tp_methods_impl,
/* struct PyMemberDef *tp_members */ nullptr,
/* struct PyGetSetDef *tp_getset */ nullptr,
/* struct PyGetSetDef *tp_getset */ function_record_PyTypeObject_methods::tp_getset_impl,
/* struct _typeobject *tp_base */ nullptr,
/* PyObject *tp_dict */ nullptr,
/* descrgetfunc tp_descr_get */ nullptr,
Expand Down Expand Up @@ -202,6 +226,38 @@ inline PyObject *reduce_ex_impl(PyObject *self, PyObject *, PyObject *) {
return nullptr;
}

inline PyObject *get_capsule_for_scipy_LowLevelCallable_NO_ABI_OR_TYPE_SAFETY_impl(
PyObject *self, PyObject *args, PyObject *kwargs) {
static const char *kwlist[] = {"signature", nullptr};
const char *signature = nullptr;
if (PyArg_ParseTupleAndKeywords(args, kwargs, "s", const_cast<char **>(kwlist), &signature)
== 0) {
return nullptr;
}
function_record *rec = function_record_ptr_from_PyObject(self);
if (rec == nullptr) {
pybind11_fail("FATAL: get_capsule_for_scipy_LowLevelCallable_impl(): cannot obtain C++ "
"function_record.");
}
if (!rec->is_stateless) {
set_error(PyExc_TypeError, repr(self) + str(" is not a stateless function."));
return nullptr;
}
// This struct must match the layout of `struct capture` in pybind11/functional.h
struct type_erased_capture {
// Return (*)(Args...) in pybind11/functional.h
void (*void_func_ptr)(); // TYPE SAFETY IS LOST COMPLETELY HERE.

static type_erased_capture *from_data(void **data) {
return PYBIND11_STD_LAUNDER(reinterpret_cast<type_erased_capture *>(data));
}
};
PYBIND11_ENSURE_PRECONDITION_FOR_FUNCTIONAL_H_PERFORMANCE_OPTIMIZATIONS(
std::is_standard_layout<type_erased_capture>::value);
auto *tec = type_erased_capture::from_data(rec->data);
return capsule(reinterpret_cast<void *>(tec->void_func_ptr), signature).release().ptr();
}

PYBIND11_NAMESPACE_END(function_record_PyTypeObject_methods)

PYBIND11_NAMESPACE_END(detail)
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ set(PYBIND11_TEST_FILES
test_pickling
test_python_multiple_inheritance
test_pytypes
test_scipy_low_level_callable
test_sequences_and_iterators
test_smart_ptr
test_stl
Expand Down
15 changes: 15 additions & 0 deletions tests/test_scipy_low_level_callable.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include "pybind11_tests.h"

namespace pybind11_tests {
namespace scipy_low_level_callable {

double square21(double x) { return x * x * 21; }

} // namespace scipy_low_level_callable
} // namespace pybind11_tests

TEST_SUBMODULE(scipy_low_level_callable, m) {
using namespace pybind11_tests::scipy_low_level_callable;

m.def("square21", square21);
}
46 changes: 46 additions & 0 deletions tests/test_scipy_low_level_callable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from __future__ import annotations

import pytest

import pybind11_tests
from pybind11_tests import scipy_low_level_callable as m


def test_square21():
assert m.square21(2.0) == 2.0 * 2.0 * 21


def _m_square21_self():
try:
return m.square21.__self__
except AttributeError as e:
pytest.skip(f"{str(e)}")


def test_python_function_record_static_properties():
func_rec = _m_square21_self()
assert func_rec.PYBIND11_DETAIL_FUNCTION_RECORD_ABI_ID == "v1"
assert func_rec.PYBIND11_PLATFORM_ABI_ID in pybind11_tests.PYBIND11_INTERNALS_ID


def test_get_capsule_for_scipy_LowLevelCallable():
cap = (
_m_square21_self().get_capsule_for_scipy_LowLevelCallable_NO_ABI_OR_TYPE_SAFETY(
signature="double (double)"
)
)
assert repr(cap).startswith("<capsule object ")


def test_with_scipy_LowLevelCallable():
scipy = pytest.importorskip("scipy")
# Explicit import needed with some (older) scipy versions:
from scipy import integrate

llc = scipy.LowLevelCallable(
_m_square21_self().get_capsule_for_scipy_LowLevelCallable_NO_ABI_OR_TYPE_SAFETY(
signature="double (double)"
)
)
integral = integrate.quad(llc, 0, 1)
assert integral[0] == pytest.approx(7, rel=1e-12)