From 2891c5f49ede5ac4db270ec49914072e45bba085 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 14 Jan 2026 12:11:44 +0300 Subject: [PATCH 01/14] Update pythoncapi_compat.h --- pythoncapi_compat.h | 441 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 412 insertions(+), 29 deletions(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 3320f68e..cdfdafa8 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -25,9 +25,6 @@ extern "C" { #if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION) # include "frameobject.h" // PyFrameObject, PyFrame_GetBack() #endif -#if PY_VERSION_HEX < 0x030C00A3 -# include // T_SHORT, READONLY -#endif #ifndef _Py_CAST @@ -1919,33 +1916,33 @@ PyLongWriter_Finish(PyLongWriter *writer) #if PY_VERSION_HEX < 0x030C00A3 -# define Py_T_SHORT T_SHORT -# define Py_T_INT T_INT -# define Py_T_LONG T_LONG -# define Py_T_FLOAT T_FLOAT -# define Py_T_DOUBLE T_DOUBLE -# define Py_T_STRING T_STRING -# define _Py_T_OBJECT T_OBJECT -# define Py_T_CHAR T_CHAR -# define Py_T_BYTE T_BYTE -# define Py_T_UBYTE T_UBYTE -# define Py_T_USHORT T_USHORT -# define Py_T_UINT T_UINT -# define Py_T_ULONG T_ULONG -# define Py_T_STRING_INPLACE T_STRING_INPLACE -# define Py_T_BOOL T_BOOL -# define Py_T_OBJECT_EX T_OBJECT_EX -# define Py_T_LONGLONG T_LONGLONG -# define Py_T_ULONGLONG T_ULONGLONG -# define Py_T_PYSSIZET T_PYSSIZET +# define Py_T_SHORT 0 +# define Py_T_INT 1 +# define Py_T_LONG 2 +# define Py_T_FLOAT 3 +# define Py_T_DOUBLE 4 +# define Py_T_STRING 5 +# define _Py_T_OBJECT 6 +# define Py_T_CHAR 7 +# define Py_T_BYTE 8 +# define Py_T_UBYTE 9 +# define Py_T_USHORT 10 +# define Py_T_UINT 11 +# define Py_T_ULONG 12 +# define Py_T_STRING_INPLACE 13 +# define Py_T_BOOL 14 +# define Py_T_OBJECT_EX 16 +# define Py_T_LONGLONG 17 +# define Py_T_ULONGLONG 18 +# define Py_T_PYSSIZET 19 # if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) -# define _Py_T_NONE T_NONE +# define _Py_T_NONE 20 # endif -# define Py_READONLY READONLY -# define Py_AUDIT_READ READ_RESTRICTED -# define _Py_WRITE_RESTRICTED PY_WRITE_RESTRICTED +# define Py_READONLY 1 +# define Py_AUDIT_READ 2 +# define _Py_WRITE_RESTRICTED 4 #endif @@ -1991,7 +1988,9 @@ static inline int Py_fclose(FILE *file) #endif -#if 0x03090000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION) +#if 0x03080000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION) +PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void); + static inline PyObject* PyConfig_Get(const char *name) { @@ -2032,7 +2031,9 @@ PyConfig_Get(const char *name) PYTHONCAPI_COMPAT_SPEC(module_search_paths, WSTR_LIST, "path"), PYTHONCAPI_COMPAT_SPEC(optimization_level, UINT, _Py_NULL), PYTHONCAPI_COMPAT_SPEC(parser_debug, BOOL, _Py_NULL), +#if 0x03090000 <= PY_VERSION_HEX PYTHONCAPI_COMPAT_SPEC(platlibdir, WSTR, "platlibdir"), +#endif PYTHONCAPI_COMPAT_SPEC(prefix, WSTR_OPT, "prefix"), PYTHONCAPI_COMPAT_SPEC(pycache_prefix, WSTR_OPT, "pycache_prefix"), PYTHONCAPI_COMPAT_SPEC(quiet, BOOL, _Py_NULL), @@ -2125,8 +2126,6 @@ PyConfig_Get(const char *name) return Py_NewRef(value); } - PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void); - const PyConfig *config = _Py_GetConfig(); void *member = (char *)config + spec->offset; switch (spec->type) { @@ -2211,6 +2210,99 @@ 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 + +// gh-128926 added PyUnstable_TryIncRef() and PyUnstable_EnableTryIncRef() to +// Python 3.14.0a5. Adapted from _Py_TryIncref() and _PyObject_SetMaybeWeakref(). +#if PY_VERSION_HEX < 0x030E00A5 +static inline int PyUnstable_TryIncRef(PyObject *op) +{ +#ifndef Py_GIL_DISABLED + if (Py_REFCNT(op) > 0) { + Py_INCREF(op); + return 1; + } + return 0; +#else + // _Py_TryIncrefFast() + uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local); + local += 1; + if (local == 0) { + // immortal + return 1; + } + if (_Py_IsOwnedByCurrentThread(op)) { + _Py_INCREF_STAT_INC(); + _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local); +#ifdef Py_REF_DEBUG + _Py_INCREF_IncRefTotal(); +#endif + return 1; + } + + // _Py_TryIncRefShared() + Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared); + for (;;) { + // If the shared refcount is zero and the object is either merged + // or may not have weak references, then we cannot incref it. + if (shared == 0 || shared == _Py_REF_MERGED) { + return 0; + } + + if (_Py_atomic_compare_exchange_ssize( + &op->ob_ref_shared, + &shared, + shared + (1 << _Py_REF_SHARED_SHIFT))) { +#ifdef Py_REF_DEBUG + _Py_INCREF_IncRefTotal(); +#endif + _Py_INCREF_STAT_INC(); + return 1; + } + } +#endif +} + +static inline void PyUnstable_EnableTryIncRef(PyObject *op) +{ +#ifdef Py_GIL_DISABLED + // _PyObject_SetMaybeWeakref() + if (_Py_IsImmortal(op)) { + return; + } + for (;;) { + Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared); + if ((shared & _Py_REF_SHARED_FLAG_MASK) != 0) { + // Nothing to do if it's in WEAKREFS, QUEUED, or MERGED states. + return; + } + if (_Py_atomic_compare_exchange_ssize( + &op->ob_ref_shared, &shared, shared | _Py_REF_MAYBE_WEAKREF)) { + return; + } + } +#else + (void)op; // unused argument +#endif +} +#endif + #if PY_VERSION_HEX < 0x030F0000 static inline PyObject* @@ -2277,6 +2369,297 @@ PySys_GetOptionalAttr(PyObject *name, PyObject **value) #endif // PY_VERSION_HEX < 0x030F00A1 +#if PY_VERSION_HEX < 0x030F00A1 +typedef struct PyBytesWriter { + char small_buffer[256]; + PyObject *obj; + Py_ssize_t size; +} PyBytesWriter; + +static inline Py_ssize_t +_PyBytesWriter_GetAllocated(PyBytesWriter *writer) +{ + if (writer->obj == NULL) { + return sizeof(writer->small_buffer); + } + else { + return PyBytes_GET_SIZE(writer->obj); + } +} + + +static inline int +_PyBytesWriter_Resize_impl(PyBytesWriter *writer, Py_ssize_t size, + int resize) +{ + int overallocate = resize; + assert(size >= 0); + + if (size <= _PyBytesWriter_GetAllocated(writer)) { + return 0; + } + + if (overallocate) { +#ifdef MS_WINDOWS + /* On Windows, overallocate by 50% is the best factor */ + if (size <= (PY_SSIZE_T_MAX - size / 2)) { + size += size / 2; + } +#else + /* On Linux, overallocate by 25% is the best factor */ + if (size <= (PY_SSIZE_T_MAX - size / 4)) { + size += size / 4; + } +#endif + } + + if (writer->obj != NULL) { + if (_PyBytes_Resize(&writer->obj, size)) { + return -1; + } + assert(writer->obj != NULL); + } + else { + writer->obj = PyBytes_FromStringAndSize(NULL, size); + if (writer->obj == NULL) { + return -1; + } + + if (resize) { + assert((size_t)size > sizeof(writer->small_buffer)); + memcpy(PyBytes_AS_STRING(writer->obj), + writer->small_buffer, + sizeof(writer->small_buffer)); + } + } + return 0; +} + +static inline void* +PyBytesWriter_GetData(PyBytesWriter *writer) +{ + if (writer->obj == NULL) { + return writer->small_buffer; + } + else { + return PyBytes_AS_STRING(writer->obj); + } +} + +static inline Py_ssize_t +PyBytesWriter_GetSize(PyBytesWriter *writer) +{ + return writer->size; +} + +static inline void +PyBytesWriter_Discard(PyBytesWriter *writer) +{ + if (writer == NULL) { + return; + } + + Py_XDECREF(writer->obj); + PyMem_Free(writer); +} + +static inline PyBytesWriter* +PyBytesWriter_Create(Py_ssize_t size) +{ + if (size < 0) { + PyErr_SetString(PyExc_ValueError, "size must be >= 0"); + return NULL; + } + + PyBytesWriter *writer = (PyBytesWriter*)PyMem_Malloc(sizeof(PyBytesWriter)); + if (writer == NULL) { + PyErr_NoMemory(); + return NULL; + } + + writer->obj = NULL; + writer->size = 0; + + if (size >= 1) { + if (_PyBytesWriter_Resize_impl(writer, size, 0) < 0) { + PyBytesWriter_Discard(writer); + return NULL; + } + writer->size = size; + } + return writer; +} + +static inline PyObject* +PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size) +{ + PyObject *result; + if (size == 0) { + result = PyBytes_FromStringAndSize("", 0); + } + else if (writer->obj != NULL) { + if (size != PyBytes_GET_SIZE(writer->obj)) { + if (_PyBytes_Resize(&writer->obj, size)) { + goto error; + } + } + result = writer->obj; + writer->obj = NULL; + } + else { + result = PyBytes_FromStringAndSize(writer->small_buffer, size); + } + PyBytesWriter_Discard(writer); + return result; + +error: + PyBytesWriter_Discard(writer); + return NULL; +} + +static inline PyObject* +PyBytesWriter_Finish(PyBytesWriter *writer) +{ + return PyBytesWriter_FinishWithSize(writer, writer->size); +} + +static inline PyObject* +PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf) +{ + Py_ssize_t size = (char*)buf - (char*)PyBytesWriter_GetData(writer); + if (size < 0 || size > _PyBytesWriter_GetAllocated(writer)) { + PyBytesWriter_Discard(writer); + PyErr_SetString(PyExc_ValueError, "invalid end pointer"); + return NULL; + } + + return PyBytesWriter_FinishWithSize(writer, size); +} + +static inline int +PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size) +{ + if (size < 0) { + PyErr_SetString(PyExc_ValueError, "size must be >= 0"); + return -1; + } + if (_PyBytesWriter_Resize_impl(writer, size, 1) < 0) { + return -1; + } + writer->size = size; + return 0; +} + +static inline int +PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t size) +{ + if (size < 0 && writer->size + size < 0) { + PyErr_SetString(PyExc_ValueError, "invalid size"); + return -1; + } + if (size > PY_SSIZE_T_MAX - writer->size) { + PyErr_NoMemory(); + return -1; + } + size = writer->size + size; + + if (_PyBytesWriter_Resize_impl(writer, size, 1) < 0) { + return -1; + } + writer->size = size; + return 0; +} + +static inline void* +PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer, + Py_ssize_t size, void *buf) +{ + Py_ssize_t pos = (char*)buf - (char*)PyBytesWriter_GetData(writer); + if (PyBytesWriter_Grow(writer, size) < 0) { + return NULL; + } + return (char*)PyBytesWriter_GetData(writer) + pos; +} + +static inline int +PyBytesWriter_WriteBytes(PyBytesWriter *writer, + const void *bytes, Py_ssize_t size) +{ + if (size < 0) { + size_t len = strlen((const char*)bytes); + if (len > (size_t)PY_SSIZE_T_MAX) { + PyErr_NoMemory(); + return -1; + } + size = (Py_ssize_t)len; + } + + Py_ssize_t pos = writer->size; + if (PyBytesWriter_Grow(writer, size) < 0) { + return -1; + } + char *buf = (char*)PyBytesWriter_GetData(writer); + memcpy(buf + pos, bytes, (size_t)size); + return 0; +} + +static inline int +PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...) + Py_GCC_ATTRIBUTE((format(printf, 2, 3))); + +static inline int +PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...) +{ + va_list vargs; + va_start(vargs, format); + PyObject *str = PyBytes_FromFormatV(format, vargs); + va_end(vargs); + + if (str == NULL) { + return -1; + } + int res = PyBytesWriter_WriteBytes(writer, + PyBytes_AS_STRING(str), + PyBytes_GET_SIZE(str)); + Py_DECREF(str); + return res; +} +#endif // PY_VERSION_HEX < 0x030F00A1 + + +#if PY_VERSION_HEX < 0x030F00A1 +static inline PyObject* +PyTuple_FromArray(PyObject *const *array, Py_ssize_t size) +{ + PyObject *tuple = PyTuple_New(size); + if (tuple == NULL) { + return NULL; + } + for (Py_ssize_t i=0; i < size; i++) { + PyObject *item = array[i]; + PyTuple_SET_ITEM(tuple, i, Py_NewRef(item)); + } + return tuple; +} +#endif + + +#if PY_VERSION_HEX < 0x030F00A1 +static inline Py_hash_t +PyUnstable_Unicode_GET_CACHED_HASH(PyObject *op) +{ +#ifdef PYPY_VERSION + (void)op; // unused argument + return -1; +#elif PY_VERSION_HEX >= 0x03000000 + return ((PyASCIIObject*)op)->hash; +#else + return ((PyUnicodeObject*)op)->hash; +#endif +} +#endif + + #ifdef __cplusplus } #endif From b6e969ae80367dcd0a3236a061880a8bf96d5cc7 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 14 Jan 2026 13:01:39 +0300 Subject: [PATCH 02/14] Add missing type casts for Py_XDECREF (for Limited API) --- gmp.c | 98 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/gmp.c b/gmp.c index dec32c6f..6e270ad1 100644 --- a/gmp.c +++ b/gmp.c @@ -516,7 +516,7 @@ MPZ_from_bytes(PyObject *obj, int is_little, int is_signed) if (is_little) { free(buffer); } - Py_XDECREF(res); + Py_XDECREF((PyObject *)res); return (MPZ_Object *)PyErr_NoMemory(); /* LCOV_EXCL_STOP */ } @@ -741,7 +741,7 @@ str(PyObject *self) #define CHECK_OP(u, a) \ if (MPZ_Check(a)) { \ u = (MPZ_Object *)a; \ - Py_INCREF(u); \ + Py_INCREF(a); \ } \ else if (PyLong_Check(a)) { \ u = MPZ_from_int(a); \ @@ -909,7 +909,7 @@ to_bool(PyObject *self) #define CHECK_OPv2(u, a) \ if (MPZ_Check(a)) { \ u = (MPZ_Object *)a; \ - Py_INCREF(u); \ + Py_INCREF(a); \ } \ else if (PyLong_Check(a)) { \ ; \ @@ -978,16 +978,16 @@ done: \ PyErr_NoMemory(); \ } \ end: \ - Py_XDECREF(u); \ - Py_XDECREF(v); \ + Py_XDECREF((PyObject *)u); \ + Py_XDECREF((PyObject *)v); \ return (PyObject *)res; \ fallback: \ - Py_XDECREF(u); \ - Py_XDECREF(v); \ + Py_XDECREF((PyObject *)u); \ + Py_XDECREF((PyObject *)v); \ Py_RETURN_NOTIMPLEMENTED; \ numbers: \ - Py_XDECREF(u); \ - Py_XDECREF(v); \ + Py_XDECREF((PyObject *)u); \ + Py_XDECREF((PyObject *)v); \ \ PyObject *uf, *vf, *rf; \ \ @@ -1045,8 +1045,8 @@ nb_divmod(PyObject *self, PyObject *other) if (!q || !r) { /* LCOV_EXCL_START */ - Py_XDECREF(q); - Py_XDECREF(r); + Py_XDECREF((PyObject *)q); + Py_XDECREF((PyObject *)r); return NULL; /* LCOV_EXCL_STOP */ } @@ -1072,15 +1072,15 @@ nb_divmod(PyObject *self, PyObject *other) /* LCOV_EXCL_START */ end: Py_DECREF(res); - Py_XDECREF(u); - Py_XDECREF(v); + Py_XDECREF((PyObject *)u); + Py_XDECREF((PyObject *)v); return NULL; /* LCOV_EXCL_STOP */ fallback: numbers: Py_DECREF(res); - Py_XDECREF(u); - Py_XDECREF(v); + Py_XDECREF((PyObject *)u); + Py_XDECREF((PyObject *)v); Py_RETURN_NOTIMPLEMENTED; } @@ -1278,16 +1278,16 @@ nb_truediv(PyObject *self, PyObject *other) PyErr_NoMemory(); /* LCOV_EXCL_LINE */ } end: - Py_XDECREF(u); - Py_XDECREF(v); + Py_XDECREF((PyObject *)u); + Py_XDECREF((PyObject *)v); return res; fallback: - Py_XDECREF(u); - Py_XDECREF(v); + Py_XDECREF((PyObject *)u); + Py_XDECREF((PyObject *)v); Py_RETURN_NOTIMPLEMENTED; numbers: - Py_XDECREF(u); - Py_XDECREF(v); + Py_XDECREF((PyObject *)u); + Py_XDECREF((PyObject *)v); PyObject *uf, *vf; @@ -1323,7 +1323,7 @@ nb_truediv(PyObject *self, PyObject *other) #define CHECK_OP_INT(u, a) \ if (MPZ_Check(a)) { \ u = (MPZ_Object *)a; \ - Py_INCREF(u); \ + Py_INCREF(a); \ } \ else { \ u = MPZ_from_int(a); \ @@ -1373,8 +1373,8 @@ nb_truediv(PyObject *self, PyObject *other) /* LCOV_EXCL_STOP */ \ } \ end: \ - Py_XDECREF(u); \ - Py_XDECREF(v); \ + Py_XDECREF((PyObject *)u); \ + Py_XDECREF((PyObject *)v); \ return (PyObject *)res; \ } @@ -1445,8 +1445,8 @@ done: \ /* LCOV_EXCL_STOP */ \ } \ end: \ - Py_XDECREF(u); \ - Py_XDECREF(v); \ + Py_XDECREF((PyObject *)u); \ + Py_XDECREF((PyObject *)v); \ return (PyObject *)res; \ } @@ -1566,16 +1566,16 @@ power(PyObject *self, PyObject *other, PyObject *module) Py_DECREF(w); } end: - Py_XDECREF(u); - Py_XDECREF(v); + Py_XDECREF((PyObject *)u); + Py_XDECREF((PyObject *)v); return (PyObject *)res; fallback: - Py_XDECREF(u); - Py_XDECREF(v); + Py_XDECREF((PyObject *)u); + Py_XDECREF((PyObject *)v); Py_RETURN_NOTIMPLEMENTED; numbers: - Py_XDECREF(u); - Py_XDECREF(v); + Py_XDECREF((PyObject *)u); + Py_XDECREF((PyObject *)v); PyObject *uf, *vf; @@ -2116,9 +2116,9 @@ gmp_gcdext(PyObject *Py_UNUSED(module), PyObject *const *args, if (!g || !s || !t) { /* LCOV_EXCL_START */ - Py_XDECREF(g); - Py_XDECREF(s); - Py_XDECREF(t); + Py_XDECREF((PyObject *)g); + Py_XDECREF((PyObject *)s); + Py_XDECREF((PyObject *)t); return PyErr_NoMemory(); /* LCOV_EXCL_STOP */ } @@ -2127,8 +2127,8 @@ gmp_gcdext(PyObject *Py_UNUSED(module), PyObject *const *args, zz_err ret = zz_gcdext(&x->z, &y->z, &g->z, &s->z, &t->z); - Py_XDECREF(x); - Py_XDECREF(y); + Py_XDECREF((PyObject *)x); + Py_XDECREF((PyObject *)y); if (ret == ZZ_MEM) { return PyErr_NoMemory(); /* LCOV_EXCL_LINE */ } @@ -2142,8 +2142,8 @@ gmp_gcdext(PyObject *Py_UNUSED(module), PyObject *const *args, Py_DECREF(g); Py_DECREF(s); Py_DECREF(t); - Py_XDECREF(x); - Py_XDECREF(y); + Py_XDECREF((PyObject *)x); + Py_XDECREF((PyObject *)y); return NULL; } @@ -2214,8 +2214,8 @@ gmp_isqrt_rem(PyObject *Py_UNUSED(module), PyObject *arg) if (!root || !rem) { /* LCOV_EXCL_START */ - Py_XDECREF(root); - Py_XDECREF(rem); + Py_XDECREF((PyObject *)root); + Py_XDECREF((PyObject *)rem); return NULL; /* LCOV_EXCL_STOP */ } @@ -2263,7 +2263,7 @@ gmp_fac(PyObject *Py_UNUSED(module), PyObject *arg) LONG_MAX); goto err; } - Py_XDECREF(x); + Py_XDECREF((PyObject *)x); if (zz_fac((zz_digit_t)n, &res->z)) { /* LCOV_EXCL_START */ PyErr_NoMemory(); @@ -2308,8 +2308,8 @@ gmp_comb(PyObject *self, PyObject *const *args, Py_ssize_t nargs) ULONG_MAX); goto err; } - Py_XDECREF(x); - Py_XDECREF(y); + Py_XDECREF((PyObject *)x); + Py_XDECREF((PyObject *)y); if (zz_bin((zz_digit_t)n, (zz_digit_t)k, &res->z)) { /* LCOV_EXCL_START */ PyErr_NoMemory(); @@ -2357,8 +2357,8 @@ gmp_perm(PyObject *self, PyObject *const *args, Py_ssize_t nargs) ULONG_MAX); goto err; } - Py_XDECREF(x); - Py_XDECREF(y); + Py_XDECREF((PyObject *)x); + Py_XDECREF((PyObject *)y); if (k > n) { return (PyObject *)res; } @@ -2559,8 +2559,8 @@ gmp__mpmath_normalize(PyObject *self, PyObject *const *args, Py_ssize_t nargs) &man->z, &exp->z, &bc)) { /* LCOV_EXCL_START */ - Py_XDECREF(man); - Py_XDECREF(exp); + Py_XDECREF((PyObject *)man); + Py_XDECREF((PyObject *)exp); return PyErr_NoMemory(); /* LCOV_EXCL_STOP */ } @@ -2643,7 +2643,7 @@ gmp__mpmath_create(PyObject *self, PyObject *const *args, Py_ssize_t nargs) { /* LCOV_EXCL_START */ Py_DECREF(man); - Py_XDECREF(exp); + Py_XDECREF((PyObject *)exp); return PyErr_NoMemory(); /* LCOV_EXCL_STOP */ } From 431c1e4e7ccaedd37de3c5e61453cdd274df7066 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 14 Jan 2026 13:03:27 +0300 Subject: [PATCH 03/14] Don't use Py_SETREF (for Limited API) XXX: use Py_XDECREF to fix test coverage --- gmp.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gmp.c b/gmp.c index 6e270ad1..50d7bac1 100644 --- a/gmp.c +++ b/gmp.c @@ -550,7 +550,7 @@ new_impl(PyTypeObject *Py_UNUSED(type), PyObject *arg, PyObject *base_arg) PyErr_Format(PyExc_TypeError, "__int__ returned non-int (type %.200s)", Py_TYPE(integer)->tp_name); - Py_DECREF(integer); + Py_XDECREF(integer); return NULL; } if (!PyLong_CheckExact(integer) @@ -562,7 +562,7 @@ new_impl(PyTypeObject *Py_UNUSED(type), PyObject *arg, PyObject *base_arg) "in a future version of Python.", Py_TYPE(integer)->tp_name)) { - Py_DECREF(integer); + Py_XDECREF(integer); return NULL; } } @@ -573,8 +573,10 @@ new_impl(PyTypeObject *Py_UNUSED(type), PyObject *arg, PyObject *base_arg) } } if (integer) { - Py_SETREF(integer, (PyObject *)MPZ_from_int(integer)); - return integer; + PyObject *mpz = (PyObject *)MPZ_from_int(integer); + + Py_DECREF(integer); + return (PyObject *)mpz; } } goto str; From 9b4d1c5502b2e842f909f3842fd8f5b84adb60f3 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 14 Jan 2026 13:15:14 +0300 Subject: [PATCH 04/14] Don't access directly slots (for Limited API) --- fmt.c | 19 +++++++------------ gmp.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/fmt.c b/fmt.c index ea5deee9..1fd5cb40 100644 --- a/fmt.c +++ b/fmt.c @@ -5,23 +5,18 @@ #if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON) static void -unknown_presentation_type(Py_UCS4 presentation_type, - const char* type_name) +unknown_presentation_type(Py_UCS4 presentation_type, PyObject* type_name) { /* %c might be out-of-range, hence the two cases. */ if (presentation_type > 32 && presentation_type < 128) { PyErr_Format(PyExc_ValueError, - "Unknown format code '%c' " - "for object of type '%.200s'", - (char)presentation_type, - type_name); + "Unknown format code '%c' for object of type '%U'", + (char)presentation_type, type_name); } else { PyErr_Format(PyExc_ValueError, - "Unknown format code '\\x%x' " - "for object of type '%.200s'", - (unsigned int)presentation_type, - type_name); + "Unknown format code '\\x%x' for object of type '%U'", + (unsigned int)presentation_type, type_name); } } @@ -301,7 +296,7 @@ parse_internal_render_format_spec(PyObject *obj, PyErr_Format(PyExc_ValueError, ("Invalid format specifier '%U' for object " "of type '%.200s'"), actual_format_spec, - Py_TYPE(obj)->tp_name); + PyType_GetName(Py_TYPE(obj))); Py_DECREF(actual_format_spec); } return 0; @@ -1141,7 +1136,7 @@ __format__(PyObject *self, PyObject *format_spec) return res; } default: - unknown_presentation_type(format.type, Py_TYPE(self)->tp_name); + unknown_presentation_type(format.type, PyType_GetName(Py_TYPE(self))); return NULL; } } diff --git a/gmp.c b/gmp.c index 50d7bac1..88e4fedb 100644 --- a/gmp.c +++ b/gmp.c @@ -526,6 +526,8 @@ MPZ_from_bytes(PyObject *obj, int is_little, int is_signed) return res; } +typedef PyObject * (*Py_nb_int_func)(PyObject *); + static PyObject * new_impl(PyTypeObject *Py_UNUSED(type), PyObject *arg, PyObject *base_arg) { @@ -540,27 +542,35 @@ new_impl(PyTypeObject *Py_UNUSED(type), PyObject *arg, PyObject *base_arg) } if (PyNumber_Check(arg)) { PyObject *integer = NULL; +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpedantic" +#endif + Py_nb_int_func nb_int = PyType_GetSlot(Py_TYPE(arg), Py_nb_int); +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif - if (Py_TYPE(arg)->tp_as_number->nb_int) { - integer = Py_TYPE(arg)->tp_as_number->nb_int(arg); + if (nb_int) { + integer = nb_int(arg); if (!integer) { return NULL; } if (!PyLong_Check(integer)) { PyErr_Format(PyExc_TypeError, - "__int__ returned non-int (type %.200s)", - Py_TYPE(integer)->tp_name); + "__int__ returned non-int (type %U)", + PyType_GetName(Py_TYPE(integer))); Py_XDECREF(integer); return NULL; } if (!PyLong_CheckExact(integer) && PyErr_WarnFormat(PyExc_DeprecationWarning, 1, - "__int__ returned non-int (type %.200s). " + "__int__ returned non-int (type %U). " "The ability to return an instance of a " "strict subclass of int " "is deprecated, and may be removed " "in a future version of Python.", - Py_TYPE(integer)->tp_name)) + PyType_GetName(Py_TYPE(integer)))) { Py_XDECREF(integer); return NULL; @@ -646,7 +656,7 @@ new(PyTypeObject *type, PyObject *args, PyObject *keywds) return NULL; /* LCOV_EXCL_LINE */ } - MPZ_Object *newobj = (MPZ_Object *)type->tp_alloc(type, 0); + MPZ_Object *newobj = (MPZ_Object *)PyType_GenericNew(type, NULL, NULL); if (!newobj) { /* LCOV_EXCL_START */ @@ -678,11 +688,12 @@ new(PyTypeObject *type, PyObject *args, PyObject *keywds) return new_impl(type, arg, base); } +typedef void (*Py_tp_free_func)(void *); + static void dealloc(PyObject *self) { MPZ_Object *u = (MPZ_Object *)self; - PyTypeObject *type = Py_TYPE(self); if (global.gmp_cache_size < CACHE_SIZE && (u->z).alloc <= MAX_CACHE_MPZ_DIGITS @@ -692,7 +703,21 @@ dealloc(PyObject *self) } else { zz_clear(&u->z); - type->tp_free(self); + if (MPZ_CheckExact(self)) { + PyObject_Free(self); + } + else { +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpedantic" +#endif + Py_tp_free_func tp_free = PyType_GetSlot(Py_TYPE(self), Py_tp_free); +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + + tp_free(self); + } } } @@ -1500,6 +1525,8 @@ zz_rshift(const zz_t *u, const zz_t *v, zz_t *w) BINOP_INT(lshift) BINOP_INT(rshift) +typedef PyObject * (*Py_nb_power_func)(PyObject *, PyObject *, PyObject *); + static PyObject * power(PyObject *self, PyObject *other, PyObject *module) { @@ -1524,7 +1551,18 @@ power(PyObject *self, PyObject *other, PyObject *module) Py_DECREF(uf); return NULL; } - resf = PyFloat_Type.tp_as_number->nb_power(uf, vf, Py_None); + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpedantic" +#endif + Py_nb_power_func nb_power = PyType_GetSlot(&PyFloat_Type, + Py_nb_power); +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + + resf = nb_power(uf, vf, Py_None); Py_DECREF(uf); Py_DECREF(vf); return resf; @@ -2668,10 +2706,9 @@ gmp__free_cache(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) for (size_t i = 0; i < global.gmp_cache_size; i++) { MPZ_Object *u = global.gmp_cache[i]; PyObject *self = (PyObject *)u; - PyTypeObject *type = Py_TYPE(self); zz_clear(&u->z); - type->tp_free(self); + PyObject_Free(self); } global.gmp_cache_size = 0; Py_RETURN_NONE; From adcc1bd07e25682c483b494dcb6153b6212225a3 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 14 Jan 2026 14:29:30 +0300 Subject: [PATCH 05/14] Use Limited API functions * PyBytes_AS_STRING -> PyBytes_AsString * PyByteArray_AS_STRING -> PyByteArray_AsString * PyUnicode_AsUTF8 -> PyUnicode_AsUTF8AndSize * PyStructSequence_SET_ITEM -> PyStructSequence_SetItem * PyTuple_GET_SIZE -> PyTuple_Size * PyTuple_GET_ITEM -> PyTuple_GetItem * PyTuple_SET_ITEM -> PyTuple_SetItem * PyUnicode_READ_CHAR -> PyUnicode_ReadChar * PyUnicode_GET_LENGTH -> PyUnicode_GetLength --- fmt.c | 16 +++++++++------- gmp.c | 31 +++++++++++++++---------------- utils.c | 8 +++++--- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/fmt.c b/fmt.c index 1fd5cb40..92906292 100644 --- a/fmt.c +++ b/fmt.c @@ -487,6 +487,7 @@ _PyUnicode_InsertThousandsGrouping(_PyUnicodeWriter *writer, assert(0 <= d_pos); assert(0 <= n_digits); assert(grouping != NULL); + assert(PyUnicode_Check(thousands_sep)); Py_ssize_t count = 0; Py_ssize_t n_zeros; @@ -504,7 +505,7 @@ _PyUnicode_InsertThousandsGrouping(_PyUnicodeWriter *writer, returns 0. */ GroupGenerator groupgen; GroupGenerator_init(&groupgen, grouping); - const Py_ssize_t thousands_sep_len = PyUnicode_GET_LENGTH(thousands_sep); + const Py_ssize_t thousands_sep_len = PyUnicode_GetLength(thousands_sep); /* if digits are not grouped, thousands separator should be an empty string */ @@ -513,8 +514,8 @@ _PyUnicode_InsertThousandsGrouping(_PyUnicodeWriter *writer, digits_pos = d_pos + n_digits; if (writer) { buffer_pos = writer->pos + n_buffer; - assert(buffer_pos <= PyUnicode_GET_LENGTH(writer->buffer)); - assert(digits_pos <= PyUnicode_GET_LENGTH(digits)); + assert(buffer_pos <= PyUnicode_GetLength(writer->buffer)); + assert(digits_pos <= PyUnicode_GetLength(digits)); } else { buffer_pos = n_buffer; @@ -581,7 +582,7 @@ calc_number_widths(NumberFieldWidths *spec, Py_ssize_t n_prefix, spec->n_digits = n_end - n_start - n_frac - n_remainder - (has_decimal?1:0); spec->n_lpadding = 0; spec->n_prefix = n_prefix; - spec->n_decimal = has_decimal ? PyUnicode_GET_LENGTH(locale->decimal_point) : 0; + spec->n_decimal = has_decimal ? PyUnicode_GetLength(locale->decimal_point) : 0; spec->n_remainder = n_remainder; spec->n_frac = n_frac; spec->n_spadding = 0; @@ -1027,6 +1028,7 @@ format_long_internal(MPZ_Object *value, const InternalFormatSpec *format) } /* Do the hard part, converting to a string in a given base */ tmp = MPZ_to_str(value, base, OPT_PREFIX); + assert(PyUnicode_Check(tmp)); if (tmp == NULL) { goto done; /* LCOV_EXCL_LINE */ } @@ -1036,11 +1038,11 @@ format_long_internal(MPZ_Object *value, const InternalFormatSpec *format) n_prefix = leading_chars_to_skip; } inumeric_chars = 0; - n_digits = PyUnicode_GET_LENGTH(tmp); + n_digits = PyUnicode_GetLength(tmp); prefix = inumeric_chars; /* Is a sign character present in the output? If so, remember it and skip it */ - if (PyUnicode_READ_CHAR(tmp, inumeric_chars) == '-') { + if (PyUnicode_ReadChar(tmp, inumeric_chars) == '-') { sign_char = '-'; ++prefix; ++leading_chars_to_skip; @@ -1093,7 +1095,7 @@ extern PyObject * to_float(PyObject *self); PyObject * __format__(PyObject *self, PyObject *format_spec) { - Py_ssize_t end = PyUnicode_GET_LENGTH(format_spec); + Py_ssize_t end = PyUnicode_GetLength(format_spec); if (!end) { return PyObject_Str(self); diff --git a/gmp.c b/gmp.c index 88e4fedb..2dca982c 100644 --- a/gmp.c +++ b/gmp.c @@ -418,7 +418,7 @@ MPZ_to_bytes(MPZ_Object *u, Py_ssize_t length, int is_little, int is_signed) return NULL; /* LCOV_EXCL_LINE */ } - unsigned char *buffer = (unsigned char *)PyBytes_AS_STRING(bytes); + unsigned char *buffer = (unsigned char *)PyBytes_AsString(bytes); zz_err ret = zz_get_bytes(&u->z, (size_t)length, is_signed, &buffer); if (ret == ZZ_OK) { @@ -614,10 +614,10 @@ new_impl(PyTypeObject *Py_UNUSED(type), PyObject *arg, PyObject *base_arg) const char *string; if (PyByteArray_Check(arg)) { - string = PyByteArray_AS_STRING(arg); + string = PyByteArray_AsString(arg); } else { - string = PyBytes_AS_STRING(arg); + string = PyBytes_AsString(arg); } PyObject *str = PyUnicode_FromString(string); @@ -646,7 +646,7 @@ static PyObject * new(PyTypeObject *type, PyObject *args, PyObject *keywds) { static char *kwlist[] = {"", "base", NULL}; - Py_ssize_t argc = PyTuple_GET_SIZE(args); + Py_ssize_t argc = PyTuple_Size(args); PyObject *arg, *base = Py_None; if (type != &MPZ_Type) { @@ -677,7 +677,7 @@ new(PyTypeObject *type, PyObject *args, PyObject *keywds) return (PyObject *)MPZ_new(); } if (argc == 1 && !keywds) { - arg = PyTuple_GET_ITEM(args, 0); + arg = PyTuple_GetItem(args, 0); return new_impl(type, arg, Py_None); } if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|O", @@ -1093,8 +1093,8 @@ nb_divmod(PyObject *self, PyObject *other) } Py_DECREF(u); Py_DECREF(v); - PyTuple_SET_ITEM(res, 0, (PyObject *)q); - PyTuple_SET_ITEM(res, 1, (PyObject *)r); + (void)PyTuple_SetItem(res, 0, (PyObject *)q); + (void)PyTuple_SetItem(res, 1, (PyObject *)r); return res; /* LCOV_EXCL_START */ end: @@ -1767,7 +1767,7 @@ to_bytes(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *arg = args[argidx[1]]; if (PyUnicode_Check(arg)) { - const char *byteorder = PyUnicode_AsUTF8(arg); + const char *byteorder = PyUnicode_AsUTF8AndSize(arg, NULL); if (!byteorder) { return NULL; /* LCOV_EXCL_LINE */ @@ -1826,7 +1826,7 @@ from_bytes(PyTypeObject *Py_UNUSED(type), PyObject *const *args, PyObject *arg = args[argidx[1]]; if (PyUnicode_Check(arg)) { - const char *byteorder = PyUnicode_AsUTF8(arg); + const char *byteorder = PyUnicode_AsUTF8AndSize(arg, NULL); if (!byteorder) { return NULL; /* LCOV_EXCL_LINE */ @@ -2446,9 +2446,10 @@ get_round_mode(PyObject *rndstr) return (zz_rnd)-1; } - Py_UCS4 rndchr = PyUnicode_READ_CHAR(rndstr, 0); + Py_UCS4 rndchr = PyUnicode_ReadChar(rndstr, 0); zz_rnd rnd = ZZ_RNDN; + assert(rndchr != -1); switch (rndchr) { case (Py_UCS4)'f': rnd = ZZ_RNDD; @@ -2785,12 +2786,10 @@ gmp_exec(PyObject *m) if (mpz_info == NULL) { return -1; /* LCOV_EXCL_LINE */ } - PyStructSequence_SET_ITEM(mpz_info, 0, - PyLong_FromLong(bits_per_digit)); - PyStructSequence_SET_ITEM(mpz_info, 1, - PyLong_FromLong(layout->digit_size)); - PyStructSequence_SET_ITEM(mpz_info, 2, - PyLong_FromUInt64(zz_get_bitcnt_max())); + PyStructSequence_SetItem(mpz_info, 0, PyLong_FromLong(bits_per_digit)); + PyStructSequence_SetItem(mpz_info, 1, PyLong_FromLong(layout->digit_size)); + PyStructSequence_SetItem(mpz_info, 2, + PyLong_FromUInt64(zz_get_bitcnt_max())); if (PyErr_Occurred()) { /* LCOV_EXCL_START */ fail1: diff --git a/utils.c b/utils.c index 5164a4f1..7e1d3cff 100644 --- a/utils.c +++ b/utils.c @@ -17,7 +17,7 @@ gmp_parse_pyargs(const gmp_pyargs *fnargs, Py_ssize_t argidx[], Py_ssize_t nkws = 0; if (kwnames) { - nkws = PyTuple_GET_SIZE(kwnames); + nkws = PyTuple_Size(kwnames); } if (nkws > fnargs->maxpos) { PyErr_Format(PyExc_TypeError, @@ -33,7 +33,8 @@ gmp_parse_pyargs(const gmp_pyargs *fnargs, Py_ssize_t argidx[], return -1; } for (Py_ssize_t i = 0; i < nkws; i++) { - const char *kwname = PyUnicode_AsUTF8(PyTuple_GET_ITEM(kwnames, i)); + const char *kwname = PyUnicode_AsUTF8AndSize(PyTuple_GetItem(kwnames, + i), NULL); Py_ssize_t j = 0; for (; j < fnargs->maxargs; j++) { @@ -65,11 +66,12 @@ gmp_parse_pyargs(const gmp_pyargs *fnargs, Py_ssize_t argidx[], PyObject * gmp_PyUnicode_TransformDecimalAndSpaceToASCII(PyObject *unicode) { + assert(PyUnicode_Check(unicode)); if (PyUnicode_IS_ASCII(unicode)) { return Py_NewRef(unicode); } - Py_ssize_t len = PyUnicode_GET_LENGTH(unicode); + Py_ssize_t len = PyUnicode_GetLength(unicode); PyObject *result = PyUnicode_New(len, 127); if (result == NULL) { From 1acdd2458d0f53a1e251d280a27ca3fe4e900037 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 14 Jan 2026 16:22:45 +0300 Subject: [PATCH 06/14] Use Py_CompileString and PyEval_EvalCode --- gmp.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gmp.c b/gmp.c index 2dca982c..e8be7823 100644 --- a/gmp.c +++ b/gmp.c @@ -2823,8 +2823,14 @@ gmp_exec(PyObject *m) "gmp.__all__ = ['comb', 'factorial', 'gcd', 'isqrt',\n" " 'lcm', 'mpz', 'perm']\n" "gmp.__version__ = imp.version('python-gmp')\n"); - PyObject *res = PyRun_String(str, Py_file_input, ns, ns); + PyObject *codeobj = Py_CompileString(str, "", Py_file_input); + PyObject *res; + if (!codeobj) { + goto fail1; /* LCOV_EXCL_LINE */ + } + res = PyEval_EvalCode(codeobj, ns, NULL); + Py_DECREF(codeobj); Py_DECREF(ns); if (!res) { goto fail1; /* LCOV_EXCL_LINE */ From f350596cf992e4bb7cc323cfb9a8890c251b9334 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 14 Jan 2026 16:53:13 +0300 Subject: [PATCH 07/14] Don't use PyHASH_MODULUS macro --- gmp.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/gmp.c b/gmp.c index e8be7823..f5a99b26 100644 --- a/gmp.c +++ b/gmp.c @@ -25,6 +25,7 @@ _Thread_local gmp_global global = { }; uint8_t bits_per_digit; +Py_hash_t pyhash_modulus; static MPZ_Object * MPZ_new(void) @@ -889,13 +890,13 @@ hash(PyObject *self) zz_digit_t digits[1]; zz_t w = {false, 1, 1, digits}; - assert((int64_t)INT64_MAX > PyHASH_MODULUS); - (void)zz_div(&u->z, (int64_t)PyHASH_MODULUS, NULL, &w); + assert((int64_t)INT64_MAX > pyhash_modulus); + (void)zz_div(&u->z, (int64_t)pyhash_modulus, NULL, &w); Py_hash_t r = w.size ? (Py_hash_t)w.digits[0] : 0; if (zz_isneg(&u->z) && r) { - r = -((Py_hash_t)PyHASH_MODULUS - r); + r = -(pyhash_modulus - r); } if (r == -1) { r = -2; @@ -2831,11 +2832,36 @@ gmp_exec(PyObject *m) } res = PyEval_EvalCode(codeobj, ns, NULL); Py_DECREF(codeobj); - Py_DECREF(ns); if (!res) { goto fail1; /* LCOV_EXCL_LINE */ } Py_DECREF(res); + + PyObject *sys_mod = PyImport_ImportModuleLevel("sys", NULL, NULL, NULL, 0); + + if (!sys_mod || PyDict_SetItemString(ns, "sys", sys_mod) < 0) { + /* LCOV_EXCL_START */ + Py_DECREF(ns); + goto fail1; + /* LCOV_EXCL_STOP */ + } + codeobj = Py_CompileString("sys.hash_info.modulus", "", + Py_eval_input); + if (!codeobj || !(res = PyEval_EvalCode(codeobj, ns, NULL))) { + /* LCOV_EXCL_START */ + Py_XDECREF(codeobj); + Py_DECREF(ns); + goto fail1; + /* LCOV_EXCL_STOP */ + } + Py_DECREF(codeobj); + Py_DECREF(sys_mod); + Py_DECREF(ns); + pyhash_modulus = (Py_hash_t)PyLong_AsSsize_t(res); + Py_DECREF(res); + if (pyhash_modulus == -1) { + goto fail1; /* LCOV_EXCL_LINE */ + } return 0; } From 62fc751e6666cb6b6a22c7c11cb47e46b6401f7b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 07:36:26 +0300 Subject: [PATCH 08/14] Enable test_func_api() unconditionally --- tests/test_functions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_functions.py b/tests/test_functions.py index efe4a53a..d9121efd 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -1,7 +1,6 @@ import inspect import math import platform -import sys import gmp import pytest @@ -235,8 +234,8 @@ def test_interfaces(): gmp._free_cache() # just for coverage -@pytest.mark.skipif(platform.python_implementation() != "CPython" - or sys.version_info < (3, 11), +# See pypy/pypy#5368 and oracle/graalpython#593 +@pytest.mark.skipif(platform.python_implementation() != "CPython", reason="no way to specify a signature") def test_func_api(): for fn in ["comb", "factorial", "gcd", "isqrt", "lcm", "perm"]: From b37e4f58d27bd96f87887fcdee5542d1d51f9a41 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 08:50:46 +0300 Subject: [PATCH 09/14] Provide explicit signatures for METH_NOARGS methods Autogenerated signatures available since 3.13 (python/cpython#107794) --- gmp.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/gmp.c b/gmp.c index f5a99b26..d83c004e 100644 --- a/gmp.c +++ b/gmp.c @@ -2045,24 +2045,32 @@ The signed argument indicates whether two’s complement is used."); extern PyObject * __format__(PyObject *self, PyObject *format_spec); +/* Explicit signatures for METH_NOARGS methods are redundant + since CPython 3.13. */ + static PyMethodDef methods[] = { {"conjugate", (PyCFunction)plus, METH_NOARGS, - "Returns self."}, + "conjugate($self, /)\n--\n\nReturns self."}, {"bit_length", bit_length, METH_NOARGS, - "Number of bits necessary to represent self in binary."}, + ("bit_length($self, /)\n--\n\nNumber of bits necessary " + "to represent self in binary.")}, {"bit_count", bit_count, METH_NOARGS, - ("Number of ones in the binary representation of the " - "absolute value of self.")}, + ("bit_count($self, /)\n--\n\nNumber of ones in the binary " + "representation of the absolute value of self.")}, {"to_bytes", (PyCFunction)to_bytes, METH_FASTCALL | METH_KEYWORDS, to_bytes__doc__}, {"from_bytes", (PyCFunction)from_bytes, METH_FASTCALL | METH_KEYWORDS | METH_CLASS, from_bytes__doc__}, {"as_integer_ratio", as_integer_ratio, METH_NOARGS, - ("Return a pair of integers, whose ratio is equal to self.\n\n" + ("as_integer_ratio($self, /)\n--\n\nReturn a pair of integers, " + "whose ratio is equal to self.\n\n" "The ratio is in lowest terms and has a positive denominator.")}, - {"__trunc__", (PyCFunction)plus, METH_NOARGS, "Returns self."}, - {"__floor__", (PyCFunction)plus, METH_NOARGS, "Returns self."}, - {"__ceil__", (PyCFunction)plus, METH_NOARGS, "Returns self."}, + {"__trunc__", (PyCFunction)plus, METH_NOARGS, + "__trunc__($self, /)\n--\n\nReturns self."}, + {"__floor__", (PyCFunction)plus, METH_NOARGS, + "__floor__($self, /)\n--\n\nReturns self."}, + {"__ceil__", (PyCFunction)plus, METH_NOARGS, + "__ceil__($self, /)\n--\n\nReturns self."}, {"__round__", (PyCFunction)__round__, METH_FASTCALL, ("__round__($self, ndigits=0, /)\n--\n\n" "Round self to to the closest multiple of 10**-ndigits\n\n" @@ -2075,8 +2083,9 @@ static PyMethodDef methods[] = { ("__format__($self, format_spec, /)\n--\n\n" "Convert self to a string according to format_spec.")}, {"__sizeof__", __sizeof__, METH_NOARGS, - "Returns size of self in memory, in bytes."}, - {"is_integer", is_integer, METH_NOARGS, "Returns True."}, + "__sizeof__($self, /)\n--\n\nReturns size of self in memory, in bytes."}, + {"is_integer", is_integer, METH_NOARGS, + "is_integer($self, /)\n--\n\nReturns True."}, {"digits", (PyCFunction)digits, METH_FASTCALL | METH_KEYWORDS, ("digits($self, base=10)\n--\n\n" "Return string representing self in the given base.\n\n" From e831dcba62e75b3b5b82c46f7597c8667ad1a013 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 09:03:13 +0300 Subject: [PATCH 10/14] Drop to/from_bytes__doc__ --- gmp.c | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/gmp.c b/gmp.c index d83c004e..d93dd137 100644 --- a/gmp.c +++ b/gmp.c @@ -2019,30 +2019,6 @@ digits(PyObject *self, PyObject *const *args, Py_ssize_t nargs, return MPZ_to_str((MPZ_Object *)self, base, 0); } -PyDoc_STRVAR( - to_bytes__doc__, - "to_bytes($self, /, length=1, byteorder=\'big\', *, signed=False)\n--\n\n\ -Return an array of bytes representing self.\n\n\ -The integer is represented using length bytes. An OverflowError is\n\ -raised if self is not representable with the given number of bytes.\n\n\ -The byteorder argument determines the byte order used to represent self.\n\ -Accepted values are \'big\' and \'little\', when the most significant\n\ -byte is at the beginning or at the end of the byte array, respectively.\n\n\ -The signed argument determines whether two\'s complement is used to\n\ -represent self. If signed is False and a negative integer is given,\n\ -an OverflowError is raised."); -PyDoc_STRVAR( - from_bytes__doc__, - "from_bytes($type, /, bytes, byteorder=\'big\', *, signed=False)\n--\n\n\ -Return the integer represented by the given array of bytes.\n\n\ -The argument bytes must either be a bytes-like object or an iterable\n\ -producing bytes.\n\n\ -The byteorder argument determines the byte order used to represent the\n\ -integer. Accepted values are \'big\' and \'little\', when the most\n\ -significant byte is at the beginning or at the end of the byte array,\n\ -respectively.\n\n\ -The signed argument indicates whether two’s complement is used."); - extern PyObject * __format__(PyObject *self, PyObject *format_spec); /* Explicit signatures for METH_NOARGS methods are redundant @@ -2058,9 +2034,27 @@ static PyMethodDef methods[] = { ("bit_count($self, /)\n--\n\nNumber of ones in the binary " "representation of the absolute value of self.")}, {"to_bytes", (PyCFunction)to_bytes, METH_FASTCALL | METH_KEYWORDS, - to_bytes__doc__}, + "to_bytes($self, /, length=1, byteorder=\'big\', *, signed=False)\n--\n\n\ +Return an array of bytes representing self.\n\n\ +The integer is represented using length bytes. An OverflowError is\n\ +raised if self is not representable with the given number of bytes.\n\n\ +The byteorder argument determines the byte order used to represent self.\n\ +Accepted values are \'big\' and \'little\', when the most significant\n\ +byte is at the beginning or at the end of the byte array, respectively.\n\n\ +The signed argument determines whether two\'s complement is used to\n\ +represent self. If signed is False and a negative integer is given,\n\ +an OverflowError is raised."}, {"from_bytes", (PyCFunction)from_bytes, - METH_FASTCALL | METH_KEYWORDS | METH_CLASS, from_bytes__doc__}, + METH_FASTCALL | METH_KEYWORDS | METH_CLASS, + "from_bytes($type, /, bytes, byteorder=\'big\', *, signed=False)\n--\n\n\ +Return the integer represented by the given array of bytes.\n\n\ +The argument bytes must either be a bytes-like object or an iterable\n\ +producing bytes.\n\n\ +The byteorder argument determines the byte order used to represent the\n\ +integer. Accepted values are \'big\' and \'little\', when the most\n\ +significant byte is at the beginning or at the end of the byte array,\n\ +respectively.\n\n\ +The signed argument indicates whether two’s complement is used."}, {"as_integer_ratio", as_integer_ratio, METH_NOARGS, ("as_integer_ratio($self, /)\n--\n\nReturn a pair of integers, " "whose ratio is equal to self.\n\n" From 2444e9fce356145bcedb8508b65173745f36fa0b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 09:46:08 +0300 Subject: [PATCH 11/14] Enable test_int_api() for CPython < 3.13 --- tests/test_mpz.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/test_mpz.py b/tests/test_mpz.py index e46dcbb2..f1701c42 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -1071,8 +1071,8 @@ def f(n): assert all(f.result() == 1 for f in futures) -@pytest.mark.skipif(platform.python_implementation() != "CPython" - or sys.version_info < (3, 13), +# See pypy/pypy#5368 and oracle/graalpython#593 +@pytest.mark.skipif(platform.python_implementation() != "CPython", reason="no way to specify a signature") def test_int_api(): for meth in dir(int): @@ -1080,4 +1080,12 @@ def test_int_api(): if meth.startswith("_") or not callable(m): continue mz = getattr(mpz, meth) - assert inspect.signature(m) == inspect.signature(mz) + try: + m_sig = inspect.signature(m) + except ValueError: + # Signatures for some METH_NOARGS builtins were + # unavailable til python/cpython#107794. + if sys.version_info < (3, 13): + continue + mz_sig = inspect.signature(mz) + assert m_sig == mz_sig From 66578169d28714590aa6044d469b9079a6719e8d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 17 Jan 2026 13:48:48 +0300 Subject: [PATCH 12/14] Check __format__() argument type --- fmt.c | 7 +++++++ tests/test_mpz.py | 2 ++ 2 files changed, 9 insertions(+) diff --git a/fmt.c b/fmt.c index 92906292..a97f4217 100644 --- a/fmt.c +++ b/fmt.c @@ -1095,6 +1095,13 @@ extern PyObject * to_float(PyObject *self); PyObject * __format__(PyObject *self, PyObject *format_spec) { + if (!PyUnicode_Check(format_spec)) { + PyErr_Format(PyExc_TypeError, + "__format__() argument must be str, not %U", + PyType_GetName(Py_TYPE(format_spec))); + return NULL; + } + Py_ssize_t end = PyUnicode_GetLength(format_spec); if (!end) { diff --git a/tests/test_mpz.py b/tests/test_mpz.py index f1701c42..b05611e8 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -121,6 +121,8 @@ def test_format_bulk(x, fmt): def test_format_interface(): mx = mpz(123) + with pytest.raises(TypeError, match="int"): + mx.__format__(321) with pytest.raises(ValueError, match="Unknown format code"): format(mx, "q") if platform.python_implementation() != "PyPy": # XXX: pypy/pypy#5311 From 723a834544115a207f517402687401ff182e3ead Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 18 Jan 2026 08:14:22 +0300 Subject: [PATCH 13/14] Keep Python.h include in utils.h --- gmp.c | 1 - mpz.h | 29 +---------------------------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/gmp.c b/gmp.c index d93dd137..f66e920f 100644 --- a/gmp.c +++ b/gmp.c @@ -1,5 +1,4 @@ #include "mpz.h" -#include "utils.h" #include #include diff --git a/mpz.h b/mpz.h index 294aa354..6569a80c 100644 --- a/mpz.h +++ b/mpz.h @@ -1,34 +1,7 @@ #ifndef MPZ_H #define MPZ_H -#if defined(__MINGW32__) && defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Warray-bounds" -#endif -#if defined(__clang__) -# pragma GCC diagnostic push /* XXX: pypy/pypy#5312 */ -# pragma GCC diagnostic ignored "-Wnewline-eof" -#endif -#if defined(__GNUC__) || defined(__clang__) -# pragma GCC diagnostic push /* XXX: oracle/graalpython#580 */ -# pragma GCC diagnostic ignored "-Wsign-conversion" -#endif - -#include "pythoncapi_compat.h" - -#define PY_SSIZE_T_CLEAN -#include - -#if defined(__GNUC__) || defined(__clang__) -# pragma GCC diagnostic pop -#endif -#if defined(__clang__) -# pragma GCC diagnostic pop -#endif -#if defined(__MINGW32__) && defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - +#include "utils.h" #include "zz/zz.h" typedef struct { From 942df5090c0ad92d81e702041fb4916257aa2005 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 18 Jan 2026 08:54:23 +0300 Subject: [PATCH 14/14] Use PyType_GetFullyQualifiedName to print types in errors --- fmt.c | 6 +++--- gmp.c | 4 ++-- tests/test_mpz.py | 3 +++ utils.c | 40 ++++++++++++++++++++++++++++++++++++++++ utils.h | 5 +++++ 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/fmt.c b/fmt.c index a97f4217..d2f23ad7 100644 --- a/fmt.c +++ b/fmt.c @@ -296,7 +296,7 @@ parse_internal_render_format_spec(PyObject *obj, PyErr_Format(PyExc_ValueError, ("Invalid format specifier '%U' for object " "of type '%.200s'"), actual_format_spec, - PyType_GetName(Py_TYPE(obj))); + PyType_GetFullyQualifiedName(Py_TYPE(obj))); Py_DECREF(actual_format_spec); } return 0; @@ -1098,7 +1098,7 @@ __format__(PyObject *self, PyObject *format_spec) if (!PyUnicode_Check(format_spec)) { PyErr_Format(PyExc_TypeError, "__format__() argument must be str, not %U", - PyType_GetName(Py_TYPE(format_spec))); + PyType_GetFullyQualifiedName(Py_TYPE(format_spec))); return NULL; } @@ -1145,7 +1145,7 @@ __format__(PyObject *self, PyObject *format_spec) return res; } default: - unknown_presentation_type(format.type, PyType_GetName(Py_TYPE(self))); + unknown_presentation_type(format.type, PyType_GetFullyQualifiedName(Py_TYPE(self))); return NULL; } } diff --git a/gmp.c b/gmp.c index f66e920f..c694d7cd 100644 --- a/gmp.c +++ b/gmp.c @@ -559,7 +559,7 @@ new_impl(PyTypeObject *Py_UNUSED(type), PyObject *arg, PyObject *base_arg) if (!PyLong_Check(integer)) { PyErr_Format(PyExc_TypeError, "__int__ returned non-int (type %U)", - PyType_GetName(Py_TYPE(integer))); + PyType_GetFullyQualifiedName(Py_TYPE(integer))); Py_XDECREF(integer); return NULL; } @@ -570,7 +570,7 @@ new_impl(PyTypeObject *Py_UNUSED(type), PyObject *arg, PyObject *base_arg) "strict subclass of int " "is deprecated, and may be removed " "in a future version of Python.", - PyType_GetName(Py_TYPE(integer)))) + PyType_GetFullyQualifiedName(Py_TYPE(integer)))) { Py_XDECREF(integer); return NULL; diff --git a/tests/test_mpz.py b/tests/test_mpz.py index b05611e8..1f7072c3 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -1,3 +1,4 @@ +import decimal import inspect import locale import math @@ -298,6 +299,8 @@ def test_mpz_interface(): mpz(with_int(int2(123))) with pytest.raises(TypeError): mpz(with_int(1j)) + with pytest.raises(TypeError): + mpz(with_int(decimal.Decimal(123))) assert mpz(with_index(123)) == 123 with pytest.raises(RuntimeError): diff --git a/utils.c b/utils.c index 7e1d3cff..d9b33fd8 100644 --- a/utils.c +++ b/utils.c @@ -112,3 +112,43 @@ gmp_PyUnicode_TransformDecimalAndSpaceToASCII(PyObject *unicode) } return result; } + +#if PY_VERSION_HEX < 0x030D00A0 +static PyObject * +PyType_GetModuleName(PyTypeObject *type) +{ + return PyObject_GetAttrString((PyObject *)type, "__module__"); +} + +PyObject * +_PyType_GetFullyQualifiedName(PyTypeObject *type) +{ + PyObject *qualname = PyType_GetQualName(type); + if (qualname == NULL) { + return NULL; /* LCOV_EXCL_LINE */ + } + + PyObject *module = PyType_GetModuleName(type); + if (module == NULL) { + /* LCOV_EXCL_START */ + Py_DECREF(qualname); + return NULL; + /* LCOV_EXCL_STOP */ + } + + PyObject *result; + + if (PyUnicode_Check(module) + && !PyUnicode_EqualToUTF8(module, "builtins") + && !PyUnicode_EqualToUTF8(module, "__main__")) + { + result = PyUnicode_FromFormat("%U.%U", module, qualname); + } + else { + result = Py_NewRef(qualname); + } + Py_XDECREF(module); + Py_XDECREF(qualname); + return result; +} +#endif diff --git a/utils.h b/utils.h index e156042c..f13b23c1 100644 --- a/utils.h +++ b/utils.h @@ -43,4 +43,9 @@ int gmp_parse_pyargs(const gmp_pyargs *fnargs, Py_ssize_t argidx[], PyObject * gmp_PyUnicode_TransformDecimalAndSpaceToASCII(PyObject *unicode); +#if PY_VERSION_HEX < 0x030D00A0 +extern PyObject * _PyType_GetFullyQualifiedName(PyTypeObject *type); +#define PyType_GetFullyQualifiedName _PyType_GetFullyQualifiedName +#endif + #endif /* UTILS_H */