From 02f72b8b938e301bbaaf0142547014e074bd564c Mon Sep 17 00:00:00 2001
From: Victor Stinner <vstinner@python.org>
Date: Mon, 28 Nov 2022 16:40:08 +0100
Subject: [PATCH] gh-89653: PEP 670: Convert macros to functions (#99843)

Convert macros to static inline functions to avoid macro pitfalls,
like duplication of side effects:

* DK_ENTRIES()
* DK_UNICODE_ENTRIES()
* PyCode_GetNumFree()
* PyFloat_AS_DOUBLE()
* PyInstanceMethod_GET_FUNCTION()
* PyMemoryView_GET_BASE()
* PyMemoryView_GET_BUFFER()
* PyMethod_GET_FUNCTION()
* PyMethod_GET_SELF()
* PySet_GET_SIZE()
* _PyHeapType_GET_MEMBERS()

Changes:

* PyCode_GetNumFree() casts PyCode_GetNumFree.co_nfreevars from int
  to Py_ssize_t to be future proof, and because Py_ssize_t is
  commonly used in the C API.
* PyCode_GetNumFree() doesn't cast its argument: the replaced macro
  already required the exact type PyCodeObject*.
* Add assertions in some functions using "CAST" macros to check
  the arguments type when Python is built with assertions
  (debug build).
* Remove an outdated comment in unicodeobject.h.
---
 Include/cpython/classobject.h    | 34 ++++++++++++++++++++++----------
 Include/cpython/code.h           |  7 ++++++-
 Include/cpython/floatobject.h    | 10 ++++++++--
 Include/cpython/memoryobject.h   | 13 ++++++++++--
 Include/cpython/setobject.h      |  9 +++++++--
 Include/cpython/unicodeobject.h  |  2 --
 Include/internal/pycore_dict.h   | 21 ++++++++++++++------
 Include/internal/pycore_object.h |  5 +++--
 8 files changed, 74 insertions(+), 27 deletions(-)

diff --git a/Include/cpython/classobject.h b/Include/cpython/classobject.h
index 05104196500..d7c9ddd1336 100644
--- a/Include/cpython/classobject.h
+++ b/Include/cpython/classobject.h
@@ -26,12 +26,20 @@ PyAPI_FUNC(PyObject *) PyMethod_New(PyObject *, PyObject *);
 PyAPI_FUNC(PyObject *) PyMethod_Function(PyObject *);
 PyAPI_FUNC(PyObject *) PyMethod_Self(PyObject *);
 
-/* Macros for direct access to these values. Type checks are *not*
-   done, so use with care. */
-#define PyMethod_GET_FUNCTION(meth) \
-        (((PyMethodObject *)(meth)) -> im_func)
-#define PyMethod_GET_SELF(meth) \
-        (((PyMethodObject *)(meth)) -> im_self)
+#define _PyMethod_CAST(meth) \
+    (assert(PyMethod_Check(meth)), _Py_CAST(PyMethodObject*, meth))
+
+/* Static inline functions for direct access to these values.
+   Type checks are *not* done, so use with care. */
+static inline PyObject* PyMethod_GET_FUNCTION(PyObject *meth) {
+    return _PyMethod_CAST(meth)->im_func;
+}
+#define PyMethod_GET_FUNCTION(meth) PyMethod_GET_FUNCTION(_PyObject_CAST(meth))
+
+static inline PyObject* PyMethod_GET_SELF(PyObject *meth) {
+    return _PyMethod_CAST(meth)->im_self;
+}
+#define PyMethod_GET_SELF(meth) PyMethod_GET_SELF(_PyObject_CAST(meth))
 
 typedef struct {
     PyObject_HEAD
@@ -45,10 +53,16 @@ PyAPI_DATA(PyTypeObject) PyInstanceMethod_Type;
 PyAPI_FUNC(PyObject *) PyInstanceMethod_New(PyObject *);
 PyAPI_FUNC(PyObject *) PyInstanceMethod_Function(PyObject *);
 
-/* Macros for direct access to these values. Type checks are *not*
-   done, so use with care. */
-#define PyInstanceMethod_GET_FUNCTION(meth) \
-        (((PyInstanceMethodObject *)(meth)) -> func)
+#define _PyInstanceMethod_CAST(meth) \
+    (assert(PyInstanceMethod_Check(meth)), \
+     _Py_CAST(PyInstanceMethodObject*, meth))
+
+/* Static inline function for direct access to these values.
+   Type checks are *not* done, so use with care. */
+static inline PyObject* PyInstanceMethod_GET_FUNCTION(PyObject *meth) {
+    return _PyInstanceMethod_CAST(meth)->func;
+}
+#define PyInstanceMethod_GET_FUNCTION(meth) PyInstanceMethod_GET_FUNCTION(_PyObject_CAST(meth))
 
 #ifdef __cplusplus
 }
diff --git a/Include/cpython/code.h b/Include/cpython/code.h
index ebac0b12a46..15b464fe2ee 100644
--- a/Include/cpython/code.h
+++ b/Include/cpython/code.h
@@ -147,7 +147,12 @@ struct PyCodeObject _PyCode_DEF(1);
 PyAPI_DATA(PyTypeObject) PyCode_Type;
 
 #define PyCode_Check(op) Py_IS_TYPE((op), &PyCode_Type)
-#define PyCode_GetNumFree(op) ((op)->co_nfreevars)
+
+static inline Py_ssize_t PyCode_GetNumFree(PyCodeObject *op) {
+    assert(PyCode_Check(op));
+    return op->co_nfreevars;
+}
+
 #define _PyCode_CODE(CO) ((_Py_CODEUNIT *)(CO)->co_code_adaptive)
 #define _PyCode_NBYTES(CO) (Py_SIZE(CO) * (Py_ssize_t)sizeof(_Py_CODEUNIT))
 
diff --git a/Include/cpython/floatobject.h b/Include/cpython/floatobject.h
index 7795d9f83f0..127093098bf 100644
--- a/Include/cpython/floatobject.h
+++ b/Include/cpython/floatobject.h
@@ -7,9 +7,15 @@ typedef struct {
     double ob_fval;
 } PyFloatObject;
 
-// Macro version of PyFloat_AsDouble() trading safety for speed.
+#define _PyFloat_CAST(op) \
+    (assert(PyFloat_Check(op)), _Py_CAST(PyFloatObject*, op))
+
+// Static inline version of PyFloat_AsDouble() trading safety for speed.
 // It doesn't check if op is a double object.
-#define PyFloat_AS_DOUBLE(op) (((PyFloatObject *)(op))->ob_fval)
+static inline double PyFloat_AS_DOUBLE(PyObject *op) {
+    return _PyFloat_CAST(op)->ob_fval;
+}
+#define PyFloat_AS_DOUBLE(op) PyFloat_AS_DOUBLE(_PyObject_CAST(op))
 
 
 PyAPI_FUNC(int) PyFloat_Pack2(double x, char *p, int le);
diff --git a/Include/cpython/memoryobject.h b/Include/cpython/memoryobject.h
index e2a1e168e46..deab3cc89f7 100644
--- a/Include/cpython/memoryobject.h
+++ b/Include/cpython/memoryobject.h
@@ -36,7 +36,16 @@ typedef struct {
     Py_ssize_t ob_array[1];       /* shape, strides, suboffsets */
 } PyMemoryViewObject;
 
+#define _PyMemoryView_CAST(op) _Py_CAST(PyMemoryViewObject*, op)
+
 /* Get a pointer to the memoryview's private copy of the exporter's buffer. */
-#define PyMemoryView_GET_BUFFER(op) (&((PyMemoryViewObject *)(op))->view)
+static inline Py_buffer* PyMemoryView_GET_BUFFER(PyObject *op) {
+    return (&_PyMemoryView_CAST(op)->view);
+}
+#define PyMemoryView_GET_BUFFER(op) PyMemoryView_GET_BUFFER(_PyObject_CAST(op))
+
 /* Get a pointer to the exporting object (this may be NULL!). */
-#define PyMemoryView_GET_BASE(op) (((PyMemoryViewObject *)(op))->view.obj)
+static inline PyObject* PyMemoryView_GET_BASE(PyObject *op) {
+    return _PyMemoryView_CAST(op)->view.obj;
+}
+#define PyMemoryView_GET_BASE(op) PyMemoryView_GET_BASE(_PyObject_CAST(op))
diff --git a/Include/cpython/setobject.h b/Include/cpython/setobject.h
index b4443a678b7..20fd63eaae5 100644
--- a/Include/cpython/setobject.h
+++ b/Include/cpython/setobject.h
@@ -58,8 +58,13 @@ typedef struct {
     PyObject *weakreflist;      /* List of weak references */
 } PySetObject;
 
-#define PySet_GET_SIZE(so) \
-    (assert(PyAnySet_Check(so)), (((PySetObject *)(so))->used))
+#define _PySet_CAST(so) \
+    (assert(PyAnySet_Check(so)), _Py_CAST(PySetObject*, so))
+
+static inline Py_ssize_t PySet_GET_SIZE(PyObject *so) {
+    return _PySet_CAST(so)->used;
+}
+#define PySet_GET_SIZE(so) PySet_GET_SIZE(_PyObject_CAST(so))
 
 PyAPI_DATA(PyObject *) _PySet_Dummy;
 
diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h
index 86eeab67275..a75336f590e 100644
--- a/Include/cpython/unicodeobject.h
+++ b/Include/cpython/unicodeobject.h
@@ -231,8 +231,6 @@ enum PyUnicode_Kind {
 // new compiler warnings on "kind < PyUnicode_KIND(str)" (compare signed and
 // unsigned numbers) where kind type is an int or on
 // "unsigned int kind = PyUnicode_KIND(str)" (cast signed to unsigned).
-// Only declare the function as static inline function in the limited C API
-// version 3.12 which is stricter.
 #define PyUnicode_KIND(op) (_PyASCIIObject_CAST(op)->state.kind)
 
 /* Return a void pointer to the raw unicode buffer. */
diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h
index 25bd3bffb2e..04b70849013 100644
--- a/Include/internal/pycore_dict.h
+++ b/Include/internal/pycore_dict.h
@@ -128,12 +128,21 @@ struct _dictvalues {
 #else
 #define DK_SIZE(dk)      (1<<DK_LOG_SIZE(dk))
 #endif
-#define DK_ENTRIES(dk) \
-    (assert((dk)->dk_kind == DICT_KEYS_GENERAL), \
-     (PyDictKeyEntry*)(&((int8_t*)((dk)->dk_indices))[(size_t)1 << (dk)->dk_log2_index_bytes]))
-#define DK_UNICODE_ENTRIES(dk) \
-    (assert((dk)->dk_kind != DICT_KEYS_GENERAL), \
-     (PyDictUnicodeEntry*)(&((int8_t*)((dk)->dk_indices))[(size_t)1 << (dk)->dk_log2_index_bytes]))
+
+static inline void* _DK_ENTRIES(PyDictKeysObject *dk) {
+    int8_t *indices = (int8_t*)(dk->dk_indices);
+    size_t index = (size_t)1 << dk->dk_log2_index_bytes;
+    return (&indices[index]);
+}
+static inline PyDictKeyEntry* DK_ENTRIES(PyDictKeysObject *dk) {
+    assert(dk->dk_kind == DICT_KEYS_GENERAL);
+    return (PyDictKeyEntry*)_DK_ENTRIES(dk);
+}
+static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) {
+    assert(dk->dk_kind != DICT_KEYS_GENERAL);
+    return (PyDictUnicodeEntry*)_DK_ENTRIES(dk);
+}
+
 #define DK_IS_UNICODE(dk) ((dk)->dk_kind != DICT_KEYS_GENERAL)
 
 #define DICT_VERSION_INCREMENT (1 << DICT_MAX_WATCHERS)
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index 8b78f79e950..33c8c0b75ea 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -355,8 +355,9 @@ extern int _PyType_HasSubclasses(PyTypeObject *);
 extern PyObject* _PyType_GetSubclasses(PyTypeObject *);
 
 // Access macro to the members which are floating "behind" the object
-#define _PyHeapType_GET_MEMBERS(etype) \
-    ((PyMemberDef *)(((char *)(etype)) + Py_TYPE(etype)->tp_basicsize))
+static inline PyMemberDef* _PyHeapType_GET_MEMBERS(PyHeapTypeObject *etype) {
+    return (PyMemberDef*)((char*)etype + Py_TYPE(etype)->tp_basicsize);
+}
 
 PyAPI_FUNC(PyObject *) _PyObject_LookupSpecial(PyObject *, PyObject *);
 
-- 
GitLab