diff --git a/docs/changelog.rst b/docs/changelog.rst index 4777e37..ca4c07f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,8 @@ Changelog ========= +* 2026-11-04: Add ``Py_SETREF()`` and ``Py_XSETREF()`` to the limited C API + older than Python 3.16. * 2026-10-12: Add functions: * ``PyEval_GetFrameBuiltins()`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index c6fe172..0c6c1c2 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -169,6 +169,34 @@ static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) #endif +// Python 3.6 added Py_SETREF() and Py_XSETREF() macros. +// Python 3.16 adds them to the limited C API. +// Don't use the more advanced typeof()/__typeof__() or C++ (auto) +// implementation, but only the memcpy() implementation to keep the code short. +#ifndef Py_SETREF +#define Py_SETREF(dst, src) \ + do { \ + PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \ + PyObject *_tmp_old_dst; \ + PyObject *_tmp_src = _PyObject_CAST(src); \ + memcpy(&_tmp_old_dst, _tmp_dst_ptr, sizeof(PyObject*)); \ + memcpy(_tmp_dst_ptr, &_tmp_src, sizeof(PyObject*)); \ + Py_DECREF(_tmp_old_dst); \ + } while (0) +#endif +#ifndef Py_XSETREF +#define Py_XSETREF(dst, src) \ + do { \ + PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \ + PyObject *_tmp_old_dst; \ + PyObject *_tmp_src = _PyObject_CAST(src); \ + memcpy(&_tmp_old_dst, _tmp_dst_ptr, sizeof(PyObject*)); \ + memcpy(_tmp_dst_ptr, &_tmp_src, sizeof(PyObject*)); \ + Py_XDECREF(_tmp_old_dst); \ + } while (0) +#endif + + // bpo-39573 added Py_SET_TYPE() to Python 3.9 #if !defined(Py_SET_TYPE) && PY_VERSION_HEX < _Py_PACK_VERSION(3, 9) static inline void _Py_SET_TYPE(PyObject *ob, PyTypeObject *type) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 1ef5946..977430f 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -75,7 +75,6 @@ test_object(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) assert(Py_XNewRef(_Py_NULL) == _Py_NULL); -#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 16) // Py_SETREF() PyObject *setref = Py_NewRef(obj); PyObject *none = Py_None; @@ -100,7 +99,6 @@ test_object(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) Py_XSETREF(xsetref, _Py_NULL); assert(Py_REFCNT(obj) == refcnt); assert(xsetref == _Py_NULL); -#endif // Py_SET_REFCNT Py_SET_REFCNT(obj, Py_REFCNT(obj));