https://github.com/python/cpython/commit/226011ba127323dea894ee67e6990f1305efa2d5
commit: 226011ba127323dea894ee67e6990f1305efa2d5
branch: main
author: Petr Viktorin <[email protected]>
committer: encukou <[email protected]>
date: 2025-11-25T14:30:33+01:00
summary:

gh-139165: Make Py_SIZE, Py_IS_TYPE,Py_ SET_SIZE regular functions in stable 
ABI (GH-139166)

* Make Py_{SIZE,IS_TYPE,SET_SIZE} regular functions in stable ABI

Group them together with Py_TYPE & Py_SET_TYPE to cut down
on repetitive preprocessor macros.
Format repetitive definitions in object.c more concisely.

Py_SET_TYPE is still left out of the Limited API.

files:
A Misc/NEWS.d/next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst
M Doc/data/stable_abi.dat
M Include/object.h
M Lib/test/test_stable_abi_ctypes.py
M Misc/stable_abi.toml
M Objects/object.c
M PC/python3dll.c

diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 95e032655cf0cc..437f552cccf55b 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -915,6 +915,7 @@ func,Py_GetPlatform,3.2,,
 func,Py_GetRecursionLimit,3.2,,
 func,Py_GetVersion,3.2,,
 data,Py_HasFileSystemDefaultEncoding,3.2,,
+func,Py_IS_TYPE,3.15,,
 func,Py_IncRef,3.2,,
 func,Py_Initialize,3.2,,
 func,Py_InitializeEx,3.2,,
@@ -936,6 +937,8 @@ func,Py_REFCNT,3.14,,
 macro,Py_RELATIVE_OFFSET,3.12,,
 func,Py_ReprEnter,3.2,,
 func,Py_ReprLeave,3.2,,
+func,Py_SET_SIZE,3.15,,
+func,Py_SIZE,3.15,,
 func,Py_SetProgramName,3.2,,
 func,Py_SetPythonHome,3.2,,
 func,Py_SetRecursionLimit,3.2,,
diff --git a/Include/object.h b/Include/object.h
index f17dcba4f476b0..ad452be8405671 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -140,12 +140,12 @@ struct _object {
 #  endif
         };
 #else
-        Py_ssize_t ob_refcnt;
+        Py_ssize_t ob_refcnt;  // part of stable ABI; do not change
 #endif
         _Py_ALIGNED_DEF(_PyObject_MIN_ALIGNMENT, char) _aligner;
     };
 
-    PyTypeObject *ob_type;
+    PyTypeObject *ob_type;  // part of stable ABI; do not change
 };
 #else
 // Objects that are not owned by any thread use a thread id (tid) of zero.
@@ -173,7 +173,7 @@ struct _object {
 #ifndef _Py_OPAQUE_PYOBJECT
 struct PyVarObject {
     PyObject ob_base;
-    Py_ssize_t ob_size; /* Number of items in variable part */
+    Py_ssize_t ob_size; // Number of items in variable part. Part of stable ABI
 };
 #endif
 typedef struct PyVarObject PyVarObject;
@@ -265,56 +265,72 @@ _Py_IsOwnedByCurrentThread(PyObject *ob)
 }
 #endif
 
-// Py_TYPE() implementation for the stable ABI
+PyAPI_DATA(PyTypeObject) PyLong_Type;
+PyAPI_DATA(PyTypeObject) PyBool_Type;
+
+/* Definitions for the stable ABI */
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 14)
 PyAPI_FUNC(PyTypeObject*) Py_TYPE(PyObject *ob);
+#endif
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15)
+PyAPI_FUNC(Py_ssize_t) Py_SIZE(PyObject *ob);
+PyAPI_FUNC(int) Py_IS_TYPE(PyObject *ob, PyTypeObject *type);
+PyAPI_FUNC(void) Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size);
+#endif
+
+#ifndef _Py_OPAQUE_PYOBJECT
 
-#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030e0000
-    // Stable ABI implements Py_TYPE() as a function call
-    // on limited C API version 3.14 and newer.
+static inline void
+Py_SET_TYPE(PyObject *ob, PyTypeObject *type)
+{
+    ob->ob_type = type;
+}
+
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < _Py_PACK_VERSION(3, 11)
+// Non-limited API & limited API 3.11 & below: use static inline functions and
+// use _PyObject_CAST so that users don't need their own casts
+#  define Py_TYPE(ob) _Py_TYPE_impl(_PyObject_CAST(ob))
+#  define Py_SIZE(ob) _Py_SIZE_impl(_PyObject_CAST(ob))
+#  define Py_IS_TYPE(ob, type) _Py_IS_TYPE_impl(_PyObject_CAST(ob), (type))
+#  define Py_SET_SIZE(ob, size) _Py_SET_SIZE_impl(_PyVarObject_CAST(ob), 
(size))
+#  define Py_SET_TYPE(ob, type) Py_SET_TYPE(_PyObject_CAST(ob), type)
+#elif Py_LIMITED_API+0 < _Py_PACK_VERSION(3, 15)
+// Limited API 3.11-3.14: use static inline functions, without casts
+#  define Py_SIZE(ob) _Py_SIZE_impl(ob)
+#  define Py_IS_TYPE(ob, type) _Py_IS_TYPE_impl((ob), (type))
+#  define Py_SET_SIZE(ob, size) _Py_SET_SIZE_impl((ob), (size))
+#  if Py_LIMITED_API+0 < _Py_PACK_VERSION(3, 14)
+//   Py_TYPE() is static inline only on Limited API 3.13 and below
+#    define Py_TYPE(ob) _Py_TYPE_impl(ob)
+#  endif
 #else
-    static inline PyTypeObject* _Py_TYPE(PyObject *ob)
-    {
-        return ob->ob_type;
-    }
-    #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
-    #   define Py_TYPE(ob) _Py_TYPE(_PyObject_CAST(ob))
-    #else
-    #   define Py_TYPE(ob) _Py_TYPE(ob)
-    #endif
+// Limited API 3.15+: use function calls
 #endif
 
-PyAPI_DATA(PyTypeObject) PyLong_Type;
-PyAPI_DATA(PyTypeObject) PyBool_Type;
+static inline
+PyTypeObject* _Py_TYPE_impl(PyObject *ob)
+{
+    return ob->ob_type;
+}
 
-#ifndef _Py_OPAQUE_PYOBJECT
 // bpo-39573: The Py_SET_SIZE() function must be used to set an object size.
-static inline Py_ssize_t Py_SIZE(PyObject *ob) {
+static inline Py_ssize_t
+_Py_SIZE_impl(PyObject *ob)
+{
     assert(Py_TYPE(ob) != &PyLong_Type);
     assert(Py_TYPE(ob) != &PyBool_Type);
     return  _PyVarObject_CAST(ob)->ob_size;
 }
-#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
-#  define Py_SIZE(ob) Py_SIZE(_PyObject_CAST(ob))
-#endif
-#endif // !defined(_Py_OPAQUE_PYOBJECT)
 
-static inline int Py_IS_TYPE(PyObject *ob, PyTypeObject *type) {
+static inline int
+_Py_IS_TYPE_impl(PyObject *ob, PyTypeObject *type)
+{
     return Py_TYPE(ob) == type;
 }
-#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
-#  define Py_IS_TYPE(ob, type) Py_IS_TYPE(_PyObject_CAST(ob), (type))
-#endif
 
-
-#ifndef _Py_OPAQUE_PYOBJECT
-static inline void Py_SET_TYPE(PyObject *ob, PyTypeObject *type) {
-    ob->ob_type = type;
-}
-#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
-#  define Py_SET_TYPE(ob, type) Py_SET_TYPE(_PyObject_CAST(ob), type)
-#endif
-
-static inline void Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) {
+static inline void
+_Py_SET_SIZE_impl(PyVarObject *ob, Py_ssize_t size)
+{
     assert(Py_TYPE(_PyObject_CAST(ob)) != &PyLong_Type);
     assert(Py_TYPE(_PyObject_CAST(ob)) != &PyBool_Type);
 #ifdef Py_GIL_DISABLED
@@ -323,9 +339,7 @@ static inline void Py_SET_SIZE(PyVarObject *ob, Py_ssize_t 
size) {
     ob->ob_size = size;
 #endif
 }
-#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
-#  define Py_SET_SIZE(ob, size) Py_SET_SIZE(_PyVarObject_CAST(ob), (size))
-#endif
+
 #endif // !defined(_Py_OPAQUE_PYOBJECT)
 
 
diff --git a/Lib/test/test_stable_abi_ctypes.py 
b/Lib/test/test_stable_abi_ctypes.py
index bc834f5a6816f3..2e93ac08f82868 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -901,6 +901,7 @@ def test_windows_feature_macros(self):
     "Py_GetRecursionLimit",
     "Py_GetVersion",
     "Py_HasFileSystemDefaultEncoding",
+    "Py_IS_TYPE",
     "Py_IncRef",
     "Py_Initialize",
     "Py_InitializeEx",
@@ -920,6 +921,8 @@ def test_windows_feature_macros(self):
     "Py_REFCNT",
     "Py_ReprEnter",
     "Py_ReprLeave",
+    "Py_SET_SIZE",
+    "Py_SIZE",
     "Py_SetPath",
     "Py_SetProgramName",
     "Py_SetPythonHome",
diff --git 
a/Misc/NEWS.d/next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst 
b/Misc/NEWS.d/next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst
new file mode 100644
index 00000000000000..039c25b8ce2b3e
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-09-22-16-32-00.gh-issue-139165.6Czn7S.rst
@@ -0,0 +1,2 @@
+Expose the functions :c:func:`Py_SIZE`, :c:func:`Py_IS_TYPE` and
+:c:func:`Py_SET_SIZE` in the Stable ABI.
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index 5c503f81d3299a..7ca3059db9ed97 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -2524,8 +2524,10 @@
     added = '3.13'
 
 [function.Py_TYPE]
+    # Before 3.14, this was a macro that accessed the PyObject member
     added = '3.14'
 [function.Py_REFCNT]
+    # Before 3.14, this was a macro that accessed the PyObject member
     added = '3.14'
 [function.PyIter_NextItem]
     added = '3.14'
@@ -2641,3 +2643,12 @@
     added = '3.15'
 [function.PyDict_SetDefaultRef]
     added = '3.15'
+[function.Py_SIZE]
+    # Before 3.15, this was a macro that accessed the PyObject member
+    added = '3.15'
+[function.Py_IS_TYPE]
+    # Before 3.15, this was a macro that accessed the PyObject member
+    added = '3.15'
+[function.Py_SET_SIZE]
+    # Before 3.15, this was a macro that accessed the PyObject member
+    added = '3.15'
diff --git a/Objects/object.c b/Objects/object.c
index 0a80c6edcf158c..fcea3503de8213 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -3361,24 +3361,6 @@ Py_GetConstantBorrowed(unsigned int constant_id)
     return Py_GetConstant(constant_id);
 }
 
-
-// Py_TYPE() implementation for the stable ABI
-#undef Py_TYPE
-PyTypeObject*
-Py_TYPE(PyObject *ob)
-{
-    return _Py_TYPE(ob);
-}
-
-
-// Py_REFCNT() implementation for the stable ABI
-#undef Py_REFCNT
-Py_ssize_t
-Py_REFCNT(PyObject *ob)
-{
-    return _Py_REFCNT(ob);
-}
-
 int
 PyUnstable_IsImmortal(PyObject *op)
 {
@@ -3405,3 +3387,16 @@ _PyObject_VisitType(PyObject *op, visitproc visit, void 
*arg)
     Py_VISIT(tp);
     return 0;
 }
+
+// Implementations for the stable ABI
+// Keep these at the end.
+#undef Py_TYPE
+#undef Py_REFCNT
+#undef Py_SIZE
+#undef Py_IS_TYPE
+#undef Py_SET_SIZE
+PyTypeObject* Py_TYPE(PyObject *ob) { return _Py_TYPE_impl(ob); }
+Py_ssize_t Py_REFCNT(PyObject *ob) { return _Py_REFCNT(ob); }
+Py_ssize_t Py_SIZE(PyObject *o) { return _Py_SIZE_impl(o); }
+int Py_IS_TYPE(PyObject *o, PyTypeObject *t) { return _Py_IS_TYPE_impl(o, t); }
+void Py_SET_SIZE(PyVarObject *o, Py_ssize_t s) { _Py_SET_SIZE_impl(o, s); }
diff --git a/PC/python3dll.c b/PC/python3dll.c
index 35db1a660a762f..0d9e7e9a1bac93 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -71,6 +71,7 @@ EXPORT_FUNC(Py_IncRef)
 EXPORT_FUNC(Py_Initialize)
 EXPORT_FUNC(Py_InitializeEx)
 EXPORT_FUNC(Py_Is)
+EXPORT_FUNC(Py_IS_TYPE)
 EXPORT_FUNC(Py_IsFalse)
 EXPORT_FUNC(Py_IsFinalizing)
 EXPORT_FUNC(Py_IsInitialized)
@@ -86,10 +87,12 @@ EXPORT_FUNC(Py_PACK_VERSION)
 EXPORT_FUNC(Py_REFCNT)
 EXPORT_FUNC(Py_ReprEnter)
 EXPORT_FUNC(Py_ReprLeave)
+EXPORT_FUNC(Py_SET_SIZE)
 EXPORT_FUNC(Py_SetPath)
 EXPORT_FUNC(Py_SetProgramName)
 EXPORT_FUNC(Py_SetPythonHome)
 EXPORT_FUNC(Py_SetRecursionLimit)
+EXPORT_FUNC(Py_SIZE)
 EXPORT_FUNC(Py_TYPE)
 EXPORT_FUNC(Py_VaBuildValue)
 EXPORT_FUNC(Py_XNewRef)

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to