From 521db10af71189c729d2b763b135559655e44002 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Tue, 26 Aug 2025 12:24:05 -0600 Subject: [PATCH 1/6] Add PyUnstable_Object_IsUniquelyReferenced --- pythoncapi_compat.h | 18 ++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 3320f68..3f62c10 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -2211,6 +2211,24 @@ PyConfig_GetInt(const char *name, int *value) } #endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION) +// gh-133144 added PyUnstable_Object_IsUniquelyReferenced() to Python 3.14.0b1 +// Adapted from _PyObject_IsUniquelyReferenced implementation +#if PY_VERSION_HEX < 0x030E00B0 +static inline int PyUnstable_Object_IsUniquelyReferenced(PyObject *obj) +{ +#if !defined(Py_GIL_DISABLED) + return Py_REFCNT(obj) == 1; +#else + // NOTE: the entire ob_ref_shared field must be zero, including flags, to + // ensure that other threads cannot concurrently create new references to + // this object. + return (_Py_IsOwnedByCurrentThread(obj) && + _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local) == 1 && + _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared) == 0); +#endif +} +#endif + #if PY_VERSION_HEX < 0x030F0000 static inline PyObject* diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index c719d42..6389775 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1972,6 +1972,27 @@ test_unicodewriter_format(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) } #endif +static PyObject * +test_uniquely_referenced(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + const char *str = "Hello World"; + PyObject *obj = PyUnicode_FromString(str); + if (obj == NULL) { + return NULL; + } + + assert(PyUnstable_Object_IsUniquelyReferenced(obj)); + + Py_INCREF(obj); + + assert(!PyUnstable_Object_IsUniquelyReferenced(obj)); + + Py_DECREF(obj); + Py_DECREF(obj); + + Py_RETURN_NONE; +} + static PyObject * test_bytes(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) @@ -2328,6 +2349,7 @@ static struct PyMethodDef methods[] = { {"test_config", test_config, METH_NOARGS, _Py_NULL}, #endif {"test_sys", test_sys, METH_NOARGS, _Py_NULL}, + {"test_uniquely_referenced", test_uniquely_referenced, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; From 4238b882e022ef127bfa8cf5592ceb44ccfb7cdb Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Tue, 26 Aug 2025 13:17:24 -0600 Subject: [PATCH 2/6] disable test on pypy --- tests/test_pythoncapi_compat_cext.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 6389775..69b1f36 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1972,6 +1972,7 @@ test_unicodewriter_format(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) } #endif +#ifndef PYPY_VERSION static PyObject * test_uniquely_referenced(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { @@ -1992,7 +1993,7 @@ test_uniquely_referenced(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) Py_RETURN_NONE; } - +#endif static PyObject * test_bytes(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) @@ -2349,7 +2350,9 @@ static struct PyMethodDef methods[] = { {"test_config", test_config, METH_NOARGS, _Py_NULL}, #endif {"test_sys", test_sys, METH_NOARGS, _Py_NULL}, +#ifndef PYPY_VERSION {"test_uniquely_referenced", test_uniquely_referenced, METH_NOARGS, _Py_NULL}, +#endif {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; From fc505d13bc86aac25dbbfe941fbffc856ed7ce94 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 1 Sep 2025 10:02:30 -0600 Subject: [PATCH 3/6] rewrite test to pass on pypy --- tests/test_pythoncapi_compat_cext.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 69b1f36..31386d5 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1972,12 +1972,10 @@ test_unicodewriter_format(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) } #endif -#ifndef PYPY_VERSION static PyObject * test_uniquely_referenced(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { - const char *str = "Hello World"; - PyObject *obj = PyUnicode_FromString(str); + PyObject *obj = Py_BuildValue("(s, s)", "hello", "world"); if (obj == NULL) { return NULL; } @@ -1993,7 +1991,6 @@ test_uniquely_referenced(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) Py_RETURN_NONE; } -#endif static PyObject * test_bytes(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) @@ -2350,9 +2347,7 @@ static struct PyMethodDef methods[] = { {"test_config", test_config, METH_NOARGS, _Py_NULL}, #endif {"test_sys", test_sys, METH_NOARGS, _Py_NULL}, -#ifndef PYPY_VERSION {"test_uniquely_referenced", test_uniquely_referenced, METH_NOARGS, _Py_NULL}, -#endif {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; From 132e82560d089cebe7df5419c9f59059b74cac82 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 1 Sep 2025 10:26:37 -0600 Subject: [PATCH 4/6] apply suggestion from code review --- pythoncapi_compat.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 3f62c10..d14e90f 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -2211,8 +2211,8 @@ PyConfig_GetInt(const char *name, int *value) } #endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION) -// gh-133144 added PyUnstable_Object_IsUniquelyReferenced() to Python 3.14.0b1 -// Adapted from _PyObject_IsUniquelyReferenced implementation +// gh-133144 added PyUnstable_Object_IsUniquelyReferenced() to Python 3.14.0b1. +// Adapted from _PyObject_IsUniquelyReferenced() implementation. #if PY_VERSION_HEX < 0x030E00B0 static inline int PyUnstable_Object_IsUniquelyReferenced(PyObject *obj) { From 6dcad6a5c4def15d698f00ed38983311fc78be6e Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 1 Sep 2025 10:39:24 -0600 Subject: [PATCH 5/6] add changelog and api docs entries --- docs/api.rst | 3 +++ docs/changelog.rst | 1 + 2 files changed, 4 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 6efae15..ccd4d08 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -208,6 +208,9 @@ Python 3.14 See `PyConfig_GetInt() documentation `__. +.. c:function:: int PyUnstable_Object_IsUniquelyReferenced(PyObject *op) + + See `PyUnstable_Object_IsUniquelyReferenced() documentation `__. Not supported: diff --git a/docs/changelog.rst b/docs/changelog.rst index b8e3a86..a493c81 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* 2025-09-01: Add ``PyUnstable_Object_IsUniquelyReferenced`` function. * 2025-06-09: Add ``PyUnicodeWriter_WriteASCII()`` function. * 2025-06-03: Add functions: From bd15c4818314a118ff0d50eb347ccb636dd03c1e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 1 Sep 2025 18:43:49 +0200 Subject: [PATCH 6/6] Update docs/changelog.rst --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index a493c81..fa9a35d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -* 2025-09-01: Add ``PyUnstable_Object_IsUniquelyReferenced`` function. +* 2025-09-01: Add ``PyUnstable_Object_IsUniquelyReferenced()`` function. * 2025-06-09: Add ``PyUnicodeWriter_WriteASCII()`` function. * 2025-06-03: Add functions: