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/15] 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/15] 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 99f9088611f990bf7363f1910fa11c745cb65a0a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 14 Jan 2026 13:03:27 +0300 Subject: [PATCH 03/15] Don't use Py_SETREF (for Limited API) --- gmp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gmp.c b/gmp.c index 6e270ad1..d32c990c 100644 --- a/gmp.c +++ b/gmp.c @@ -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 c7d9e7c84b8c8d4366ac14ff22a2c81ba1aa23fb Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 14 Jan 2026 13:15:14 +0300 Subject: [PATCH 04/15] 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 d32c990c..5596288d 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_DECREF(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_DECREF(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 08eea727277a928a9510544d0378d3484dae01c5 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 14 Jan 2026 14:29:30 +0300 Subject: [PATCH 05/15] 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 5596288d..07bfa469 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 a395ac08ce0250220276ab6a11c8826de2a160b4 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 14 Jan 2026 16:22:45 +0300 Subject: [PATCH 06/15] 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 07bfa469..840f4cae 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 c30ee7da1346e603161e14d121ff5db08469b4d3 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 14 Jan 2026 16:53:13 +0300 Subject: [PATCH 07/15] 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 840f4cae..62c7e504 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 6e9297a1a85f7ecf5d27515e493654395565c406 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 05:16:22 +0300 Subject: [PATCH 08/15] Skip pytest-xdist on GraalVM --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 42706960..77caccd6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,8 @@ Documentation = "https://python-gmp.readthedocs.io/en/latest/" [project.optional-dependencies] tests = ["pytest", "hypothesis", "mpmath"] -ci = ["python-gmp[tests]", "pytest-xdist"] +ci = ["python-gmp[tests]", + "pytest-xdist; platform_python_implementation != 'GraalVM'"] docs = ["sphinx>=8.2"] develop = ["python-gmp[tests,docs]", "pre-commit", "pyperf"] From 1b7d2df2e183118f3e20ddecc52f273626ac7f00 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 06:30:49 +0300 Subject: [PATCH 09/15] XXX requires-python = ">= 3.11" --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 77caccd6..3a29565b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ classifiers = ["Development Status :: 4 - Beta", "Operating System :: MacOS", "Topic :: Scientific/Engineering :: Mathematics", "Topic :: Software Development :: Libraries :: Python Modules"] -requires-python = ">= 3.9" +requires-python = ">= 3.11" [project.urls] Homepage = "https://github.com/diofant/python-gmp" From aed36fdf6d7e0e308588a7a302581585e20dfc87 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 06:46:49 +0300 Subject: [PATCH 10/15] Revert "Skip pytest-xdist on GraalVM" This reverts commit 6e9297a1a85f7ecf5d27515e493654395565c406. --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3a29565b..a17002a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,8 +46,7 @@ Documentation = "https://python-gmp.readthedocs.io/en/latest/" [project.optional-dependencies] tests = ["pytest", "hypothesis", "mpmath"] -ci = ["python-gmp[tests]", - "pytest-xdist; platform_python_implementation != 'GraalVM'"] +ci = ["python-gmp[tests]", "pytest-xdist"] docs = ["sphinx>=8.2"] develop = ["python-gmp[tests,docs]", "pre-commit", "pyperf"] From fa25a0d11e268ff6bfe5f5b68389d4cba471f3ae Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 07:36:26 +0300 Subject: [PATCH 11/15] Enable test_func_api() on CPython < 3.11 --- tests/test_functions.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_functions.py b/tests/test_functions.py index efe4a53a..3278a1d2 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -235,11 +235,16 @@ 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"]: f = getattr(math, fn) fz = getattr(gmp, fn) - assert inspect.signature(f) == inspect.signature(fz) + f_sig = inspect.signature(f) + if fn == "factorial" and sys.version_info < (3, 11): + # Parameter x was renamed in python/cpython#32377 + x = f_sig.parameters["x"] + f_sig = inspect.Signature([x.replace(name="n")]) + assert inspect.signature(fz) == f_sig From dd7bbb8e046a805947850e85b2eddf5ace6bddd0 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 08:50:46 +0300 Subject: [PATCH 12/15] 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 62c7e504..506cc734 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 9020e3ade4d60b5c91f9152876a68d942204ce50 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 09:03:13 +0300 Subject: [PATCH 13/15] Drop to/from_bytes__doc__ --- gmp.c | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/gmp.c b/gmp.c index 506cc734..c239227e 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 3c8807de5e7ee434568cb3cb212daf9171d8f356 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 09:46:08 +0300 Subject: [PATCH 14/15] Enable test_int_api() for CPython < 3.13 --- tests/test_mpz.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/tests/test_mpz.py b/tests/test_mpz.py index e46dcbb2..108bbe10 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,24 @@ 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: + if sys.version_info < (3, 13): + continue + mz_sig = inspect.signature(mz) + if sys.version_info < (3, 11): + # to/from_bytes got new defaults in python/cpython#28265 + if meth == "to_bytes" and sys.version_info < (3, 11): + length = m_sig.parameters["length"] + byteorder = m_sig.parameters["byteorder"] + m_sig = inspect.Signature([m_sig.parameters["self"], + length.replace(default=1), + byteorder.replace(default="big"), + m_sig.parameters["signed"]]) + if meth == "from_bytes" and sys.version_info < (3, 11): + byteorder = m_sig.parameters["byteorder"] + m_sig = inspect.Signature([m_sig.parameters["bytes"], + byteorder.replace(default="big"), + m_sig.parameters["signed"]]) + assert m_sig == mz_sig From 94e872d7fcdbe537765d6855430e5758a8fe5c9b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 10:43:24 +0300 Subject: [PATCH 15/15] Restore coverage (XXX) --- gmp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gmp.c b/gmp.c index c239227e..d93dd137 100644 --- a/gmp.c +++ b/gmp.c @@ -561,7 +561,7 @@ new_impl(PyTypeObject *Py_UNUSED(type), PyObject *arg, PyObject *base_arg) PyErr_Format(PyExc_TypeError, "__int__ returned non-int (type %U)", PyType_GetName(Py_TYPE(integer))); - Py_DECREF(integer); + Py_XDECREF(integer); return NULL; } if (!PyLong_CheckExact(integer) @@ -573,7 +573,7 @@ new_impl(PyTypeObject *Py_UNUSED(type), PyObject *arg, PyObject *base_arg) "in a future version of Python.", PyType_GetName(Py_TYPE(integer)))) { - Py_DECREF(integer); + Py_XDECREF(integer); return NULL; } }