https://github.com/python/cpython/commit/e2ca4dc94c553c34578d229c8254b55464045641
commit: e2ca4dc94c553c34578d229c8254b55464045641
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-09-15T23:16:43+02:00
summary:
gh-157495: Implement Py_MIN/MAX/ABS using typeof() (#157496)
On GCC and clang, implement Py_MIN(), Py_MAX() and Py_ABS()
using typeof() and statement expression to only evaluate each
argument once and detect signed/unsigned comparison.
Test also these macros in test_cext and test_cppext to test different
compiler flags and test the limited C API.
files:
A Misc/NEWS.d/next/C_API/2026-09-14-16-10-23.gh-issue-157495.LzMLtE.rst
M Include/pymacro.h
M Lib/test/test_cext/extension.c
M Lib/test/test_cppext/extension.cpp
M Modules/_ssl.c
M Modules/_testcapimodule.c
M Modules/cjkcodecs/cjkcodecs.h
M Objects/obmalloc.c
diff --git a/Include/pymacro.h b/Include/pymacro.h
index 7ecce44a0d2a42..f31c9505a6a812 100644
--- a/Include/pymacro.h
+++ b/Include/pymacro.h
@@ -107,15 +107,40 @@
# endif
#endif
+#if ((defined(__GNUC__) || defined(__clang__)) \
+ && defined(_Py_TYPEOF) && !defined(__cplusplus))
+ // Implement Py_MIN(), Py_MAX() and Py_ABS() using _Py_TYPEOF() and
+ // statement expression to only evaluate each argument only once.
+ // It cannot be used in C++: ISO C++ forbids braced-groups within
+ // expressions. Statement expression is a GNU extension. Use __extension__
+ // to avoid compiler warning in pedantic mode.
-/* Minimum value between x and y */
-#define Py_MIN(x, y) (((x) > (y)) ? (y) : (x))
-
-/* Maximum value between x and y */
-#define Py_MAX(x, y) (((x) > (y)) ? (x) : (y))
+ /* Minimum value between x and y */
+# define Py_MIN(x, y) \
+ __extension__ \
+ ({ _Py_TYPEOF (x) _x = (x); \
+ _Py_TYPEOF (y) _y = (y); \
+ _x < _y ? _x : _y; })
+ /* Maximum value between x and y */
+# define Py_MAX(x, y) \
+ __extension__ \
+ ({ _Py_TYPEOF (x) _x = (x); \
+ _Py_TYPEOF (y) _y = (y); \
+ _x > _y ? _x : _y; })
+ /* Absolute value of the number x */
+# define Py_ABS(x) \
+ __extension__ \
+ ({ _Py_TYPEOF (x) _x = (x); \
+ _x < 0 ? -_x : _x; })
+#else
+ /* Minimum value between x and y */
+# define Py_MIN(x, y) (((x) > (y)) ? (y) : (x))
+ /* Maximum value between x and y */
+# define Py_MAX(x, y) (((x) > (y)) ? (x) : (y))
+ /* Absolute value of the number x */
+# define Py_ABS(x) ((x) < 0 ? -(x) : (x))
+#endif
-/* Absolute value of the number x */
-#define Py_ABS(x) ((x) < 0 ? -(x) : (x))
/* Safer implementation that avoids an undefined behavior for the minimal
value of the signed integer type if its absolute value is larger than
the maximal value of the signed integer type (in the two's complement
diff --git a/Lib/test/test_cext/extension.c b/Lib/test/test_cext/extension.c
index b58c889ba8bcb3..0a26a0d8753711 100644
--- a/Lib/test/test_cext/extension.c
+++ b/Lib/test/test_cext/extension.c
@@ -91,10 +91,15 @@ _testcext_exec(PyObject *module)
if (!result) return -1;
Py_DECREF(result);
- // test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR()
+ // Test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR()
Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int));
assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0);
+ // Test Py_MIN(), Py_MAX(), Py_ABS()
+ assert(Py_MIN(5, 11) == 5);
+ assert(Py_MAX(5, 11) == 11);
+ assert(Py_ABS(-5) == 5);
+
// Test Py_CLEAR(): use typeof()/__typeof__() if available, or memcpy()
obj = Py_None;
Py_CLEAR(obj);
diff --git a/Lib/test/test_cppext/extension.cpp
b/Lib/test/test_cppext/extension.cpp
index 7496581c6756da..1ff56d0e7fd25a 100644
--- a/Lib/test/test_cppext/extension.cpp
+++ b/Lib/test/test_cppext/extension.cpp
@@ -302,6 +302,11 @@ _testcppext_exec(PyObject *module)
Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int));
assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0);
+ // Test Py_MIN(), Py_MAX(), Py_ABS()
+ assert(Py_MIN(5, 11) == 5);
+ assert(Py_MAX(5, 11) == 11);
+ assert(Py_ABS(-5) == 5);
+
// Test Py_CLEAR(): use typeof()/__typeof__() if available, or memcpy()
PyObject *obj = Py_None;
Py_CLEAR(obj);
diff --git
a/Misc/NEWS.d/next/C_API/2026-09-14-16-10-23.gh-issue-157495.LzMLtE.rst
b/Misc/NEWS.d/next/C_API/2026-09-14-16-10-23.gh-issue-157495.LzMLtE.rst
new file mode 100644
index 00000000000000..0e89171bba8657
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2026-09-14-16-10-23.gh-issue-157495.LzMLtE.rst
@@ -0,0 +1,3 @@
+If ``typeof()`` is available, implement :c:macro:`Py_MIN`, :c:macro:`Py_MAX`
+and :c:macro:`Py_ABS` using ``typeof()`` and statement expression to only
+evaluate each argument once. Patch by Victor Stinner.
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index 73b32c1d86c72e..9bddb9ce62d5b9 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -5883,7 +5883,8 @@ _ssl_MemoryBIO_read_impl(PySSLMemoryBIO *self, int len)
{
int avail, nbytes;
- avail = (int)Py_MIN(BIO_ctrl_pending(self->bio), INT_MAX);
+ size_t pending = BIO_ctrl_pending(self->bio);
+ avail = (int)Py_MIN(pending, (size_t)INT_MAX);
if ((len < 0) || (len > avail))
len = avail;
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index eb769294fd21db..577bb14df13405 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2045,12 +2045,22 @@ test_macros(PyObject *self, PyObject *Py_UNUSED(args))
static_assert(1 == 1, "bug");
Py_BUILD_ASSERT(1 == 1);
-
// Py_MIN(), Py_MAX(), Py_ABS()
assert(Py_MIN(5, 11) == 5);
assert(Py_MAX(5, 11) == 11);
assert(Py_ABS(-5) == 5);
+#if ((defined(__GNUC__) || defined(__clang__)) \
+ && defined(_Py_TYPEOF) && !defined(__cplusplus))
+ // When _Py_TYPEOF() is available, arguments are only evaluated once
+ int x = 5, y = 11;
+ assert(Py_MIN(++x, ++y) == 6);
+ x = 5; y = 11;
+ assert(Py_MAX(++x, ++y) == 12);
+ x = -5;
+ assert(Py_ABS(--x) == 6);
+#endif
+
// Py_STRINGIFY()
assert(strcmp(Py_STRINGIFY(123), "123") == 0);
diff --git a/Modules/cjkcodecs/cjkcodecs.h b/Modules/cjkcodecs/cjkcodecs.h
index 41e1287c865070..b4f316ce0c06e4 100644
--- a/Modules/cjkcodecs/cjkcodecs.h
+++ b/Modules/cjkcodecs/cjkcodecs.h
@@ -163,7 +163,7 @@ get_module_state(PyObject *mod)
do { \
Py_UCS4 _c1 = (c1); \
Py_UCS4 _c2 = (c2); \
- if (_PyUnicodeWriter_Prepare(writer, 2, Py_MAX(_c1, c2)) < 0) \
+ if (_PyUnicodeWriter_Prepare(writer, 2, Py_MAX(_c1, _c2)) < 0) \
return MBERR_EXCEPTION; \
PyUnicode_WRITE(writer->kind, writer->data, writer->pos, _c1); \
PyUnicode_WRITE(writer->kind, writer->data, writer->pos + 1, _c2); \
diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c
index 0947d47c8a5558..9a41a422467223 100644
--- a/Objects/obmalloc.c
+++ b/Objects/obmalloc.c
@@ -3255,10 +3255,10 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t
nbytes)
}
else {
size_t i = original_nbytes - ERASED_SIZE;
- memcpy(data, save, Py_MIN(nbytes, ERASED_SIZE));
+ memcpy(data, save, Py_MIN(nbytes, (size_t)ERASED_SIZE));
if (nbytes > i) {
memcpy(data + i, &save[ERASED_SIZE],
- Py_MIN(nbytes - i, ERASED_SIZE));
+ Py_MIN(nbytes - i, (size_t)ERASED_SIZE));
}
}
#endif
_______________________________________________
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]