mirror of
https://github.com/pybind/pybind11.git
synced 2026-05-14 02:03:34 +00:00
Merge tag 'v3.0.3' into stable
v3.0.3 release
This commit is contained in:
4
.github/workflows/nightlies.yml
vendored
4
.github/workflows/nightlies.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
nox -s build
|
||||
nox -s build_global
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: Packages
|
||||
path: dist/*
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
needs: [build_wheel]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: Packages
|
||||
path: dist
|
||||
|
||||
8
.github/workflows/pip.yml
vendored
8
.github/workflows/pip.yml
vendored
@@ -72,13 +72,13 @@ jobs:
|
||||
run: twine check dist/*
|
||||
|
||||
- name: Save standard package
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: standard
|
||||
path: dist/pybind11-*
|
||||
|
||||
- name: Save global package
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: global
|
||||
path: dist/*global-*
|
||||
@@ -100,10 +100,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Downloads all to directories matching the artifact names
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
|
||||
- name: Generate artifact attestation for sdist and wheel
|
||||
uses: actions/attest-build-provenance@v3
|
||||
uses: actions/attest-build-provenance@v4
|
||||
with:
|
||||
subject-path: "*/pybind11*"
|
||||
|
||||
|
||||
18
.github/workflows/tests-cibw.yml
vendored
18
.github/workflows/tests-cibw.yml
vendored
@@ -70,24 +70,6 @@ jobs:
|
||||
if: contains(matrix.runs-on, 'macos')
|
||||
run: echo "CIBW_TEST_COMMAND=" >> "$GITHUB_ENV"
|
||||
|
||||
# Temporarily disable Android tests on ubuntu-latest due to emulator issues.
|
||||
# See https://github.com/pybind/pybind11/pull/5914.
|
||||
- name: "NOTE: Android tests are disabled on ubuntu-latest"
|
||||
if: contains(matrix.runs-on, 'ubuntu')
|
||||
run: |
|
||||
echo "CIBW_TEST_COMMAND=" >> "$GITHUB_ENV"
|
||||
echo '::warning::Android cibuildwheel tests are disabled on ubuntu-latest (CIBW_TEST_COMMAND is empty). See PR 5914.'
|
||||
|
||||
# https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/
|
||||
- name: Enable KVM for Android emulator
|
||||
if: contains(matrix.runs-on, 'ubuntu')
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- run: pipx install patchelf
|
||||
|
||||
- uses: pypa/cibuildwheel@v3.3
|
||||
env:
|
||||
CIBW_PLATFORM: android
|
||||
|
||||
@@ -25,14 +25,14 @@ repos:
|
||||
|
||||
# Clang format the codebase automatically
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: "v21.1.8"
|
||||
rev: "v22.1.0"
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types_or: [c++, c, cuda]
|
||||
|
||||
# Ruff, the Python auto-correcting linter/formatter written in Rust
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.14
|
||||
rev: v0.15.4
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: ["--fix", "--show-fixes"]
|
||||
@@ -122,7 +122,7 @@ repos:
|
||||
# Use mirror because pre-commit autoupdate confuses tags in the upstream repo.
|
||||
# See https://github.com/crate-ci/typos/issues/390
|
||||
- repo: https://github.com/adhtruong/mirrors-typos
|
||||
rev: "v1.42.3"
|
||||
rev: "v1.44.0"
|
||||
hooks:
|
||||
- id: typos
|
||||
args: []
|
||||
@@ -144,14 +144,14 @@ repos:
|
||||
|
||||
# PyLint has native support - not always usable, but works for us
|
||||
- repo: https://github.com/PyCQA/pylint
|
||||
rev: "v4.0.4"
|
||||
rev: "v4.0.5"
|
||||
hooks:
|
||||
- id: pylint
|
||||
files: ^pybind11
|
||||
|
||||
# Check schemas on some of our YAML files
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.36.1
|
||||
rev: 0.37.0
|
||||
hooks:
|
||||
- id: check-readthedocs
|
||||
- id: check-github-workflows
|
||||
|
||||
@@ -13,6 +13,62 @@ Changes will be added here periodically from the "Suggested changelog
|
||||
entry" block in pull request descriptions.
|
||||
|
||||
|
||||
## Version 3.0.3 (March 31, 2026)
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- Fixed TSS key exhaustion in `implicitly_convertible()` when many implicit conversions are registered across large module sets.
|
||||
[#6020](https://github.com/pybind/pybind11/pull/6020)
|
||||
|
||||
- Fixed heap-buffer-overflow in `pythonbuf` with undersized buffers by enforcing a minimum buffer size.
|
||||
[#6019](https://github.com/pybind/pybind11/pull/6019)
|
||||
|
||||
- Fixed virtual-inheritance pointer offset crashes when dispatching inherited methods through virtual bases.
|
||||
[#6017](https://github.com/pybind/pybind11/pull/6017)
|
||||
|
||||
- Fixed `free(): invalid pointer` crashes during interpreter shutdown with `py::enum_<>` by duplicating late-added `def_property_static` argument strings.
|
||||
[#6015](https://github.com/pybind/pybind11/pull/6015)
|
||||
|
||||
- Fixed `function_record` heap-type deallocation to call `PyObject_Free()` and decref the type.
|
||||
[#6010](https://github.com/pybind/pybind11/pull/6010)
|
||||
|
||||
- Hardened `PYBIND11_MODULE_PYINIT` and `get_internals()` against module-initialization crashes.
|
||||
[#6018](https://github.com/pybind/pybind11/pull/6018)
|
||||
|
||||
- Fixed `static_pointer_cast` build failure with virtual inheritance in `holder_caster_foreign_helpers.h`.
|
||||
[#6014](https://github.com/pybind/pybind11/pull/6014)
|
||||
|
||||
- Fixed ambiguous `factory` template specialization that caused compilation failures with nvcc + GCC 14.
|
||||
[#6011](https://github.com/pybind/pybind11/pull/6011)
|
||||
|
||||
- Fixed crash in `def_readwrite` for non-smart-holder properties of smart-holder classes.
|
||||
[#6008](https://github.com/pybind/pybind11/pull/6008)
|
||||
|
||||
- Fixed memory leak for `py::dynamic_attr()` objects on Python 3.13+ by clearing managed `__dict__` contents during deallocation.
|
||||
[#5999](https://github.com/pybind/pybind11/pull/5999)
|
||||
|
||||
- Fixed binding of `noexcept` and ref-qualified (`&`, `&&`) methods inherited from unregistered base classes.
|
||||
[#5992](https://github.com/pybind/pybind11/pull/5992)
|
||||
|
||||
Internal:
|
||||
|
||||
- Moved `tomlkit` dependency to the dev dependency group.
|
||||
[#5990](https://github.com/pybind/pybind11/pull/5990)
|
||||
|
||||
- Switched to newer public CPython APIs (`PyType_GetFlags` and public vectorcall APIs where available).
|
||||
[#6005](https://github.com/pybind/pybind11/pull/6005)
|
||||
|
||||
Tests:
|
||||
|
||||
- Made an async callback test deterministic by replacing fixed sleep with bounded waiting.
|
||||
[#5986](https://github.com/pybind/pybind11/pull/5986)
|
||||
|
||||
CI:
|
||||
|
||||
- Re-enabled Android tests in the cibuildwheel workflow.
|
||||
[#6001](https://github.com/pybind/pybind11/pull/6001)
|
||||
|
||||
|
||||
## Version 3.0.2 (February 16, 2026)
|
||||
|
||||
New Features:
|
||||
|
||||
@@ -41,13 +41,13 @@ myst-parser==3.0.1
|
||||
# via -r requirements.in
|
||||
packaging==24.0
|
||||
# via sphinx
|
||||
pygments==2.17.2
|
||||
pygments==2.20.0
|
||||
# via
|
||||
# furo
|
||||
# sphinx
|
||||
pyyaml==6.0.2
|
||||
# via myst-parser
|
||||
requests==2.32.4
|
||||
requests==2.33.0
|
||||
# via sphinx
|
||||
snowballstemmer==2.2.0
|
||||
# via sphinx
|
||||
|
||||
@@ -378,7 +378,7 @@ struct type_record {
|
||||
#ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET
|
||||
dynamic_attr |= base_info->type->tp_dictoffset != 0;
|
||||
#else
|
||||
dynamic_attr |= (base_info->type->tp_flags & Py_TPFLAGS_MANAGED_DICT) != 0;
|
||||
dynamic_attr |= (PyType_GetFlags(base_info->type) & Py_TPFLAGS_MANAGED_DICT) != 0;
|
||||
#endif
|
||||
|
||||
if (caster) {
|
||||
|
||||
@@ -199,8 +199,7 @@ public:
|
||||
template <typename T_, \
|
||||
::pybind11::detail::enable_if_t< \
|
||||
std::is_same<type, ::pybind11::detail::remove_cv_t<T_>>::value, \
|
||||
int> \
|
||||
= 0> \
|
||||
int> = 0> \
|
||||
static ::pybind11::handle cast( \
|
||||
T_ *src, ::pybind11::return_value_policy policy, ::pybind11::handle parent) { \
|
||||
if (!src) \
|
||||
@@ -1020,7 +1019,19 @@ public:
|
||||
return smart_holder_type_caster_support::smart_holder_from_shared_ptr(
|
||||
src, policy, parent, srcs.result);
|
||||
}
|
||||
return type_caster_base<type>::cast_holder(srcs, &src);
|
||||
|
||||
auto *tinfo = srcs.result.tinfo;
|
||||
if (tinfo != nullptr && tinfo->holder_enum_v == holder_enum_t::std_shared_ptr) {
|
||||
return type_caster_base<type>::cast_holder(srcs, &src);
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
return type_caster_base<type>::cast(
|
||||
srcs, return_value_policy::reference_internal, parent);
|
||||
}
|
||||
|
||||
throw cast_error("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");
|
||||
}
|
||||
|
||||
// This function will succeed even if the `responsible_parent` does not own the
|
||||
@@ -1662,8 +1673,7 @@ PYBIND11_NAMESPACE_END(detail)
|
||||
template <typename T,
|
||||
detail::enable_if_t<!detail::is_pyobject<T>::value
|
||||
&& !detail::is_same_ignoring_cvref<T, PyObject *>::value,
|
||||
int>
|
||||
= 0>
|
||||
int> = 0>
|
||||
T cast(const handle &handle) {
|
||||
using namespace detail;
|
||||
constexpr bool is_enum_cast = type_uses_type_caster_enum_type<intrinsic_t<T>>::value;
|
||||
@@ -1698,8 +1708,7 @@ template <typename T,
|
||||
typename Handle,
|
||||
detail::enable_if_t<detail::is_same_ignoring_cvref<T, PyObject *>::value
|
||||
&& detail::is_same_ignoring_cvref<Handle, handle>::value,
|
||||
int>
|
||||
= 0>
|
||||
int> = 0>
|
||||
T cast(Handle &&handle) {
|
||||
return handle.inc_ref().ptr();
|
||||
}
|
||||
@@ -1708,8 +1717,7 @@ template <typename T,
|
||||
typename Object,
|
||||
detail::enable_if_t<detail::is_same_ignoring_cvref<T, PyObject *>::value
|
||||
&& detail::is_same_ignoring_cvref<Object, object>::value,
|
||||
int>
|
||||
= 0>
|
||||
int> = 0>
|
||||
T cast(Object &&obj) {
|
||||
return obj.release().ptr();
|
||||
}
|
||||
@@ -2243,8 +2251,13 @@ public:
|
||||
if (m_names) {
|
||||
nargs -= m_names.size();
|
||||
}
|
||||
PyObject *result = _PyObject_Vectorcall(
|
||||
ptr, m_args.data() + 1, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, m_names.ptr());
|
||||
PyObject *result =
|
||||
#if PY_VERSION_HEX >= 0x03090000
|
||||
PyObject_Vectorcall(
|
||||
#else
|
||||
_PyObject_Vectorcall(
|
||||
#endif
|
||||
ptr, m_args.data() + 1, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, m_names.ptr());
|
||||
if (!result) {
|
||||
throw error_already_set();
|
||||
}
|
||||
|
||||
@@ -64,8 +64,7 @@ public:
|
||||
return src;
|
||||
}
|
||||
static const std::chrono::duration<rep, period> &
|
||||
get_duration(const std::chrono::duration<rep, period> &&)
|
||||
= delete;
|
||||
get_duration(const std::chrono::duration<rep, period> &&) = delete;
|
||||
|
||||
// If this is a time_point get the time_since_epoch
|
||||
template <typename Clock>
|
||||
|
||||
@@ -503,6 +503,17 @@ extern "C" inline void pybind11_object_dealloc(PyObject *self) {
|
||||
PyObject_GC_UnTrack(self);
|
||||
}
|
||||
|
||||
#if PY_VERSION_HEX >= 0x030D0000
|
||||
// PyObject_ClearManagedDict() is available from Python 3.13+. It must be
|
||||
// called before tp_free() because on Python 3.14+ tp_free no longer
|
||||
// implicitly clears the managed dict, which would abandon the refcounts of
|
||||
// objects stored in __dict__ of py::dynamic_attr() types, causing permanent
|
||||
// memory leaks.
|
||||
if (PyType_HasFeature(type, Py_TPFLAGS_MANAGED_DICT)) {
|
||||
PyObject_ClearManagedDict(self);
|
||||
}
|
||||
#endif
|
||||
|
||||
clear_instance(self);
|
||||
|
||||
type->tp_free(self);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
/* -- start version constants -- */
|
||||
#define PYBIND11_VERSION_MAJOR 3
|
||||
#define PYBIND11_VERSION_MINOR 0
|
||||
#define PYBIND11_VERSION_MICRO 2
|
||||
#define PYBIND11_VERSION_MICRO 3
|
||||
// ALPHA = 0xA, BETA = 0xB, GAMMA = 0xC (release candidate), FINAL = 0xF (stable release)
|
||||
// - The release level is set to "alpha" for development versions.
|
||||
// Use 0xA0 (LEVEL=0xA, SERIAL=0) for development versions.
|
||||
@@ -27,7 +27,7 @@
|
||||
#define PYBIND11_VERSION_RELEASE_LEVEL PY_RELEASE_LEVEL_FINAL
|
||||
#define PYBIND11_VERSION_RELEASE_SERIAL 0
|
||||
// String version of (micro, release level, release serial), e.g.: 0a0, 0b1, 0rc1, 0
|
||||
#define PYBIND11_VERSION_PATCH 2
|
||||
#define PYBIND11_VERSION_PATCH 3
|
||||
/* -- end version constants -- */
|
||||
|
||||
#if !defined(Py_PACK_FULL_VERSION)
|
||||
@@ -167,6 +167,14 @@
|
||||
# define PYBIND11_NOINLINE __attribute__((noinline)) inline
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define PYBIND11_ALWAYS_INLINE __forceinline
|
||||
#elif defined(__GNUC__)
|
||||
# define PYBIND11_ALWAYS_INLINE __attribute__((__always_inline__)) inline
|
||||
#else
|
||||
# define PYBIND11_ALWAYS_INLINE inline
|
||||
#endif
|
||||
|
||||
#if defined(__MINGW32__)
|
||||
// For unknown reasons all PYBIND11_DEPRECATED member trigger a warning when declared
|
||||
// whether it is used or not
|
||||
@@ -449,19 +457,24 @@ PyModuleDef_Init should be treated like any other PyObject (so not shared across
|
||||
static int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject *); \
|
||||
PYBIND11_PLUGIN_IMPL(name) { \
|
||||
PYBIND11_CHECK_PYTHON_VERSION \
|
||||
pybind11::detail::ensure_internals(); \
|
||||
static ::pybind11::detail::slots_array mod_def_slots = ::pybind11::detail::init_slots( \
|
||||
&PYBIND11_CONCAT(pybind11_exec_, name), ##__VA_ARGS__); \
|
||||
static PyModuleDef def{/* m_base */ PyModuleDef_HEAD_INIT, \
|
||||
/* m_name */ PYBIND11_TOSTRING(name), \
|
||||
/* m_doc */ nullptr, \
|
||||
/* m_size */ 0, \
|
||||
/* m_methods */ nullptr, \
|
||||
/* m_slots */ mod_def_slots.data(), \
|
||||
/* m_traverse */ nullptr, \
|
||||
/* m_clear */ nullptr, \
|
||||
/* m_free */ nullptr}; \
|
||||
return PyModuleDef_Init(&def); \
|
||||
try { \
|
||||
pybind11::detail::ensure_internals(); \
|
||||
static ::pybind11::detail::slots_array mod_def_slots \
|
||||
= ::pybind11::detail::init_slots(&PYBIND11_CONCAT(pybind11_exec_, name), \
|
||||
##__VA_ARGS__); \
|
||||
static PyModuleDef def{/* m_base */ PyModuleDef_HEAD_INIT, \
|
||||
/* m_name */ PYBIND11_TOSTRING(name), \
|
||||
/* m_doc */ nullptr, \
|
||||
/* m_size */ 0, \
|
||||
/* m_methods */ nullptr, \
|
||||
/* m_slots */ mod_def_slots.data(), \
|
||||
/* m_traverse */ nullptr, \
|
||||
/* m_clear */ nullptr, \
|
||||
/* m_free */ nullptr}; \
|
||||
return PyModuleDef_Init(&def); \
|
||||
} \
|
||||
PYBIND11_CATCH_INIT_EXCEPTIONS \
|
||||
return nullptr; \
|
||||
}
|
||||
|
||||
#define PYBIND11_MODULE_EXEC(name, variable) \
|
||||
@@ -1026,6 +1039,17 @@ struct is_instantiation<Class, Class<Us...>> : std::true_type {};
|
||||
template <typename T>
|
||||
using is_shared_ptr = is_instantiation<std::shared_ptr, T>;
|
||||
|
||||
/// Detects whether static_cast<Derived*>(Base*) is valid, i.e. the inheritance is non-virtual.
|
||||
/// Used to detect virtual bases: if this is false, pointer adjustments require the implicit_casts
|
||||
/// chain rather than reinterpret_cast.
|
||||
template <typename Base, typename Derived, typename = void>
|
||||
struct is_static_downcastable : std::false_type {};
|
||||
template <typename Base, typename Derived>
|
||||
struct is_static_downcastable<Base,
|
||||
Derived,
|
||||
void_t<decltype(static_cast<Derived *>(std::declval<Base *>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
/// Check if T looks like an input iterator
|
||||
template <typename T, typename = void>
|
||||
struct is_input_iterator : std::false_type {};
|
||||
@@ -1048,14 +1072,30 @@ struct strip_function_object {
|
||||
using type = typename remove_class<decltype(&F::operator())>::type;
|
||||
};
|
||||
|
||||
// Strip noexcept from a free function type (C++17: noexcept is part of the type).
|
||||
template <typename T>
|
||||
struct remove_noexcept {
|
||||
using type = T;
|
||||
};
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
template <typename R, typename... A>
|
||||
struct remove_noexcept<R(A...) noexcept> {
|
||||
using type = R(A...);
|
||||
};
|
||||
#endif
|
||||
template <typename T>
|
||||
using remove_noexcept_t = typename remove_noexcept<T>::type;
|
||||
|
||||
// Extracts the function signature from a function, function pointer or lambda.
|
||||
// Strips noexcept from the result so that factory/pickle_factory partial specializations
|
||||
// (which match plain Return(Args...)) work correctly with noexcept callables (issue #2234).
|
||||
template <typename Function, typename F = remove_reference_t<Function>>
|
||||
using function_signature_t = conditional_t<
|
||||
using function_signature_t = remove_noexcept_t<conditional_t<
|
||||
std::is_function<F>::value,
|
||||
F,
|
||||
typename conditional_t<std::is_pointer<F>::value || std::is_member_pointer<F>::value,
|
||||
std::remove_pointer<F>,
|
||||
strip_function_object<F>>::type>;
|
||||
strip_function_object<F>>::type>>;
|
||||
|
||||
/// Returns true if the type looks like a lambda: that is, isn't a function, pointer or member
|
||||
/// pointer. Note that this can catch all sorts of other things, too; this is intended to be used
|
||||
@@ -1204,6 +1244,36 @@ struct overload_cast_impl {
|
||||
-> decltype(pmf) {
|
||||
return pmf;
|
||||
}
|
||||
|
||||
// Define const/non-const member-pointer selector pairs for qualifier combinations.
|
||||
// The `qualifiers` parameter is used in type position, where extra parentheses are invalid.
|
||||
// NOLINTBEGIN(bugprone-macro-parentheses)
|
||||
#define PYBIND11_OVERLOAD_CAST_MEMBER_PTR(qualifiers) \
|
||||
template <typename Return, typename Class> \
|
||||
constexpr auto operator()(Return (Class::*pmf)(Args...) qualifiers, std::false_type = {}) \
|
||||
const noexcept -> decltype(pmf) { \
|
||||
return pmf; \
|
||||
} \
|
||||
template <typename Return, typename Class> \
|
||||
constexpr auto operator()(Return (Class::*pmf)(Args...) const qualifiers, std::true_type) \
|
||||
const noexcept -> decltype(pmf) { \
|
||||
return pmf; \
|
||||
}
|
||||
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(&)
|
||||
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(&&)
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
template <typename Return>
|
||||
constexpr auto operator()(Return (*pf)(Args...) noexcept) const noexcept -> decltype(pf) {
|
||||
return pf;
|
||||
}
|
||||
|
||||
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(noexcept)
|
||||
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(& noexcept)
|
||||
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(&& noexcept)
|
||||
#endif
|
||||
#undef PYBIND11_OVERLOAD_CAST_MEMBER_PTR
|
||||
// NOLINTEND(bugprone-macro-parentheses)
|
||||
};
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
|
||||
@@ -31,13 +31,31 @@ struct holder_caster_foreign_helpers {
|
||||
PyObject *o;
|
||||
};
|
||||
|
||||
// Downcast shared_ptr from the enable_shared_from_this base to the target type.
|
||||
// SFINAE probe: use static_pointer_cast when the static downcast is valid (common case),
|
||||
// fall back to dynamic_pointer_cast when it isn't (virtual inheritance — issue #5989).
|
||||
// We can't use dynamic_pointer_cast unconditionally because it requires polymorphic types;
|
||||
// we can't use is_polymorphic to choose because that's orthogonal to virtual inheritance.
|
||||
// (The implementation uses the "tag dispatch via overload priority" trick.)
|
||||
template <typename type, typename esft_base>
|
||||
static auto esft_downcast(const std::shared_ptr<esft_base> &existing, int /*preferred*/)
|
||||
-> decltype(static_cast<type *>(std::declval<esft_base *>()), std::shared_ptr<type>()) {
|
||||
return std::static_pointer_cast<type>(existing);
|
||||
}
|
||||
|
||||
template <typename type, typename esft_base>
|
||||
static std::shared_ptr<type> esft_downcast(const std::shared_ptr<esft_base> &existing,
|
||||
... /*fallback*/) {
|
||||
return std::dynamic_pointer_cast<type>(existing);
|
||||
}
|
||||
|
||||
template <typename type>
|
||||
static auto set_via_shared_from_this(type *value, std::shared_ptr<type> *holder_out)
|
||||
-> decltype(value->shared_from_this(), bool()) {
|
||||
// object derives from enable_shared_from_this;
|
||||
// try to reuse an existing shared_ptr if one is known
|
||||
if (auto existing = try_get_shared_from_this(value)) {
|
||||
*holder_out = std::static_pointer_cast<type>(existing);
|
||||
*holder_out = esft_downcast<type>(existing, 0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -304,11 +304,10 @@ struct constructor {
|
||||
extra...);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Class,
|
||||
typename... Extra,
|
||||
enable_if_t<Class::has_alias && std::is_constructible<Cpp<Class>, Args...>::value, int>
|
||||
= 0>
|
||||
template <typename Class,
|
||||
typename... Extra,
|
||||
enable_if_t<Class::has_alias && std::is_constructible<Cpp<Class>, Args...>::value,
|
||||
int> = 0>
|
||||
static void execute(Class &cl, const Extra &...extra) {
|
||||
cl.def(
|
||||
"__init__",
|
||||
@@ -325,11 +324,10 @@ struct constructor {
|
||||
extra...);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Class,
|
||||
typename... Extra,
|
||||
enable_if_t<Class::has_alias && !std::is_constructible<Cpp<Class>, Args...>::value, int>
|
||||
= 0>
|
||||
template <typename Class,
|
||||
typename... Extra,
|
||||
enable_if_t<Class::has_alias && !std::is_constructible<Cpp<Class>, Args...>::value,
|
||||
int> = 0>
|
||||
static void execute(Class &cl, const Extra &...extra) {
|
||||
cl.def(
|
||||
"__init__",
|
||||
@@ -345,11 +343,10 @@ struct constructor {
|
||||
// Implementing class for py::init_alias<...>()
|
||||
template <typename... Args>
|
||||
struct alias_constructor {
|
||||
template <
|
||||
typename Class,
|
||||
typename... Extra,
|
||||
enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value, int>
|
||||
= 0>
|
||||
template <typename Class,
|
||||
typename... Extra,
|
||||
enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value,
|
||||
int> = 0>
|
||||
static void execute(Class &cl, const Extra &...extra) {
|
||||
cl.def(
|
||||
"__init__",
|
||||
@@ -370,8 +367,15 @@ template <typename CFunc,
|
||||
struct factory;
|
||||
|
||||
// Specialization for py::init(Func)
|
||||
// Note: The 4th template parameter `void_type()` is explicitly specified to resolve a
|
||||
// template ambiguity with the dual-factory specialization below when compiled with
|
||||
// nvcc + GCC (see #5565). Without it, both specializations match equally well for the
|
||||
// single-factory case, since the 4th parameter defaults to
|
||||
// `function_signature_t<void_type(*)()>` = `void_type()`, which the dual-factory
|
||||
// specialization can also decompose as `AReturn(AArgs...)` with `AReturn=void_type`
|
||||
// and `AArgs={}`.
|
||||
template <typename Func, typename Return, typename... Args>
|
||||
struct factory<Func, void_type (*)(), Return(Args...)> {
|
||||
struct factory<Func, void_type (*)(), Return(Args...), void_type()> {
|
||||
remove_reference_t<Func> class_factory;
|
||||
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
|
||||
@@ -59,15 +59,13 @@ using ExceptionTranslator = void (*)(std::exception_ptr);
|
||||
# define PYBIND11_TLS_KEY_INIT(var) \
|
||||
_Pragma("clang diagnostic push") /**/ \
|
||||
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
|
||||
Py_tss_t var \
|
||||
= Py_tss_NEEDS_INIT; \
|
||||
Py_tss_t var = Py_tss_NEEDS_INIT; \
|
||||
_Pragma("clang diagnostic pop")
|
||||
#elif defined(__GNUC__) && !defined(__INTEL_COMPILER)
|
||||
# define PYBIND11_TLS_KEY_INIT(var) \
|
||||
_Pragma("GCC diagnostic push") /**/ \
|
||||
_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
|
||||
Py_tss_t var \
|
||||
= Py_tss_NEEDS_INIT; \
|
||||
Py_tss_t var = Py_tss_NEEDS_INIT; \
|
||||
_Pragma("GCC diagnostic pop")
|
||||
#else
|
||||
# define PYBIND11_TLS_KEY_INIT(var) Py_tss_t var = Py_tss_NEEDS_INIT;
|
||||
@@ -864,7 +862,11 @@ inline internals_pp_manager<internals> &get_internals_pp_manager() {
|
||||
/// Return a reference to the current `internals` data
|
||||
PYBIND11_NOINLINE internals &get_internals() {
|
||||
auto &ppmgr = get_internals_pp_manager();
|
||||
auto &internals_ptr = *ppmgr.get_pp();
|
||||
auto *pp = ppmgr.get_pp();
|
||||
if (!pp) {
|
||||
pybind11_fail("get_internals: get_pp() returned nullptr");
|
||||
}
|
||||
auto &internals_ptr = *pp;
|
||||
if (!internals_ptr) {
|
||||
// Slow path, something needs fetched from the state dict or created
|
||||
gil_scoped_acquire_simple gil;
|
||||
@@ -872,6 +874,9 @@ PYBIND11_NOINLINE internals &get_internals() {
|
||||
|
||||
ppmgr.create_pp_content_once(&internals_ptr);
|
||||
|
||||
if (!internals_ptr) {
|
||||
pybind11_fail("get_internals: create_pp_content_once() produced nullptr");
|
||||
}
|
||||
if (!internals_ptr->instance_base) {
|
||||
// This calls get_internals, so cannot be called from within the internals constructor
|
||||
// called above because internals_ptr must be set before get_internals is called again
|
||||
|
||||
@@ -117,8 +117,16 @@ private:
|
||||
int sync() override { return _sync(); }
|
||||
|
||||
public:
|
||||
// Minimum buffer size must accommodate the largest incomplete UTF-8 sequence
|
||||
// (3 bytes) plus one position reserved for overflow(), i.e. 4 bytes total.
|
||||
static constexpr size_t minimum_buffer_size = 4;
|
||||
|
||||
explicit pythonbuf(const object &pyostream, size_t buffer_size = 1024)
|
||||
: buf_size(buffer_size), d_buffer(new char[buf_size]), pywrite(pyostream.attr("write")),
|
||||
: buf_size(buffer_size < minimum_buffer_size // ternary avoids C++14 std::max ODR-use of
|
||||
// static constexpr
|
||||
? minimum_buffer_size
|
||||
: buffer_size),
|
||||
d_buffer(new char[buf_size]), pywrite(pyostream.attr("write")),
|
||||
pyflush(pyostream.attr("flush")) {
|
||||
setp(d_buffer.get(), d_buffer.get() + buf_size - 1);
|
||||
}
|
||||
|
||||
@@ -2327,4 +2327,86 @@ Helper vectorize(Return (Class::*f)(Args...) const) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
|
||||
// Intentionally no &&/const&& overloads: vectorized method calls operate on the bound Python
|
||||
// instance and should not consume/move-from self.
|
||||
// Vectorize a class method (non-const, lvalue ref-qualified):
|
||||
template <typename Return,
|
||||
typename Class,
|
||||
typename... Args,
|
||||
typename Helper = detail::vectorize_helper<
|
||||
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) &>())),
|
||||
Return,
|
||||
Class *,
|
||||
Args...>>
|
||||
Helper vectorize(Return (Class::*f)(Args...) &) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
|
||||
// Vectorize a class method (const, lvalue ref-qualified):
|
||||
template <typename Return,
|
||||
typename Class,
|
||||
typename... Args,
|
||||
typename Helper = detail::vectorize_helper<
|
||||
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const &>())),
|
||||
Return,
|
||||
const Class *,
|
||||
Args...>>
|
||||
Helper vectorize(Return (Class::*f)(Args...) const &) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
// Vectorize a class method (non-const, noexcept):
|
||||
template <typename Return,
|
||||
typename Class,
|
||||
typename... Args,
|
||||
typename Helper = detail::vectorize_helper<
|
||||
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) noexcept>())),
|
||||
Return,
|
||||
Class *,
|
||||
Args...>>
|
||||
Helper vectorize(Return (Class::*f)(Args...) noexcept) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
|
||||
// Vectorize a class method (const, noexcept):
|
||||
template <typename Return,
|
||||
typename Class,
|
||||
typename... Args,
|
||||
typename Helper = detail::vectorize_helper<
|
||||
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const noexcept>())),
|
||||
Return,
|
||||
const Class *,
|
||||
Args...>>
|
||||
Helper vectorize(Return (Class::*f)(Args...) const noexcept) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
|
||||
// Vectorize a class method (non-const, lvalue ref-qualified, noexcept):
|
||||
template <typename Return,
|
||||
typename Class,
|
||||
typename... Args,
|
||||
typename Helper = detail::vectorize_helper<
|
||||
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) & noexcept>())),
|
||||
Return,
|
||||
Class *,
|
||||
Args...>>
|
||||
Helper vectorize(Return (Class::*f)(Args...) & noexcept) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
|
||||
// Vectorize a class method (const, lvalue ref-qualified, noexcept):
|
||||
template <typename Return,
|
||||
typename Class,
|
||||
typename... Args,
|
||||
typename Helper = detail::vectorize_helper<
|
||||
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const & noexcept>())),
|
||||
Return,
|
||||
const Class *,
|
||||
Args...>>
|
||||
Helper vectorize(Return (Class::*f)(Args...) const & noexcept) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
||||
|
||||
@@ -369,6 +369,96 @@ public:
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (non-const, rvalue ref-qualifier)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) &&, const Extra &...extra) {
|
||||
initialize(
|
||||
[f](Class *c, Arg... args) -> Return {
|
||||
return (std::move(*c).*f)(std::forward<Arg>(args)...);
|
||||
},
|
||||
(Return (*)(Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (const, rvalue ref-qualifier)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) const &&, const Extra &...extra) {
|
||||
initialize(
|
||||
[f](const Class *c, Arg... args) -> Return {
|
||||
return (std::move(*c).*f)(std::forward<Arg>(args)...);
|
||||
},
|
||||
(Return (*)(const Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
/// Construct a cpp_function from a class method (non-const, no ref-qualifier, noexcept)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) noexcept, const Extra &...extra) {
|
||||
initialize(
|
||||
[f](Class *c, Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
|
||||
(Return (*)(Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (non-const, lvalue ref-qualifier, noexcept)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) & noexcept, const Extra &...extra) {
|
||||
initialize(
|
||||
[f](Class *c, Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
|
||||
(Return (*)(Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (const, no ref-qualifier, noexcept)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) const noexcept, const Extra &...extra) {
|
||||
initialize([f](const Class *c,
|
||||
Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
|
||||
(Return (*)(const Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (const, lvalue ref-qualifier, noexcept)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) const & noexcept, const Extra &...extra) {
|
||||
initialize([f](const Class *c,
|
||||
Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
|
||||
(Return (*)(const Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (non-const, rvalue ref-qualifier, noexcept)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) && noexcept, const Extra &...extra) {
|
||||
initialize(
|
||||
[f](Class *c, Arg... args) -> Return {
|
||||
return (std::move(*c).*f)(std::forward<Arg>(args)...);
|
||||
},
|
||||
(Return (*)(Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (const, rvalue ref-qualifier, noexcept)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) const && noexcept, const Extra &...extra) {
|
||||
initialize(
|
||||
[f](const Class *c, Arg... args) -> Return {
|
||||
return (std::move(*c).*f)(std::forward<Arg>(args)...);
|
||||
},
|
||||
(Return (*)(const Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Return the function name
|
||||
object name() const { return attr("__name__"); }
|
||||
|
||||
@@ -1321,9 +1411,15 @@ PYBIND11_NAMESPACE_BEGIN(function_record_PyTypeObject_methods)
|
||||
|
||||
// This implementation needs the definition of `class cpp_function`.
|
||||
inline void tp_dealloc_impl(PyObject *self) {
|
||||
// Save type before PyObject_Free invalidates self.
|
||||
auto *type = Py_TYPE(self);
|
||||
auto *py_func_rec = reinterpret_cast<function_record_PyObject *>(self);
|
||||
cpp_function::destruct(py_func_rec->cpp_func_rec);
|
||||
py_func_rec->cpp_func_rec = nullptr;
|
||||
// PyObject_New increments the heap type refcount and allocates via
|
||||
// PyObject_Malloc; balance both here
|
||||
PyObject_Free(self);
|
||||
Py_DECREF(type);
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_END(function_record_PyTypeObject_methods)
|
||||
@@ -1859,29 +1955,89 @@ inline void add_class_method(object &cls, const char *name_, const cpp_function
|
||||
}
|
||||
}
|
||||
|
||||
/// Type trait to rebind a member function pointer's class to `Derived`, preserving all
|
||||
/// cv/ref/noexcept qualifiers. The primary template has no `type` member, providing SFINAE
|
||||
/// failure for unsupported member function pointer types. `source_class` holds the original
|
||||
/// class for use in `is_accessible_base_of` checks.
|
||||
template <typename Derived, typename T>
|
||||
struct rebind_member_ptr {};
|
||||
|
||||
// Define one specialization per supported qualifier combination via a local macro.
|
||||
// The qualifiers argument appears in type position, not expression position, so
|
||||
// parenthesizing it would produce invalid C++.
|
||||
// The no-qualifier specialization is written out explicitly to avoid invoking the macro with an
|
||||
// empty argument, which triggers MSVC warning C4003.
|
||||
template <typename Derived, typename Return, typename Class, typename... Args>
|
||||
struct rebind_member_ptr<Derived, Return (Class::*)(Args...)> {
|
||||
using type = Return (Derived::*)(Args...);
|
||||
using source_class = Class;
|
||||
};
|
||||
// NOLINTBEGIN(bugprone-macro-parentheses)
|
||||
#define PYBIND11_REBIND_MEMBER_PTR(qualifiers) \
|
||||
template <typename Derived, typename Return, typename Class, typename... Args> \
|
||||
struct rebind_member_ptr<Derived, Return (Class::*)(Args...) qualifiers> { \
|
||||
using type = Return (Derived::*)(Args...) qualifiers; \
|
||||
using source_class = Class; \
|
||||
}
|
||||
PYBIND11_REBIND_MEMBER_PTR(const);
|
||||
PYBIND11_REBIND_MEMBER_PTR(&);
|
||||
PYBIND11_REBIND_MEMBER_PTR(const &);
|
||||
PYBIND11_REBIND_MEMBER_PTR(&&);
|
||||
PYBIND11_REBIND_MEMBER_PTR(const &&);
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
PYBIND11_REBIND_MEMBER_PTR(noexcept);
|
||||
PYBIND11_REBIND_MEMBER_PTR(const noexcept);
|
||||
PYBIND11_REBIND_MEMBER_PTR(& noexcept);
|
||||
PYBIND11_REBIND_MEMBER_PTR(const & noexcept);
|
||||
PYBIND11_REBIND_MEMBER_PTR(&& noexcept);
|
||||
PYBIND11_REBIND_MEMBER_PTR(const && noexcept);
|
||||
#endif
|
||||
#undef PYBIND11_REBIND_MEMBER_PTR
|
||||
// NOLINTEND(bugprone-macro-parentheses)
|
||||
|
||||
/// Shared implementation body for all method_adaptor member-function-pointer overloads.
|
||||
/// Asserts Base is accessible from Derived, then casts the member pointer.
|
||||
template <typename Derived,
|
||||
typename T,
|
||||
typename Traits = rebind_member_ptr<Derived, T>,
|
||||
typename Adapted = typename Traits::type>
|
||||
constexpr PYBIND11_ALWAYS_INLINE Adapted adapt_member_ptr(T pmf) {
|
||||
static_assert(
|
||||
detail::is_accessible_base_of<typename Traits::source_class, Derived>::value,
|
||||
"Cannot bind an inaccessible base class method; use a lambda definition instead");
|
||||
return pmf;
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
/// Given a pointer to a member function, cast it to its `Derived` version.
|
||||
/// Forward everything else unchanged.
|
||||
template <typename /*Derived*/, typename F>
|
||||
auto method_adaptor(F &&f) -> decltype(std::forward<F>(f)) {
|
||||
/// For all other callables (lambdas, function pointers, etc.), forward unchanged.
|
||||
///
|
||||
/// Two overloads cover all cases without explicit per-qualifier instantiations:
|
||||
///
|
||||
/// (1) Generic fallback — disabled for member function pointers so that (2) wins
|
||||
/// without any partial-ordering ambiguity.
|
||||
/// (2) MFP overload — SFINAE on rebind_member_ptr::type, which exists for every
|
||||
/// supported qualifier combination (const, &, &&, noexcept, ...). A single
|
||||
/// template therefore covers all combinations that rebind_member_ptr handles.
|
||||
template <
|
||||
typename /*Derived*/,
|
||||
typename F,
|
||||
detail::enable_if_t<!std::is_member_function_pointer<detail::remove_reference_t<F>>::value,
|
||||
int> = 0>
|
||||
constexpr auto method_adaptor(F &&f) -> decltype(std::forward<F>(f)) {
|
||||
return std::forward<F>(f);
|
||||
}
|
||||
|
||||
template <typename Derived, typename Return, typename Class, typename... Args>
|
||||
auto method_adaptor(Return (Class::*pmf)(Args...)) -> Return (Derived::*)(Args...) {
|
||||
static_assert(
|
||||
detail::is_accessible_base_of<Class, Derived>::value,
|
||||
"Cannot bind an inaccessible base class method; use a lambda definition instead");
|
||||
return pmf;
|
||||
}
|
||||
|
||||
template <typename Derived, typename Return, typename Class, typename... Args>
|
||||
auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)(Args...) const {
|
||||
static_assert(
|
||||
detail::is_accessible_base_of<Class, Derived>::value,
|
||||
"Cannot bind an inaccessible base class method; use a lambda definition instead");
|
||||
return pmf;
|
||||
template <typename Derived,
|
||||
typename T,
|
||||
typename Adapted = typename detail::rebind_member_ptr<Derived, T>::type>
|
||||
constexpr Adapted method_adaptor(T pmf) {
|
||||
// Expected to be redundant (SFINAE on rebind_member_ptr) but cheap and makes the intent
|
||||
// explicit.
|
||||
static_assert(std::is_member_function_pointer<T>::value,
|
||||
"method_adaptor: T must be a member function pointer");
|
||||
return detail::adapt_member_ptr<Derived>(pmf);
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
@@ -2239,6 +2395,13 @@ public:
|
||||
rec.add_base(typeid(Base), [](void *src) -> void * {
|
||||
return static_cast<Base *>(reinterpret_cast<type *>(src));
|
||||
});
|
||||
// Virtual inheritance means the base subobject is at a dynamic offset,
|
||||
// so the reinterpret_cast shortcut in load_impl Case 2a is invalid.
|
||||
// Force the MI path (implicit_casts) for correct pointer adjustment.
|
||||
// Detection: static_cast<Derived*>(Base*) is ill-formed for virtual bases.
|
||||
if PYBIND11_MAYBE_CONSTEXPR (!detail::is_static_downcastable<Base, type>::value) {
|
||||
rec.multiple_inheritance = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Base, detail::enable_if_t<!is_base<Base>::value, int> = 0>
|
||||
@@ -2340,6 +2503,40 @@ public:
|
||||
return def_buffer([func](const type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
|
||||
// Intentionally no &&/const&& overloads: buffer protocol callbacks are invoked on an
|
||||
// existing Python object and should not move-from self.
|
||||
template <typename Return, typename Class, typename... Args>
|
||||
class_ &def_buffer(Return (Class::*func)(Args...) &) {
|
||||
return def_buffer([func](type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
|
||||
template <typename Return, typename Class, typename... Args>
|
||||
class_ &def_buffer(Return (Class::*func)(Args...) const &) {
|
||||
return def_buffer([func](const type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
template <typename Return, typename Class, typename... Args>
|
||||
class_ &def_buffer(Return (Class::*func)(Args...) noexcept) {
|
||||
return def_buffer([func](type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
|
||||
template <typename Return, typename Class, typename... Args>
|
||||
class_ &def_buffer(Return (Class::*func)(Args...) const noexcept) {
|
||||
return def_buffer([func](const type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
|
||||
template <typename Return, typename Class, typename... Args>
|
||||
class_ &def_buffer(Return (Class::*func)(Args...) & noexcept) {
|
||||
return def_buffer([func](type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
|
||||
template <typename Return, typename Class, typename... Args>
|
||||
class_ &def_buffer(Return (Class::*func)(Args...) const & noexcept) {
|
||||
return def_buffer([func](const type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename C, typename D, typename... Extra>
|
||||
class_ &def_readwrite(const char *name, D C::*pm, const Extra &...extra) {
|
||||
static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,
|
||||
@@ -2467,19 +2664,41 @@ public:
|
||||
if (rec_fget) {
|
||||
char *doc_prev = rec_fget->doc; /* 'extra' field may include a property-specific
|
||||
documentation string */
|
||||
auto args_before = rec_fget->args.size();
|
||||
detail::process_attributes<Extra...>::init(extra..., rec_fget);
|
||||
if (rec_fget->doc && rec_fget->doc != doc_prev) {
|
||||
std::free(doc_prev);
|
||||
rec_fget->doc = PYBIND11_COMPAT_STRDUP(rec_fget->doc);
|
||||
}
|
||||
// Args added by process_attributes (e.g. "self" via is_method + pos_only/kw_only)
|
||||
// need their strings strdup'd: initialize_generic's strdup loop already ran during
|
||||
// cpp_function construction, so it won't process these late additions. Without this,
|
||||
// destruct() would call free() on string literals. See gh-5976.
|
||||
for (auto i = args_before; i < rec_fget->args.size(); ++i) {
|
||||
if (rec_fget->args[i].name) {
|
||||
rec_fget->args[i].name = PYBIND11_COMPAT_STRDUP(rec_fget->args[i].name);
|
||||
}
|
||||
if (rec_fget->args[i].descr) {
|
||||
rec_fget->args[i].descr = PYBIND11_COMPAT_STRDUP(rec_fget->args[i].descr);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rec_fset) {
|
||||
char *doc_prev = rec_fset->doc;
|
||||
auto args_before = rec_fset->args.size();
|
||||
detail::process_attributes<Extra...>::init(extra..., rec_fset);
|
||||
if (rec_fset->doc && rec_fset->doc != doc_prev) {
|
||||
std::free(doc_prev);
|
||||
rec_fset->doc = PYBIND11_COMPAT_STRDUP(rec_fset->doc);
|
||||
}
|
||||
for (auto i = args_before; i < rec_fset->args.size(); ++i) {
|
||||
if (rec_fset->args[i].name) {
|
||||
rec_fset->args[i].name = PYBIND11_COMPAT_STRDUP(rec_fset->args[i].name);
|
||||
}
|
||||
if (rec_fset->args[i].descr) {
|
||||
rec_fset->args[i].descr = PYBIND11_COMPAT_STRDUP(rec_fset->args[i].descr);
|
||||
}
|
||||
}
|
||||
if (!rec_active) {
|
||||
rec_active = rec_fset;
|
||||
}
|
||||
@@ -3299,13 +3518,10 @@ typing::Iterator<ValueType> make_value_iterator(Type &value, Extra &&...extra) {
|
||||
|
||||
template <typename InputType, typename OutputType>
|
||||
void implicitly_convertible() {
|
||||
static int tss_sentinel_pointee = 1; // arbitrary value
|
||||
struct set_flag {
|
||||
thread_specific_storage<int> &flag;
|
||||
explicit set_flag(thread_specific_storage<int> &flag_) : flag(flag_) {
|
||||
flag = &tss_sentinel_pointee; // trick: the pointer itself is the sentinel
|
||||
}
|
||||
~set_flag() { flag.reset(nullptr); }
|
||||
bool &flag;
|
||||
explicit set_flag(bool &flag_) : flag(flag_) { flag_ = true; }
|
||||
~set_flag() { flag = false; }
|
||||
|
||||
// Prevent copying/moving to ensure RAII guard is used safely
|
||||
set_flag(const set_flag &) = delete;
|
||||
@@ -3314,7 +3530,7 @@ void implicitly_convertible() {
|
||||
set_flag &operator=(set_flag &&) = delete;
|
||||
};
|
||||
auto implicit_caster = [](PyObject *obj, PyTypeObject *type) -> PyObject * {
|
||||
static thread_specific_storage<int> currently_used;
|
||||
thread_local bool currently_used = false;
|
||||
if (currently_used) { // implicit conversions are non-reentrant
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -259,8 +259,7 @@ public:
|
||||
detail::enable_if_t<detail::all_of<detail::none_of<std::is_base_of<handle, T>,
|
||||
detail::is_pyobj_ptr_or_nullptr_t<T>>,
|
||||
std::is_convertible<T, PyObject *>>::value,
|
||||
int>
|
||||
= 0>
|
||||
int> = 0>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
handle(T &obj) : m_ptr(obj) {}
|
||||
|
||||
@@ -1694,8 +1693,7 @@ public:
|
||||
template <typename T,
|
||||
detail::enable_if_t<!std::is_base_of<object, detail::remove_cvref_t<T>>::value
|
||||
&& std::is_constructible<handle, T>::value,
|
||||
int>
|
||||
= 0>
|
||||
int> = 0>
|
||||
explicit str(T &&h) : object(raw_str(handle(std::forward<T>(h)).ptr()), stolen_t{}) {
|
||||
if (!m_ptr) {
|
||||
throw error_already_set();
|
||||
|
||||
@@ -57,9 +57,11 @@ pybind11 = "pybind11.share.pkgconfig"
|
||||
test = [
|
||||
"pytest",
|
||||
"build",
|
||||
"tomlkit",
|
||||
]
|
||||
dev = [{ include-group = "test" }]
|
||||
dev = [
|
||||
"tomlkit",
|
||||
{ include-group = "test" }
|
||||
]
|
||||
|
||||
|
||||
[tool.scikit-build]
|
||||
|
||||
@@ -172,6 +172,7 @@ set(PYBIND11_TEST_FILES
|
||||
test_scoped_critical_section
|
||||
test_sequences_and_iterators
|
||||
test_smart_ptr
|
||||
test_standalone_enum_module.py
|
||||
test_stl
|
||||
test_stl_binders
|
||||
test_tagbased_polymorphic
|
||||
@@ -249,6 +250,7 @@ tests_extra_targets("test_exceptions.py" "cross_module_interleaved_error_already
|
||||
tests_extra_targets("test_gil_scoped.py" "cross_module_gil_utils")
|
||||
tests_extra_targets("test_cpp_conduit.py"
|
||||
"exo_planet_pybind11;exo_planet_c_api;home_planet_very_lonely_traveler")
|
||||
tests_extra_targets("test_standalone_enum_module.py" "standalone_enum_module")
|
||||
|
||||
set(PYBIND11_EIGEN_REPO
|
||||
"https://gitlab.com/libeigen/eigen.git"
|
||||
|
||||
@@ -4,6 +4,8 @@ import platform
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
import pytest
|
||||
|
||||
ANDROID = sys.platform.startswith("android")
|
||||
IOS = sys.platform.startswith("ios")
|
||||
LINUX = sys.platform.startswith("linux")
|
||||
@@ -50,6 +52,9 @@ def check_script_success_in_subprocess(code: str, *, rerun: int = 8) -> None:
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
if ANDROID or IOS or sys.platform.startswith("emscripten"):
|
||||
pytest.skip("Requires subprocess support")
|
||||
|
||||
code = textwrap.dedent(code).strip()
|
||||
try:
|
||||
for _ in range(rerun): # run flakily failing test multiple times
|
||||
|
||||
@@ -96,6 +96,12 @@ PYBIND11_MODULE(pybind11_tests, m, py::mod_gil_not_used()) {
|
||||
#else
|
||||
false;
|
||||
#endif
|
||||
m.attr("PYBIND11_TEST_SMART_HOLDER") =
|
||||
#if defined(PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE)
|
||||
true;
|
||||
#else
|
||||
false;
|
||||
#endif
|
||||
|
||||
bind_ConstructorStats(m);
|
||||
|
||||
@@ -105,6 +111,12 @@ PYBIND11_MODULE(pybind11_tests, m, py::mod_gil_not_used()) {
|
||||
m.attr("detailed_error_messages_enabled") = false;
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_noexcept_function_type)
|
||||
m.attr("defined___cpp_noexcept_function_type") = true;
|
||||
#else
|
||||
m.attr("defined___cpp_noexcept_function_type") = false;
|
||||
#endif
|
||||
|
||||
py::class_<UserType>(m, "UserType", "A `py::class_` type for testing")
|
||||
.def(py::init<>())
|
||||
.def(py::init<int>())
|
||||
|
||||
13
tests/standalone_enum_module.cpp
Normal file
13
tests/standalone_enum_module.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2026 The pybind Community.
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
namespace standalone_enum_module_ns {
|
||||
enum SomeEnum {};
|
||||
} // namespace standalone_enum_module_ns
|
||||
|
||||
using namespace standalone_enum_module_ns;
|
||||
|
||||
PYBIND11_MODULE(standalone_enum_module, m) { // Added in PR #6015
|
||||
pybind11::enum_<SomeEnum> some_enum_wrapper(m, "SomeEnum");
|
||||
}
|
||||
@@ -439,4 +439,133 @@ TEST_SUBMODULE(buffers, m) {
|
||||
PyBuffer_Release(&buffer);
|
||||
return result;
|
||||
});
|
||||
|
||||
// test_noexcept_def_buffer (issue #2234)
|
||||
// def_buffer(Return (Class::*)(Args...) noexcept) and
|
||||
// def_buffer(Return (Class::*)(Args...) const noexcept) must compile and work correctly.
|
||||
struct OneDBuffer {
|
||||
// Declare m_data before m_n to match initialiser list order below.
|
||||
float *m_data;
|
||||
py::ssize_t m_n;
|
||||
explicit OneDBuffer(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
|
||||
~OneDBuffer() { delete[] m_data; }
|
||||
// Exercises def_buffer(Return (Class::*)(Args...) noexcept)
|
||||
py::buffer_info get_buffer() noexcept {
|
||||
return py::buffer_info(m_data,
|
||||
sizeof(float),
|
||||
py::format_descriptor<float>::format(),
|
||||
1,
|
||||
{m_n},
|
||||
{(py::ssize_t) sizeof(float)});
|
||||
}
|
||||
};
|
||||
|
||||
// non-const noexcept member function form
|
||||
py::class_<OneDBuffer>(m, "OneDBuffer", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def_buffer(&OneDBuffer::get_buffer);
|
||||
|
||||
// const noexcept member function form (separate class to avoid ambiguity)
|
||||
struct OneDBufferConst {
|
||||
float *m_data;
|
||||
py::ssize_t m_n;
|
||||
explicit OneDBufferConst(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
|
||||
~OneDBufferConst() { delete[] m_data; }
|
||||
// Exercises def_buffer(Return (Class::*)(Args...) const noexcept)
|
||||
py::buffer_info get_buffer() const noexcept {
|
||||
return py::buffer_info(m_data,
|
||||
sizeof(float),
|
||||
py::format_descriptor<float>::format(),
|
||||
1,
|
||||
{m_n},
|
||||
{(py::ssize_t) sizeof(float)},
|
||||
/*readonly=*/true);
|
||||
}
|
||||
};
|
||||
py::class_<OneDBufferConst>(m, "OneDBufferConst", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def_buffer(&OneDBufferConst::get_buffer);
|
||||
|
||||
// test_ref_qualified_def_buffer
|
||||
struct OneDBufferLRef {
|
||||
float *m_data;
|
||||
py::ssize_t m_n;
|
||||
explicit OneDBufferLRef(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
|
||||
~OneDBufferLRef() { delete[] m_data; }
|
||||
// Exercises def_buffer(Return (Class::*)(Args...) &)
|
||||
py::buffer_info get_buffer() & {
|
||||
return py::buffer_info(m_data,
|
||||
sizeof(float),
|
||||
py::format_descriptor<float>::format(),
|
||||
1,
|
||||
{m_n},
|
||||
{(py::ssize_t) sizeof(float)});
|
||||
}
|
||||
};
|
||||
py::class_<OneDBufferLRef>(m, "OneDBufferLRef", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def_buffer(&OneDBufferLRef::get_buffer);
|
||||
|
||||
struct OneDBufferConstLRef {
|
||||
float *m_data;
|
||||
py::ssize_t m_n;
|
||||
explicit OneDBufferConstLRef(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
|
||||
~OneDBufferConstLRef() { delete[] m_data; }
|
||||
// Exercises def_buffer(Return (Class::*)(Args...) const &)
|
||||
py::buffer_info get_buffer() const & {
|
||||
return py::buffer_info(m_data,
|
||||
sizeof(float),
|
||||
py::format_descriptor<float>::format(),
|
||||
1,
|
||||
{m_n},
|
||||
{(py::ssize_t) sizeof(float)},
|
||||
/*readonly=*/true);
|
||||
}
|
||||
};
|
||||
py::class_<OneDBufferConstLRef>(m, "OneDBufferConstLRef", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def_buffer(&OneDBufferConstLRef::get_buffer);
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
struct OneDBufferLRefNoexcept {
|
||||
float *m_data;
|
||||
py::ssize_t m_n;
|
||||
explicit OneDBufferLRefNoexcept(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
|
||||
~OneDBufferLRefNoexcept() { delete[] m_data; }
|
||||
// Exercises def_buffer(Return (Class::*)(Args...) & noexcept)
|
||||
py::buffer_info get_buffer() & noexcept {
|
||||
return py::buffer_info(m_data,
|
||||
sizeof(float),
|
||||
py::format_descriptor<float>::format(),
|
||||
1,
|
||||
{m_n},
|
||||
{(py::ssize_t) sizeof(float)});
|
||||
}
|
||||
};
|
||||
py::class_<OneDBufferLRefNoexcept>(m, "OneDBufferLRefNoexcept", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def_buffer(&OneDBufferLRefNoexcept::get_buffer);
|
||||
|
||||
struct OneDBufferConstLRefNoexcept {
|
||||
float *m_data;
|
||||
py::ssize_t m_n;
|
||||
explicit OneDBufferConstLRefNoexcept(py::ssize_t n)
|
||||
: m_data(new float[(size_t) n]()), m_n(n) {}
|
||||
~OneDBufferConstLRefNoexcept() { delete[] m_data; }
|
||||
// Exercises def_buffer(Return (Class::*)(Args...) const & noexcept)
|
||||
py::buffer_info get_buffer() const & noexcept {
|
||||
return py::buffer_info(m_data,
|
||||
sizeof(float),
|
||||
py::format_descriptor<float>::format(),
|
||||
1,
|
||||
{m_n},
|
||||
{(py::ssize_t) sizeof(float)},
|
||||
/*readonly=*/true);
|
||||
}
|
||||
};
|
||||
py::class_<OneDBufferConstLRefNoexcept>(
|
||||
m, "OneDBufferConstLRefNoexcept", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def_buffer(&OneDBufferConstLRefNoexcept::get_buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import struct
|
||||
import pytest
|
||||
|
||||
import env
|
||||
from pybind11_tests import ConstructorStats
|
||||
from pybind11_tests import ConstructorStats, defined___cpp_noexcept_function_type
|
||||
from pybind11_tests import buffers as m
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
@@ -399,3 +399,73 @@ def test_to_pybuffer_contiguity(type):
|
||||
m.get_py_buffer(dmat, m.PyBUF_ANY_CONTIGUOUS)
|
||||
with pytest.raises(expected_exception):
|
||||
m.get_py_buffer(dmat, m.PyBUF_F_CONTIGUOUS)
|
||||
|
||||
|
||||
def test_noexcept_def_buffer():
|
||||
"""Test issue #2234: def_buffer with noexcept member function pointers.
|
||||
|
||||
Covers both new def_buffer specialisations:
|
||||
- def_buffer(Return (Class::*)(Args...) noexcept)
|
||||
- def_buffer(Return (Class::*)(Args...) const noexcept)
|
||||
"""
|
||||
# non-const noexcept member function form
|
||||
buf = m.OneDBuffer(5)
|
||||
arr = np.frombuffer(buf, dtype=np.float32)
|
||||
assert arr.shape == (5,)
|
||||
arr[2] = 3.14
|
||||
arr2 = np.frombuffer(buf, dtype=np.float32)
|
||||
assert arr2[2] == pytest.approx(3.14)
|
||||
|
||||
# const noexcept member function form
|
||||
cbuf = m.OneDBufferConst(4)
|
||||
carr = np.frombuffer(cbuf, dtype=np.float32)
|
||||
assert carr.shape == (4,)
|
||||
assert carr.flags["WRITEABLE"] is False
|
||||
|
||||
|
||||
def test_ref_qualified_def_buffer():
|
||||
"""Test issue #2234 follow-up: def_buffer with ref-qualified member function pointers.
|
||||
|
||||
Covers:
|
||||
- def_buffer(Return (Class::*)(Args...) &)
|
||||
- def_buffer(Return (Class::*)(Args...) const &)
|
||||
- def_buffer(Return (Class::*)(Args...) & noexcept)
|
||||
- def_buffer(Return (Class::*)(Args...) const & noexcept)
|
||||
"""
|
||||
# non-const lvalue ref-qualified member function form
|
||||
buf = m.OneDBufferLRef(5)
|
||||
arr = np.frombuffer(buf, dtype=np.float32)
|
||||
assert arr.shape == (5,)
|
||||
arr[1] = 2.5
|
||||
arr2 = np.frombuffer(buf, dtype=np.float32)
|
||||
assert arr2[1] == pytest.approx(2.5)
|
||||
|
||||
# const lvalue ref-qualified member function form
|
||||
cbuf = m.OneDBufferConstLRef(4)
|
||||
carr = np.frombuffer(cbuf, dtype=np.float32)
|
||||
assert carr.shape == (4,)
|
||||
assert carr.flags["WRITEABLE"] is False
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not defined___cpp_noexcept_function_type,
|
||||
reason="Requires __cpp_noexcept_function_type",
|
||||
)
|
||||
def test_ref_qualified_noexcept_def_buffer():
|
||||
"""Test issue #2234 follow-up: def_buffer with noexcept ref-qualified member pointers.
|
||||
|
||||
Covers:
|
||||
- def_buffer(Return (Class::*)(Args...) & noexcept)
|
||||
- def_buffer(Return (Class::*)(Args...) const & noexcept)
|
||||
"""
|
||||
nbuf = m.OneDBufferLRefNoexcept(3)
|
||||
narr = np.frombuffer(nbuf, dtype=np.float32)
|
||||
assert narr.shape == (3,)
|
||||
narr[2] = 7.0
|
||||
narr2 = np.frombuffer(nbuf, dtype=np.float32)
|
||||
assert narr2[2] == pytest.approx(7.0)
|
||||
|
||||
ncbuf = m.OneDBufferConstLRefNoexcept(2)
|
||||
ncarr = np.frombuffer(ncbuf, dtype=np.float32)
|
||||
assert ncarr.shape == (2,)
|
||||
assert ncarr.flags["WRITEABLE"] is False
|
||||
|
||||
@@ -179,10 +179,11 @@ def test_async_callbacks():
|
||||
# do some work async
|
||||
work = [1, 2, 3, 4]
|
||||
m.test_async_callback(gen_f(), work)
|
||||
# wait until work is done
|
||||
from time import sleep
|
||||
|
||||
sleep(0.5)
|
||||
# Wait for all detached worker threads to finish.
|
||||
deadline = time.monotonic() + 5.0
|
||||
while len(res) < len(work) and time.monotonic() < deadline:
|
||||
time.sleep(0.01)
|
||||
assert len(res) == len(work), f"Timed out waiting for callbacks: res={res!r}"
|
||||
assert sum(res) == sum(x + 3 for x in work)
|
||||
|
||||
|
||||
|
||||
@@ -43,6 +43,24 @@ struct WithConstCharPtrMember {
|
||||
const char *const_char_ptr_member = "ConstChar*";
|
||||
};
|
||||
|
||||
// See PR #6008
|
||||
enum class EnumAB {
|
||||
A = 0,
|
||||
B = 1,
|
||||
};
|
||||
|
||||
struct ShWithEnumABMember {
|
||||
EnumAB level = EnumAB::A;
|
||||
};
|
||||
|
||||
struct SimpleStruct {
|
||||
int value = 7;
|
||||
};
|
||||
|
||||
struct ShWithSimpleStructMember {
|
||||
SimpleStruct legacy;
|
||||
};
|
||||
|
||||
} // namespace test_class_sh_property
|
||||
|
||||
TEST_SUBMODULE(class_sh_property, m) {
|
||||
@@ -91,4 +109,21 @@ TEST_SUBMODULE(class_sh_property, m) {
|
||||
py::classh<WithConstCharPtrMember>(m, "WithConstCharPtrMember")
|
||||
.def(py::init<>())
|
||||
.def_readonly("const_char_ptr_member", &WithConstCharPtrMember::const_char_ptr_member);
|
||||
|
||||
// See PR #6008
|
||||
py::enum_<EnumAB>(m, "EnumAB").value("A", EnumAB::A).value("B", EnumAB::B);
|
||||
|
||||
py::classh<ShWithEnumABMember>(m, "ShWithEnumABMember")
|
||||
.def(py::init<>())
|
||||
.def_readwrite("level", &ShWithEnumABMember::level);
|
||||
|
||||
py::class_<SimpleStruct>(m, "SimpleStruct")
|
||||
.def(py::init<>())
|
||||
.def_readwrite("value", &SimpleStruct::value);
|
||||
|
||||
py::classh<ShWithSimpleStructMember>(m, "ShWithSimpleStructMember")
|
||||
.def(py::init<>())
|
||||
.def_readwrite("legacy", &ShWithSimpleStructMember::legacy);
|
||||
|
||||
m.def("getSimpleStructAsShared", []() { return std::make_shared<SimpleStruct>(); });
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import pytest
|
||||
|
||||
import env # noqa: F401
|
||||
import pybind11_tests
|
||||
from pybind11_tests import class_sh_property as m
|
||||
|
||||
|
||||
@@ -164,3 +165,42 @@ def test_readonly_char6_member():
|
||||
def test_readonly_const_char_ptr_member():
|
||||
obj = m.WithConstCharPtrMember()
|
||||
assert obj.const_char_ptr_member == "ConstChar*"
|
||||
|
||||
|
||||
# See PR #6008
|
||||
def test_enum_member_with_smart_holder_def_readwrite():
|
||||
obj = m.ShWithEnumABMember()
|
||||
assert obj.level == m.EnumAB.A
|
||||
for _ in range(100):
|
||||
v = obj.level
|
||||
assert v == m.EnumAB.A
|
||||
del v
|
||||
|
||||
|
||||
# See PR #6008
|
||||
def test_non_smart_holder_member_type_with_smart_holder_owner():
|
||||
obj = m.ShWithSimpleStructMember()
|
||||
for _ in range(1000):
|
||||
v = obj.legacy
|
||||
assert v.value == 7
|
||||
del v
|
||||
|
||||
|
||||
# See PR #6008, previously this was UB
|
||||
@pytest.mark.skipif(
|
||||
pybind11_tests.PYBIND11_TEST_SMART_HOLDER,
|
||||
reason="PYBIND11_TEST_SMART_HOLDER changes the default holder",
|
||||
)
|
||||
def test_shared_ptr_return_for_unique_ptr_holder():
|
||||
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",
|
||||
):
|
||||
m.getSimpleStructAsShared()
|
||||
|
||||
|
||||
def test_non_smart_holder_member_type_with_smart_holder_owner_aliases_member():
|
||||
obj = m.ShWithSimpleStructMember()
|
||||
legacy = obj.legacy
|
||||
legacy.value = 13
|
||||
assert obj.legacy.value == 13
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import gc
|
||||
import os
|
||||
import sys
|
||||
import weakref
|
||||
|
||||
import pytest
|
||||
@@ -52,10 +51,6 @@ def test_indirect_cycle(gc_tester):
|
||||
gc_tester(obj)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
env.IOS or sys.platform.startswith("emscripten"),
|
||||
reason="Requires subprocess support",
|
||||
)
|
||||
@pytest.mark.skipif("env.PYPY or env.GRAALPY")
|
||||
def test_py_cast_useable_on_shutdown():
|
||||
"""Test that py::cast works during interpreter shutdown.
|
||||
|
||||
@@ -168,6 +168,9 @@ ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK = (*ALL_BASIC_TESTS, _intentional_dead
|
||||
|
||||
|
||||
def _run_in_process(target, *args, **kwargs):
|
||||
if env.ANDROID or env.IOS or sys.platform.startswith("emscripten"):
|
||||
pytest.skip("Requires subprocess support")
|
||||
|
||||
test_fn = target if len(args) == 0 else args[0]
|
||||
# Do not need to wait much, 10s should be more than enough.
|
||||
timeout = 0.1 if test_fn is _intentional_deadlock else 10
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
#include "constructor_stats.h"
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#if !defined(PYPY_VERSION)
|
||||
// Flag set by the capsule destructor in test_dynamic_attr_dealloc_frees_dict_contents.
|
||||
// File scope so the captureless capsule destructor (void(*)(void*)) can access it.
|
||||
static bool s_dynamic_attr_capsule_freed = false;
|
||||
#endif
|
||||
|
||||
#if !defined(PYBIND11_OVERLOAD_CAST)
|
||||
template <typename... Args>
|
||||
using overload_cast_ = pybind11::detail::overload_cast_impl<Args...>;
|
||||
@@ -161,6 +167,123 @@ public:
|
||||
double sum() const { return rw_value + ro_value; }
|
||||
};
|
||||
|
||||
// Issue #2234: noexcept methods in an unregistered base should be bindable on the derived class.
|
||||
// In C++17, noexcept is part of the function type, so &Derived::method resolves to
|
||||
// a Base member function pointer with noexcept, requiring explicit template specializations.
|
||||
class NoexceptUnregisteredBase {
|
||||
public:
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) const noexcept, ...)
|
||||
int value() const noexcept { return m_value; }
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) noexcept, ...)
|
||||
void set_value(int v) noexcept { m_value = v; }
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) & noexcept, ...)
|
||||
void increment() & noexcept { ++m_value; }
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) const & noexcept, ...)
|
||||
int capped_value() const & noexcept { return m_value < 100 ? m_value : 100; }
|
||||
|
||||
private:
|
||||
int m_value = 99;
|
||||
};
|
||||
class NoexceptDerived : public NoexceptUnregisteredBase {
|
||||
public:
|
||||
using NoexceptUnregisteredBase::NoexceptUnregisteredBase;
|
||||
};
|
||||
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) &&, ...) and
|
||||
// cpp_function(Return (Class::*)(Args...) const &&, ...) via an unregistered base.
|
||||
class RValueRefUnregisteredBase {
|
||||
public:
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) &&, ...).
|
||||
// Moves m_payload to verify that std::move(*c).*f is used in the lambda body.
|
||||
std::string take() && { // NOLINT(readability-make-member-function-const)
|
||||
return std::move(m_payload);
|
||||
}
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) const &&, ...)
|
||||
int peek() const && { return m_value; }
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) && noexcept, ...)
|
||||
std::string take_noexcept() && noexcept { // NOLINT(readability-make-member-function-const)
|
||||
return std::move(m_payload);
|
||||
}
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) const && noexcept, ...)
|
||||
int peek_noexcept() const && noexcept { return m_value; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
int m_value = 77;
|
||||
std::string m_payload{"rref_payload"};
|
||||
};
|
||||
class RValueRefDerived : public RValueRefUnregisteredBase {
|
||||
public:
|
||||
using RValueRefUnregisteredBase::RValueRefUnregisteredBase;
|
||||
};
|
||||
|
||||
// Exercises overload_cast with noexcept member function pointers (issue #2234).
|
||||
// In C++17, overload_cast must have noexcept variants to resolve noexcept overloads.
|
||||
struct NoexceptOverloaded {
|
||||
py::str method(int) noexcept { return "(int)"; }
|
||||
py::str method(int) const noexcept { return "(int) const"; }
|
||||
py::str method(float) noexcept { return "(float)"; }
|
||||
};
|
||||
// Exercises overload_cast with noexcept free function pointers.
|
||||
int noexcept_free_func(int x) noexcept { return x + 1; }
|
||||
int noexcept_free_func(float x) noexcept { return static_cast<int>(x) + 2; }
|
||||
|
||||
// Exercises overload_cast with ref-qualified member function pointers.
|
||||
struct RefQualifiedOverloaded {
|
||||
py::str method(int) & { return "(int) &"; }
|
||||
py::str method(int) const & { return "(int) const &"; }
|
||||
py::str method(float) && { return "(float) &&"; }
|
||||
py::str method(float) const && { return "(float) const &&"; }
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
py::str method(long) & noexcept { return "(long) & noexcept"; }
|
||||
py::str method(long) const & noexcept { return "(long) const & noexcept"; }
|
||||
py::str method(double) && noexcept { return "(double) && noexcept"; }
|
||||
py::str method(double) const && noexcept { return "(double) const && noexcept"; }
|
||||
#endif
|
||||
};
|
||||
|
||||
// Compile-only guard: catch overload_cast resolution regressions/ambiguities early.
|
||||
using RefQualifiedOverloadedIntCast = py::detail::overload_cast_impl<int>;
|
||||
using RefQualifiedOverloadedFloatCast = py::detail::overload_cast_impl<float>;
|
||||
static_assert(
|
||||
std::is_same<decltype(RefQualifiedOverloadedIntCast{}(&RefQualifiedOverloaded::method)),
|
||||
py::str (RefQualifiedOverloaded::*)(int) &>::value,
|
||||
"");
|
||||
static_assert(std::is_same<decltype(RefQualifiedOverloadedIntCast{}(
|
||||
&RefQualifiedOverloaded::method, py::const_)),
|
||||
py::str (RefQualifiedOverloaded::*)(int) const &>::value,
|
||||
"");
|
||||
static_assert(
|
||||
std::is_same<decltype(RefQualifiedOverloadedFloatCast{}(&RefQualifiedOverloaded::method)),
|
||||
py::str (RefQualifiedOverloaded::*)(float) &&>::value,
|
||||
"");
|
||||
static_assert(std::is_same<decltype(RefQualifiedOverloadedFloatCast{}(
|
||||
&RefQualifiedOverloaded::method, py::const_)),
|
||||
py::str (RefQualifiedOverloaded::*)(float) const &&>::value,
|
||||
"");
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
using RefQualifiedOverloadedLongCast = py::detail::overload_cast_impl<long>;
|
||||
using RefQualifiedOverloadedDoubleCast = py::detail::overload_cast_impl<double>;
|
||||
static_assert(
|
||||
std::is_same<decltype(RefQualifiedOverloadedLongCast{}(&RefQualifiedOverloaded::method)),
|
||||
py::str (RefQualifiedOverloaded::*)(long) & noexcept>::value,
|
||||
"");
|
||||
static_assert(std::is_same<decltype(RefQualifiedOverloadedLongCast{}(
|
||||
&RefQualifiedOverloaded::method, py::const_)),
|
||||
py::str (RefQualifiedOverloaded::*)(long) const & noexcept>::value,
|
||||
"");
|
||||
static_assert(
|
||||
std::is_same<decltype(RefQualifiedOverloadedDoubleCast{}(&RefQualifiedOverloaded::method)),
|
||||
py::str (RefQualifiedOverloaded::*)(double) && noexcept>::value,
|
||||
"");
|
||||
static_assert(std::is_same<decltype(RefQualifiedOverloadedDoubleCast{}(
|
||||
&RefQualifiedOverloaded::method, py::const_)),
|
||||
py::str (RefQualifiedOverloaded::*)(double) const && noexcept>::value,
|
||||
"");
|
||||
#endif
|
||||
|
||||
// Test explicit lvalue ref-qualification
|
||||
struct RefQualified {
|
||||
int value = 0;
|
||||
@@ -388,6 +511,24 @@ TEST_SUBMODULE(methods_and_attributes, m) {
|
||||
|
||||
class CppDerivedDynamicClass : public DynamicClass {};
|
||||
py::class_<CppDerivedDynamicClass, DynamicClass>(m, "CppDerivedDynamicClass").def(py::init());
|
||||
|
||||
// test_dynamic_attr_dealloc_frees_dict_contents
|
||||
// Regression test: pybind11_object_dealloc() must call PyObject_ClearManagedDict()
|
||||
// before tp_free() so that objects stored in a py::dynamic_attr() instance __dict__
|
||||
// have their refcounts decremented when the pybind11 object is freed. On Python 3.14+
|
||||
// tp_free no longer implicitly clears the managed dict, causing permanent leaks.
|
||||
m.def("make_dynamic_attr_with_capsule", []() -> py::object {
|
||||
s_dynamic_attr_capsule_freed = false;
|
||||
auto *dummy = new int(0);
|
||||
py::capsule cap(dummy, [](void *ptr) {
|
||||
delete static_cast<int *>(ptr);
|
||||
s_dynamic_attr_capsule_freed = true;
|
||||
});
|
||||
py::object obj = py::cast(new DynamicClass(), py::return_value_policy::take_ownership);
|
||||
obj.attr("data") = cap;
|
||||
return obj;
|
||||
});
|
||||
m.def("is_dynamic_attr_capsule_freed", []() { return s_dynamic_attr_capsule_freed; });
|
||||
#endif
|
||||
|
||||
// test_bad_arg_default
|
||||
@@ -474,6 +615,161 @@ TEST_SUBMODULE(methods_and_attributes, m) {
|
||||
= decltype(py::method_adaptor<RegisteredDerived>(&RegisteredDerived::do_nothing));
|
||||
static_assert(std::is_same<Adapted, void (RegisteredDerived::*)() const>::value, "");
|
||||
|
||||
// test_noexcept_base (issue #2234)
|
||||
// In C++17, noexcept is part of the function type. Binding a noexcept method from an
|
||||
// unregistered base class must resolve `self` to the derived type, not the base type.
|
||||
py::class_<NoexceptDerived>(m, "NoexceptDerived")
|
||||
.def(py::init<>())
|
||||
// cpp_function(Return (Class::*)(Args...) const noexcept, ...)
|
||||
.def("value", &NoexceptDerived::value)
|
||||
// cpp_function(Return (Class::*)(Args...) noexcept, ...)
|
||||
.def("set_value", &NoexceptDerived::set_value)
|
||||
// cpp_function(Return (Class::*)(Args...) & noexcept, ...)
|
||||
.def("increment", &NoexceptDerived::increment)
|
||||
// cpp_function(Return (Class::*)(Args...) const & noexcept, ...)
|
||||
.def("capped_value", &NoexceptDerived::capped_value);
|
||||
|
||||
// test_rvalue_ref_qualified_methods: rvalue-ref-qualified methods from an unregistered base.
|
||||
// method_adaptor must rebind &&/const&& member pointers to the derived type.
|
||||
py::class_<RValueRefDerived>(m, "RValueRefDerived")
|
||||
.def(py::init<>())
|
||||
// cpp_function(Return (Class::*)(Args...) &&, ...)
|
||||
.def("take", &RValueRefDerived::take)
|
||||
// cpp_function(Return (Class::*)(Args...) const &&, ...)
|
||||
.def("peek", &RValueRefDerived::peek)
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
// cpp_function(Return (Class::*)(Args...) && noexcept, ...)
|
||||
.def("take_noexcept", &RValueRefDerived::take_noexcept)
|
||||
// cpp_function(Return (Class::*)(Args...) const && noexcept, ...)
|
||||
.def("peek_noexcept", &RValueRefDerived::peek_noexcept)
|
||||
#endif
|
||||
;
|
||||
|
||||
// Verify that &&-qualified methods cannot be called on lvalues, only on rvalues.
|
||||
// This confirms that the cpp_function lambda must use std::move(*c).*f, not c->*f.
|
||||
#if __cplusplus >= 201703L
|
||||
static_assert(!std::is_invocable<std::string (RValueRefUnregisteredBase::*)() &&,
|
||||
RValueRefUnregisteredBase &>::value,
|
||||
"&&-qualified method must not be callable on lvalue");
|
||||
static_assert(std::is_invocable<std::string (RValueRefUnregisteredBase::*)() &&,
|
||||
RValueRefUnregisteredBase &&>::value,
|
||||
"&&-qualified method must be callable on rvalue");
|
||||
#endif
|
||||
|
||||
// Verify method_adaptor preserves &&/const&& qualifiers when rebinding.
|
||||
using AdaptedRRef = decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::take));
|
||||
static_assert(std::is_same<AdaptedRRef, std::string (RValueRefDerived::*)() &&>::value, "");
|
||||
using AdaptedConstRRef
|
||||
= decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::peek));
|
||||
static_assert(std::is_same<AdaptedConstRRef, int (RValueRefDerived::*)() const &&>::value, "");
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
// method_adaptor must also handle noexcept member function pointers (issue #2234).
|
||||
// Verify the noexcept specifier is preserved in the resulting Derived pointer type.
|
||||
using AdaptedConstNoexcept
|
||||
= decltype(py::method_adaptor<NoexceptDerived>(&NoexceptDerived::value));
|
||||
static_assert(
|
||||
std::is_same<AdaptedConstNoexcept, int (NoexceptDerived::*)() const noexcept>::value, "");
|
||||
using AdaptedNoexcept
|
||||
= decltype(py::method_adaptor<NoexceptDerived>(&NoexceptDerived::set_value));
|
||||
static_assert(std::is_same<AdaptedNoexcept, void (NoexceptDerived::*)(int) noexcept>::value,
|
||||
"");
|
||||
using AdaptedRRefNoexcept
|
||||
= decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::take_noexcept));
|
||||
static_assert(std::is_same < AdaptedRRefNoexcept,
|
||||
std::string (RValueRefDerived::*)() && noexcept > ::value,
|
||||
"");
|
||||
using AdaptedConstRRefNoexcept
|
||||
= decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::peek_noexcept));
|
||||
static_assert(std::is_same < AdaptedConstRRefNoexcept,
|
||||
int (RValueRefDerived::*)() const && noexcept > ::value,
|
||||
"");
|
||||
#endif
|
||||
|
||||
// test_noexcept_overload_cast (issue #2234)
|
||||
// overload_cast must have noexcept operator() overloads so it can resolve noexcept methods.
|
||||
#ifdef PYBIND11_OVERLOAD_CAST
|
||||
py::class_<NoexceptOverloaded>(m, "NoexceptOverloaded")
|
||||
.def(py::init<>())
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type)
|
||||
.def("method", py::overload_cast<int>(&NoexceptOverloaded::method))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) const noexcept, true_type)
|
||||
.def("method_const", py::overload_cast<int>(&NoexceptOverloaded::method, py::const_))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type) float
|
||||
.def("method_float", py::overload_cast<float>(&NoexceptOverloaded::method));
|
||||
// overload_cast_impl::operator()(Return (*)(Args...) noexcept)
|
||||
m.def("noexcept_free_func", py::overload_cast<int>(noexcept_free_func));
|
||||
m.def("noexcept_free_func_float", py::overload_cast<float>(noexcept_free_func));
|
||||
|
||||
py::class_<RefQualifiedOverloaded>(m, "RefQualifiedOverloaded")
|
||||
.def(py::init<>())
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) &, false_type)
|
||||
.def("method_lref", py::overload_cast<int>(&RefQualifiedOverloaded::method))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) const &, true_type)
|
||||
.def("method_const_lref",
|
||||
py::overload_cast<int>(&RefQualifiedOverloaded::method, py::const_))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) &&, false_type)
|
||||
.def("method_rref", py::overload_cast<float>(&RefQualifiedOverloaded::method))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) const &&, true_type)
|
||||
.def("method_const_rref",
|
||||
py::overload_cast<float>(&RefQualifiedOverloaded::method, py::const_))
|
||||
# ifdef __cpp_noexcept_function_type
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) & noexcept, false_type)
|
||||
.def("method_lref_noexcept", py::overload_cast<long>(&RefQualifiedOverloaded::method))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) const & noexcept, true_type)
|
||||
.def("method_const_lref_noexcept",
|
||||
py::overload_cast<long>(&RefQualifiedOverloaded::method, py::const_))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) && noexcept, false_type)
|
||||
.def("method_rref_noexcept", py::overload_cast<double>(&RefQualifiedOverloaded::method))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) const && noexcept, true_type)
|
||||
.def("method_const_rref_noexcept",
|
||||
py::overload_cast<double>(&RefQualifiedOverloaded::method, py::const_))
|
||||
# endif
|
||||
;
|
||||
#else
|
||||
// Fallback using explicit static_cast for C++11/14
|
||||
py::class_<NoexceptOverloaded>(m, "NoexceptOverloaded")
|
||||
.def(py::init<>())
|
||||
.def("method",
|
||||
static_cast<py::str (NoexceptOverloaded::*)(int)>(&NoexceptOverloaded::method))
|
||||
.def("method_const",
|
||||
static_cast<py::str (NoexceptOverloaded::*)(int) const>(&NoexceptOverloaded::method))
|
||||
.def("method_float",
|
||||
static_cast<py::str (NoexceptOverloaded::*)(float)>(&NoexceptOverloaded::method));
|
||||
m.def("noexcept_free_func", static_cast<int (*)(int)>(noexcept_free_func));
|
||||
m.def("noexcept_free_func_float", static_cast<int (*)(float)>(noexcept_free_func));
|
||||
|
||||
py::class_<RefQualifiedOverloaded>(m, "RefQualifiedOverloaded")
|
||||
.def(py::init<>())
|
||||
.def("method_lref",
|
||||
static_cast<py::str (RefQualifiedOverloaded::*)(int) &>(
|
||||
&RefQualifiedOverloaded::method))
|
||||
.def("method_const_lref",
|
||||
static_cast<py::str (RefQualifiedOverloaded::*)(int) const &>(
|
||||
&RefQualifiedOverloaded::method))
|
||||
.def("method_rref",
|
||||
static_cast<py::str (RefQualifiedOverloaded::*)(float) &&>(
|
||||
&RefQualifiedOverloaded::method))
|
||||
.def("method_const_rref",
|
||||
static_cast<py::str (RefQualifiedOverloaded::*)(float) const &&>(
|
||||
&RefQualifiedOverloaded::method))
|
||||
# ifdef __cpp_noexcept_function_type
|
||||
.def("method_lref_noexcept",
|
||||
static_cast<py::str (RefQualifiedOverloaded::*)(long) & noexcept>(
|
||||
&RefQualifiedOverloaded::method))
|
||||
.def("method_const_lref_noexcept",
|
||||
static_cast<py::str (RefQualifiedOverloaded::*)(long) const & noexcept>(
|
||||
&RefQualifiedOverloaded::method))
|
||||
.def("method_rref_noexcept",
|
||||
static_cast < py::str (RefQualifiedOverloaded::*)(double)
|
||||
&& noexcept > (&RefQualifiedOverloaded::method))
|
||||
.def("method_const_rref_noexcept",
|
||||
static_cast < py::str (RefQualifiedOverloaded::*)(double) const && noexcept
|
||||
> (&RefQualifiedOverloaded::method))
|
||||
# endif
|
||||
;
|
||||
#endif
|
||||
|
||||
// test_methods_and_attributes
|
||||
py::class_<RefQualified>(m, "RefQualified")
|
||||
.def(py::init<>())
|
||||
|
||||
@@ -5,7 +5,7 @@ import sys
|
||||
import pytest
|
||||
|
||||
import env
|
||||
from pybind11_tests import ConstructorStats
|
||||
from pybind11_tests import ConstructorStats, defined___cpp_noexcept_function_type
|
||||
from pybind11_tests import methods_and_attributes as m
|
||||
|
||||
NO_GETTER_MSG = (
|
||||
@@ -383,6 +383,23 @@ def test_cyclic_gc():
|
||||
assert cstats.alive() == 0
|
||||
|
||||
|
||||
@pytest.mark.xfail("env.PYPY", strict=False)
|
||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
||||
def test_dynamic_attr_dealloc_frees_dict_contents():
|
||||
"""Regression: py::dynamic_attr() objects must free __dict__ contents on dealloc.
|
||||
|
||||
pybind11_object_dealloc() did not call PyObject_ClearManagedDict() before tp_free(),
|
||||
causing objects stored in __dict__ to have their refcounts permanently abandoned on
|
||||
Python 3.14+ (where tp_free no longer implicitly clears the managed dict).
|
||||
This caused capsule destructors to never run, leaking the underlying C++ data.
|
||||
"""
|
||||
instance = m.make_dynamic_attr_with_capsule()
|
||||
assert not m.is_dynamic_attr_capsule_freed()
|
||||
del instance
|
||||
pytest.gc_collect()
|
||||
assert m.is_dynamic_attr_capsule_freed()
|
||||
|
||||
|
||||
def test_bad_arg_default(msg):
|
||||
from pybind11_tests import detailed_error_messages_enabled
|
||||
|
||||
@@ -517,6 +534,130 @@ def test_unregistered_base_implementations():
|
||||
assert a.ro_value_prop == 1.75
|
||||
|
||||
|
||||
def test_noexcept_base():
|
||||
"""Test issue #2234: binding noexcept methods inherited from an unregistered base class.
|
||||
|
||||
In C++17 noexcept is part of the function type, so &Derived::noexcept_method resolves
|
||||
to a Base member-function pointer with noexcept specifier. pybind11 must use the Derived
|
||||
type as `self`, not the Base type, otherwise the call raises TypeError at runtime.
|
||||
|
||||
Covers all four new cpp_function constructor specialisations:
|
||||
- Return (Class::*)(Args...) noexcept (set_value)
|
||||
- Return (Class::*)(Args...) const noexcept (value)
|
||||
- Return (Class::*)(Args...) & noexcept (increment)
|
||||
- Return (Class::*)(Args...) const & noexcept (capped_value)
|
||||
"""
|
||||
obj = m.NoexceptDerived()
|
||||
# const noexcept
|
||||
assert obj.value() == 99
|
||||
# noexcept (non-const)
|
||||
obj.set_value(7)
|
||||
assert obj.value() == 7
|
||||
# & noexcept (non-const lvalue ref-qualified)
|
||||
obj.increment()
|
||||
assert obj.value() == 8
|
||||
# const & noexcept (const lvalue ref-qualified)
|
||||
assert obj.capped_value() == 8
|
||||
obj.set_value(200)
|
||||
assert obj.capped_value() == 100 # capped at 100
|
||||
|
||||
|
||||
def test_rvalue_ref_qualified_methods():
|
||||
"""Test that rvalue-ref-qualified (&&/const&&) methods from an unregistered base bind
|
||||
correctly with `self` resolved to the derived type.
|
||||
|
||||
take() moves m_payload out on each call, so the second call returns "".
|
||||
This confirms that the cpp_function lambda uses std::move(*c).*f rather than c->*f.
|
||||
|
||||
Covers:
|
||||
- Return (Class::*)(Args...) && (take)
|
||||
- Return (Class::*)(Args...) const && (peek)
|
||||
"""
|
||||
obj = m.RValueRefDerived()
|
||||
# && moves m_payload: first call gets the value, second gets empty string
|
||||
assert obj.take() == "rref_payload"
|
||||
assert obj.take() == ""
|
||||
# const && doesn't move: peek() is stable across calls
|
||||
assert obj.peek() == 77
|
||||
assert obj.peek() == 77
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not defined___cpp_noexcept_function_type,
|
||||
reason="Requires __cpp_noexcept_function_type",
|
||||
)
|
||||
def test_noexcept_rvalue_ref_qualified_methods():
|
||||
"""Test noexcept rvalue-ref-qualified methods from an unregistered base.
|
||||
|
||||
Covers:
|
||||
- Return (Class::*)(Args...) && noexcept (take_noexcept)
|
||||
- Return (Class::*)(Args...) const && noexcept (peek_noexcept)
|
||||
"""
|
||||
obj = m.RValueRefDerived()
|
||||
assert obj.take_noexcept() == "rref_payload"
|
||||
assert obj.take_noexcept() == ""
|
||||
assert obj.peek_noexcept() == 77
|
||||
assert obj.peek_noexcept() == 77
|
||||
|
||||
|
||||
def test_noexcept_overload_cast():
|
||||
"""Test issue #2234: overload_cast must handle noexcept member and free function pointers.
|
||||
|
||||
In C++17 noexcept is part of the function type, so overload_cast_impl needs dedicated
|
||||
operator() overloads for noexcept free functions and non-const/const member functions.
|
||||
"""
|
||||
obj = m.NoexceptOverloaded()
|
||||
# overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type)
|
||||
assert obj.method(1) == "(int)"
|
||||
# overload_cast_impl::operator()(Return (Class::*)(Args...) const noexcept, true_type)
|
||||
assert obj.method_const(2) == "(int) const"
|
||||
# overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type) float
|
||||
assert obj.method_float(3.0) == "(float)"
|
||||
# overload_cast_impl::operator()(Return (*)(Args...) noexcept)
|
||||
assert m.noexcept_free_func(10) == 11
|
||||
assert m.noexcept_free_func_float(10.0) == 12
|
||||
|
||||
|
||||
def test_ref_qualified_overload_cast():
|
||||
"""Test issue #2234 follow-up: overload_cast with ref-qualified member pointers.
|
||||
|
||||
Covers:
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) &, false_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) const &, true_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) &&, false_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) const &&, true_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) & noexcept, false_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) const & noexcept, true_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) && noexcept, false_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) const && noexcept, true_type)
|
||||
"""
|
||||
obj = m.RefQualifiedOverloaded()
|
||||
assert obj.method_lref(1) == "(int) &"
|
||||
assert obj.method_const_lref(1) == "(int) const &"
|
||||
assert obj.method_rref(1.0) == "(float) &&"
|
||||
assert obj.method_const_rref(1.0) == "(float) const &&"
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not defined___cpp_noexcept_function_type,
|
||||
reason="Requires __cpp_noexcept_function_type",
|
||||
)
|
||||
def test_noexcept_ref_qualified_overload_cast():
|
||||
"""Test issue #2234 follow-up: overload_cast with noexcept ref-qualified member pointers.
|
||||
|
||||
Covers:
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) & noexcept, false_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) const & noexcept, true_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) && noexcept, false_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) const && noexcept, true_type)
|
||||
"""
|
||||
obj = m.RefQualifiedOverloaded()
|
||||
assert obj.method_lref_noexcept(1) == "(long) & noexcept"
|
||||
assert obj.method_const_lref_noexcept(1) == "(long) const & noexcept"
|
||||
assert obj.method_rref_noexcept(1.0) == "(double) && noexcept"
|
||||
assert obj.method_const_rref_noexcept(1.0) == "(double) const && noexcept"
|
||||
|
||||
|
||||
def test_ref_qualified():
|
||||
"""Tests that explicit lvalue ref-qualified methods can be called just like their
|
||||
non ref-qualified counterparts."""
|
||||
|
||||
@@ -78,6 +78,27 @@ TEST_SUBMODULE(numpy_vectorize, m) {
|
||||
struct VectorizeTestClass {
|
||||
explicit VectorizeTestClass(int v) : value{v} {};
|
||||
float method(int x, float y) const { return y + (float) (x + value); }
|
||||
// Exercises vectorize(Return (Class::*)(Args...) &)
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const)
|
||||
float method_lref(int x, float y) & { return y + (float) (x + value); }
|
||||
// Exercises vectorize(Return (Class::*)(Args...) const &)
|
||||
float method_const_lref(int x, float y) const & { return y + (float) (x + value); }
|
||||
// Exercises vectorize(Return (Class::*)(Args...) noexcept)
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const)
|
||||
float method_noexcept(int x, float y) noexcept { return y + (float) (x + value); }
|
||||
// Exercises vectorize(Return (Class::*)(Args...) const noexcept)
|
||||
float method_const_noexcept(int x, float y) const noexcept {
|
||||
return y + (float) (x + value);
|
||||
}
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
// Exercises vectorize(Return (Class::*)(Args...) & noexcept)
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const)
|
||||
float method_lref_noexcept(int x, float y) & noexcept { return y + (float) (x + value); }
|
||||
// Exercises vectorize(Return (Class::*)(Args...) const & noexcept)
|
||||
float method_const_lref_noexcept(int x, float y) const & noexcept {
|
||||
return y + (float) (x + value);
|
||||
}
|
||||
#endif
|
||||
int value = 0;
|
||||
};
|
||||
py::class_<VectorizeTestClass> vtc(m, "VectorizeTestClass");
|
||||
@@ -85,6 +106,15 @@ TEST_SUBMODULE(numpy_vectorize, m) {
|
||||
|
||||
// Automatic vectorizing of methods
|
||||
vtc.def("method", py::vectorize(&VectorizeTestClass::method));
|
||||
vtc.def("method_lref", py::vectorize(&VectorizeTestClass::method_lref));
|
||||
vtc.def("method_const_lref", py::vectorize(&VectorizeTestClass::method_const_lref));
|
||||
vtc.def("method_noexcept", py::vectorize(&VectorizeTestClass::method_noexcept));
|
||||
vtc.def("method_const_noexcept", py::vectorize(&VectorizeTestClass::method_const_noexcept));
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
vtc.def("method_lref_noexcept", py::vectorize(&VectorizeTestClass::method_lref_noexcept));
|
||||
vtc.def("method_const_lref_noexcept",
|
||||
py::vectorize(&VectorizeTestClass::method_const_lref_noexcept));
|
||||
#endif
|
||||
|
||||
// test_trivial_broadcasting
|
||||
// Internal optimization test for whether the input is trivially broadcastable:
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import defined___cpp_noexcept_function_type
|
||||
from pybind11_tests import numpy_vectorize as m
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
@@ -246,6 +247,56 @@ def test_method_vectorization():
|
||||
assert np.all(o.method(x, y) == [[14, 15], [24, 25]])
|
||||
|
||||
|
||||
def test_ref_qualified_method_vectorization():
|
||||
"""Test issue #2234 follow-up: vectorize with lvalue-ref-qualified member pointers.
|
||||
|
||||
Covers:
|
||||
- vectorize(Return (Class::*)(Args...) &)
|
||||
- vectorize(Return (Class::*)(Args...) const &)
|
||||
- vectorize(Return (Class::*)(Args...) & noexcept)
|
||||
- vectorize(Return (Class::*)(Args...) const & noexcept)
|
||||
"""
|
||||
o = m.VectorizeTestClass(3)
|
||||
x = np.array([1, 2], dtype="int")
|
||||
y = np.array([[10], [20]], dtype="float32")
|
||||
assert np.all(o.method_lref(x, y) == [[14, 15], [24, 25]])
|
||||
assert np.all(o.method_const_lref(x, y) == [[14, 15], [24, 25]])
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not defined___cpp_noexcept_function_type,
|
||||
reason="Requires __cpp_noexcept_function_type",
|
||||
)
|
||||
def test_noexcept_ref_qualified_method_vectorization():
|
||||
"""Test issue #2234 follow-up: vectorize with noexcept lvalue-ref-qualified member pointers.
|
||||
|
||||
Covers:
|
||||
- vectorize(Return (Class::*)(Args...) & noexcept)
|
||||
- vectorize(Return (Class::*)(Args...) const & noexcept)
|
||||
"""
|
||||
o = m.VectorizeTestClass(3)
|
||||
x = np.array([1, 2], dtype="int")
|
||||
y = np.array([[10], [20]], dtype="float32")
|
||||
assert np.all(o.method_lref_noexcept(x, y) == [[14, 15], [24, 25]])
|
||||
assert np.all(o.method_const_lref_noexcept(x, y) == [[14, 15], [24, 25]])
|
||||
|
||||
|
||||
def test_noexcept_method_vectorization():
|
||||
"""Test issue #2234: vectorize must handle noexcept member function pointers.
|
||||
|
||||
Covers both new vectorize specialisations:
|
||||
- vectorize(Return (Class::*)(Args...) noexcept)
|
||||
- vectorize(Return (Class::*)(Args...) const noexcept)
|
||||
"""
|
||||
o = m.VectorizeTestClass(3)
|
||||
x = np.array([1, 2], dtype="int")
|
||||
y = np.array([[10], [20]], dtype="float32")
|
||||
# vectorize(Return (Class::*)(Args...) noexcept)
|
||||
assert np.all(o.method_noexcept(x, y) == [[14, 15], [24, 25]])
|
||||
# vectorize(Return (Class::*)(Args...) const noexcept)
|
||||
assert np.all(o.method_const_noexcept(x, y) == [[14, 15], [24, 25]])
|
||||
|
||||
|
||||
def test_array_collapse():
|
||||
assert not isinstance(m.vectorized_func(1, 2, 3), np.ndarray)
|
||||
assert not isinstance(m.vectorized_func(np.array(1), 2, 3), np.ndarray)
|
||||
|
||||
@@ -1086,8 +1086,7 @@ TEST_SUBMODULE(pytypes, m) {
|
||||
static_class.def(py::init());
|
||||
static_class.attr_with_type_hint<py::typing::ClassVar<float>>("x") = 1.0;
|
||||
static_class.attr_with_type_hint<py::typing::ClassVar<py::typing::Dict<py::str, int>>>(
|
||||
"dict_str_int")
|
||||
= py::dict();
|
||||
"dict_str_int") = py::dict();
|
||||
|
||||
struct Instance {};
|
||||
auto instance = py::class_<Instance>(m, "Instance", py::dynamic_attr());
|
||||
|
||||
@@ -247,6 +247,28 @@ struct SharedFromThisVBase : std::enable_shared_from_this<SharedFromThisVBase> {
|
||||
};
|
||||
struct SharedFromThisVirt : virtual SharedFromThisVBase {};
|
||||
|
||||
// Issue #5989: static_pointer_cast where dynamic_pointer_cast is needed
|
||||
// (virtual inheritance with shared_ptr holder)
|
||||
struct SftVirtBase : std::enable_shared_from_this<SftVirtBase> {
|
||||
SftVirtBase() = default;
|
||||
virtual ~SftVirtBase() = default;
|
||||
static std::shared_ptr<SftVirtBase> create() { return std::make_shared<SftVirtBase>(); }
|
||||
virtual std::string name() { return "SftVirtBase"; }
|
||||
};
|
||||
struct SftVirtDerived : SftVirtBase {
|
||||
using SftVirtBase::SftVirtBase;
|
||||
static std::shared_ptr<SftVirtDerived> create() { return std::make_shared<SftVirtDerived>(); }
|
||||
std::string name() override { return "SftVirtDerived"; }
|
||||
};
|
||||
struct SftVirtDerived2 : virtual SftVirtDerived {
|
||||
using SftVirtDerived::SftVirtDerived;
|
||||
static std::shared_ptr<SftVirtDerived2> create() {
|
||||
return std::make_shared<SftVirtDerived2>();
|
||||
}
|
||||
std::string name() override { return "SftVirtDerived2"; }
|
||||
std::string call_name(const std::shared_ptr<SftVirtDerived2> &d2) { return d2->name(); }
|
||||
};
|
||||
|
||||
// test_move_only_holder
|
||||
struct C {
|
||||
C() { print_created(this); }
|
||||
@@ -522,6 +544,17 @@ TEST_SUBMODULE(smart_ptr, m) {
|
||||
py::class_<SharedFromThisVirt, std::shared_ptr<SharedFromThisVirt>>(m, "SharedFromThisVirt")
|
||||
.def_static("get", []() { return sft.get(); });
|
||||
|
||||
// Issue #5989: static_pointer_cast where dynamic_pointer_cast is needed
|
||||
py::class_<SftVirtBase, std::shared_ptr<SftVirtBase>>(m, "SftVirtBase")
|
||||
.def(py::init<>(&SftVirtBase::create))
|
||||
.def("name", &SftVirtBase::name);
|
||||
py::class_<SftVirtDerived, SftVirtBase, std::shared_ptr<SftVirtDerived>>(m, "SftVirtDerived")
|
||||
.def(py::init<>(&SftVirtDerived::create));
|
||||
py::class_<SftVirtDerived2, SftVirtDerived, std::shared_ptr<SftVirtDerived2>>(
|
||||
m, "SftVirtDerived2")
|
||||
.def(py::init<>(&SftVirtDerived2::create))
|
||||
.def("call_name", &SftVirtDerived2::call_name, py::arg("d2"));
|
||||
|
||||
// test_move_only_holder
|
||||
py::class_<C, custom_unique_ptr<C>>(m, "TypeWithMoveOnlyHolder")
|
||||
.def_static("make", []() { return custom_unique_ptr<C>(new C); })
|
||||
|
||||
@@ -251,6 +251,19 @@ def test_shared_ptr_from_this_and_references():
|
||||
assert y is z
|
||||
|
||||
|
||||
def test_shared_from_this_virt_shared_ptr_arg():
|
||||
"""Issue #5989: static_pointer_cast fails with virtual inheritance."""
|
||||
b = m.SftVirtBase()
|
||||
assert b.name() == "SftVirtBase"
|
||||
|
||||
d = m.SftVirtDerived()
|
||||
assert d.name() == "SftVirtDerived"
|
||||
|
||||
d2 = m.SftVirtDerived2()
|
||||
assert d2.name() == "SftVirtDerived2"
|
||||
assert d2.call_name(d2) == "SftVirtDerived2"
|
||||
|
||||
|
||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
||||
def test_move_only_holder():
|
||||
a = m.TypeWithMoveOnlyHolder.make()
|
||||
|
||||
18
tests/test_standalone_enum_module.py
Normal file
18
tests/test_standalone_enum_module.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import env
|
||||
|
||||
|
||||
def test_enum_import_exit_no_crash():
|
||||
# Added in PR #6015. Modeled after reproducer under issue #5976
|
||||
env.check_script_success_in_subprocess(
|
||||
f"""
|
||||
import sys
|
||||
sys.path.insert(0, {os.path.dirname(env.__file__)!r})
|
||||
import standalone_enum_module as m
|
||||
assert m.SomeEnum.__class__.__name__ == "pybind11_type"
|
||||
""",
|
||||
rerun=1,
|
||||
)
|
||||
@@ -173,8 +173,7 @@ struct AdderBase {
|
||||
using DataVisitor = std::function<void(const Data &)>;
|
||||
|
||||
virtual void
|
||||
operator()(const Data &first, const Data &second, const DataVisitor &visitor) const
|
||||
= 0;
|
||||
operator()(const Data &first, const Data &second, const DataVisitor &visitor) const = 0;
|
||||
virtual ~AdderBase() = default;
|
||||
AdderBase() = default;
|
||||
AdderBase(const AdderBase &) = delete;
|
||||
|
||||
@@ -4,13 +4,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pybind11/detail/pybind11_namespace_macros.h>
|
||||
|
||||
#include <catch.hpp>
|
||||
|
||||
#define PYBIND11_CATCH2_SKIP_IF(condition, reason) \
|
||||
do { \
|
||||
PYBIND11_WARNING_PUSH \
|
||||
PYBIND11_WARNING_DISABLE_MSVC(4127) \
|
||||
if (condition) { \
|
||||
Catch::cout() << "[ SKIPPED ] " << (reason) << '\n'; \
|
||||
Catch::cout().flush(); \
|
||||
return; \
|
||||
} \
|
||||
PYBIND11_WARNING_POP \
|
||||
} while (0)
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
|
||||
PYBIND11_WARNING_DISABLE_MSVC(4996)
|
||||
|
||||
#include "catch_skip.h"
|
||||
|
||||
#include <catch.hpp>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
@@ -84,6 +86,14 @@ PYBIND11_EMBEDDED_MODULE(trampoline_module, m) {
|
||||
.def("func", &test_override_cache_helper::func);
|
||||
}
|
||||
|
||||
enum class SomeEnum { value1, value2 }; // Added in PR #6015
|
||||
|
||||
PYBIND11_EMBEDDED_MODULE(enum_module, m, py::multiple_interpreters::per_interpreter_gil()) {
|
||||
py::enum_<SomeEnum>(m, "SomeEnum")
|
||||
.value("value1", SomeEnum::value1)
|
||||
.value("value2", SomeEnum::value2);
|
||||
}
|
||||
|
||||
PYBIND11_EMBEDDED_MODULE(throw_exception, ) { throw std::runtime_error("C++ Error"); }
|
||||
|
||||
PYBIND11_EMBEDDED_MODULE(throw_error_already_set, ) {
|
||||
@@ -343,6 +353,24 @@ TEST_CASE("Restart the interpreter") {
|
||||
REQUIRE(py_widget.attr("the_message").cast<std::string>() == "Hello after restart");
|
||||
}
|
||||
|
||||
TEST_CASE("Enum module survives restart") { // Added in PR #6015
|
||||
// Regression test for gh-5976: py::enum_ uses def_property_static, which
|
||||
// calls process_attributes::init after initialize_generic's strdup loop,
|
||||
// leaving arg names as string literals. Without the fix, destruct() would
|
||||
// call free() on those literals during interpreter finalization.
|
||||
PYBIND11_CATCH2_SKIP_IF(PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION == 12,
|
||||
"Pre-existing crash in enum cleanup during finalize on Python 3.12");
|
||||
|
||||
auto enum_mod = py::module_::import("enum_module");
|
||||
REQUIRE(enum_mod.attr("SomeEnum").attr("value1").attr("name").cast<std::string>() == "value1");
|
||||
|
||||
py::finalize_interpreter();
|
||||
py::initialize_interpreter();
|
||||
|
||||
enum_mod = py::module_::import("enum_module");
|
||||
REQUIRE(enum_mod.attr("SomeEnum").attr("value2").attr("name").cast<std::string>() == "value2");
|
||||
}
|
||||
|
||||
TEST_CASE("Execution frame") {
|
||||
// When the interpreter is embedded, there is no execution frame, but `py::exec`
|
||||
// should still function by using reasonable globals: `__main__.__dict__`.
|
||||
|
||||
Reference in New Issue
Block a user