From 4583d4dead1991ed169d4450da2ed030c16213a8 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 (cherry picked from commit 2891c5f49ede5ac4db270ec49914072e45bba085) --- 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 983738bca27e314340a03f0c09dd669e57005b1e 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) (cherry picked from commit b6e969ae80367dcd0a3236a061880a8bf96d5cc7) # Conflicts: # gmp.c --- gmp.c | 439 +++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 404 insertions(+), 35 deletions(-) diff --git a/gmp.c b/gmp.c index 3422350e..c77a480f 100644 --- a/gmp.c +++ b/gmp.c @@ -436,7 +436,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 */ } @@ -661,7 +661,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); \ @@ -793,7 +793,26 @@ to_bool(PyObject *self) return !zz_iszero(&((MPZ_Object *)self)->z); } +<<<<<<< HEAD #define BINOP_INT(suff) \ +======= +#define CHECK_OPv2(u, a) \ + if (MPZ_Check(a)) { \ + u = (MPZ_Object *)a; \ + Py_INCREF(a); \ + } \ + else if (PyLong_Check(a)) { \ + ; \ + } \ + else if (Number_Check(a)) { \ + goto numbers; \ + } \ + else { \ + goto fallback; \ + } + +#define BINOP(suff, slot) \ +>>>>>>> b6e969a (Add missing type casts for Py_XDECREF (for Limited API)) static PyObject * \ nb_##suff(PyObject *self, PyObject *other) \ { \ @@ -822,10 +841,11 @@ to_bool(PyObject *self) /* LCOV_EXCL_STOP */ \ } \ end: \ - Py_XDECREF(u); \ - Py_XDECREF(v); \ + Py_XDECREF((PyObject *)u); \ + Py_XDECREF((PyObject *)v); \ return (PyObject *)res; \ fallback: \ +<<<<<<< HEAD numbers: \ Py_XDECREF(u); \ Py_XDECREF(v); \ @@ -901,6 +921,42 @@ to_bool(PyObject *self) Py_DECREF(uf); \ Py_DECREF(vf); \ return res; \ +======= + Py_XDECREF((PyObject *)u); \ + Py_XDECREF((PyObject *)v); \ + Py_RETURN_NOTIMPLEMENTED; \ + numbers: \ + Py_XDECREF((PyObject *)u); \ + Py_XDECREF((PyObject *)v); \ + \ + PyObject *uf, *vf, *rf; \ + \ + if (Number_Check(self)) { \ + uf = self; \ + Py_INCREF(uf); \ + } \ + else { \ + uf = to_float(self); \ + if (!uf) { \ + return NULL; \ + } \ + } \ + if (Number_Check(other)) { \ + vf = other; \ + Py_INCREF(vf); \ + } \ + else { \ + vf = to_float(other); \ + if (!vf) { \ + Py_DECREF(uf); \ + return NULL; \ + } \ + } \ + rf = slot(uf, vf); \ + Py_DECREF(uf); \ + Py_DECREF(vf); \ + return rf; \ +>>>>>>> b6e969a (Add missing type casts for Py_XDECREF (for Limited API)) } BINOP(add, PyNumber_Add) @@ -939,8 +995,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 */ } @@ -966,15 +1022,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; } @@ -1006,16 +1062,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; @@ -1081,7 +1137,7 @@ BINOP_INT(xor) #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); \ @@ -1090,6 +1146,175 @@ BINOP_INT(xor) } \ } \ +<<<<<<< HEAD +======= +#define CHECK_OP_INTv2(u, a) \ + if (MPZ_Check(a)) { \ + u = (MPZ_Object *)a; \ + Py_INCREF(u); \ + } \ + else if (PyLong_Check(a)) { \ + ; \ + } \ + else { \ + goto end; \ + } \ + +#define BINOP_INT(suff) \ + static PyObject * \ + nb_##suff(PyObject *self, PyObject *other) \ + { \ + MPZ_Object *u = NULL, *v = NULL, *res = NULL; \ + \ + CHECK_OP_INT(u, self); \ + CHECK_OP_INT(v, other); \ + \ + res = MPZ_new(); \ + zz_err ret = ZZ_OK; \ + \ + if (!res || (ret = zz_##suff(&u->z, &v->z, &res->z))) { \ + /* LCOV_EXCL_START */ \ + Py_CLEAR(res); \ + if (ret == ZZ_VAL) { \ + PyErr_SetString(PyExc_ValueError, \ + "negative shift count"); \ + } \ + else if (ret == ZZ_BUF) { \ + PyErr_SetString(PyExc_OverflowError, \ + "too many digits in integer"); \ + } \ + else { \ + PyErr_NoMemory(); \ + } \ + /* LCOV_EXCL_STOP */ \ + } \ + end: \ + Py_XDECREF((PyObject *)u); \ + Py_XDECREF((PyObject *)v); \ + return (PyObject *)res; \ + } + +#define BINOP_INTv2(suff) \ + static PyObject * \ + nb_##suff(PyObject *self, PyObject *other) \ + { \ + MPZ_Object *u = NULL, *v = NULL, *res = NULL; \ + \ + CHECK_OP_INTv2(u, self); \ + CHECK_OP_INTv2(v, other); \ + \ + res = MPZ_new(); \ + if (!res) { \ + goto end; \ + } \ + \ + zz_err ret = ZZ_OK; \ + \ + if (!u) { \ + int error = PyLong_IsNegative(self) || zz_isneg(&v->z); \ + \ + if (!error) { \ + int64_t temp = PyLong_AsSdigit_t(self, &error); \ + \ + if (!error) { \ + ret = zz_i64_##suff(temp, &v->z, &res->z); \ + goto done; \ + } \ + } \ + u = MPZ_from_int(self); \ + if (!u) { \ + goto end; \ + } \ + } \ + if (!v) { \ + int error = zz_isneg(&u->z) || PyLong_IsNegative(other); \ + \ + if (!error) { \ + int64_t temp = PyLong_AsSdigit_t(other, &error); \ + \ + if (!error) { \ + ret = zz_##suff##_i64(&u->z, temp, &res->z); \ + goto done; \ + } \ + } \ + v = MPZ_from_int(other); \ + if (!v) { \ + goto end; \ + } \ + } \ + ret = zz_##suff(&u->z, &v->z, &res->z); \ +done: \ + if (ret) { \ + /* LCOV_EXCL_START */ \ + Py_CLEAR(res); \ + if (ret == ZZ_VAL) { \ + PyErr_SetString(PyExc_ValueError, \ + "negative shift count"); \ + } \ + else if (ret == ZZ_BUF) { \ + PyErr_SetString(PyExc_OverflowError, \ + "too many digits in integer"); \ + } \ + else { \ + PyErr_NoMemory(); \ + } \ + /* LCOV_EXCL_STOP */ \ + } \ + end: \ + Py_XDECREF((PyObject *)u); \ + Py_XDECREF((PyObject *)v); \ + return (PyObject *)res; \ + } + +static inline zz_err +zz_and_i64(const zz_t *u, int64_t v, zz_t *w) +{ + if (zz_iszero(u) || !v) { + return zz_set(0, w); + } + assert(!zz_isneg(u) && v > 0); + return zz_set((int64_t)(u->digits[0] & (zz_digit_t)v), w); +} +#define zz_i64_and(x, y, r) zz_and_i64((y), (x), (r)) + +BINOP_INTv2(and) +BINOP_INT(or) +BINOP_INT(xor) + +static inline zz_err +zz_lshift(const zz_t *u, const zz_t *v, zz_t *w) +{ + if (zz_isneg(v)) { + return ZZ_VAL; + } + + int64_t shift; + + if (zz_get(v, &shift)) { + return ZZ_BUF; + } + return zz_mul_2exp(u, (zz_bitcnt_t)shift, w); +} + +static inline zz_err +zz_rshift(const zz_t *u, const zz_t *v, zz_t *w) +{ + if (zz_isneg(v)) { + return ZZ_VAL; + } + + int64_t shift; + + if (zz_get(v, &shift)) { + return zz_set(zz_isneg(u) ? -1 : 0, w); + } + return zz_quo_2exp(u, (zz_bitcnt_t)shift, w); +} + +BINOP_INT(lshift) +BINOP_INT(rshift) + +>>>>>>> b6e969a (Add missing type casts for Py_XDECREF (for Limited API)) static PyObject * power(PyObject *self, PyObject *other, PyObject *module) { @@ -1158,16 +1383,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; @@ -1709,9 +1934,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 */ } @@ -1720,8 +1945,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) { PyErr_NoMemory(); /* LCOV_EXCL_LINE */ } @@ -1735,8 +1960,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; } @@ -1776,8 +2001,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 */ } @@ -1840,9 +2065,153 @@ gmp_isqrt_rem(PyObject *Py_UNUSED(module), PyObject *arg) return NULL; \ } +<<<<<<< HEAD MAKE_MPZ_UI_FUN(fac) MAKE_MPZ_UI_FUN(fac2) MAKE_MPZ_UI_FUN(fib) +======= + int64_t n; + + if (zz_get(&x->z, &n) || n > LONG_MAX) { + PyErr_Format(PyExc_OverflowError, + "fac() argument should not exceed %ld", + LONG_MAX); + goto err; + } + Py_XDECREF((PyObject *)x); + if (zz_fac((zz_digit_t)n, &res->z)) { + /* LCOV_EXCL_START */ + PyErr_NoMemory(); + goto err; + /* LCOV_EXCL_STOP */ + } + return (PyObject *)res; +err: +end: + Py_DECREF(res); + return NULL; +} + +static PyObject * +gmp_comb(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (nargs != 2) { + PyErr_SetString(PyExc_TypeError, "two arguments required"); + return NULL; + } + + MPZ_Object *x, *y, *res = MPZ_new(); + + if (!res) { + return NULL; /* LCOV_EXCL_LINE */ + } + CHECK_OP_INT(x, args[0]); + CHECK_OP_INT(y, args[1]); + if (zz_isneg(&x->z) || zz_isneg(&y->z)) { + PyErr_SetString(PyExc_ValueError, + "comb() not defined for negative values"); + goto err; + } + + int64_t n, k; + + if ((zz_get(&x->z, &n) || n > ULONG_MAX) + || (zz_get(&y->z, &k) || k > ULONG_MAX)) + { + PyErr_Format(PyExc_OverflowError, + "comb() arguments should not exceed %ld", + ULONG_MAX); + goto err; + } + 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(); + goto err; + /* LCOV_EXCL_STOP */ + } + return (PyObject *)res; +err: +end: + Py_DECREF(res); + return NULL; +} + +static PyObject * +gmp_perm(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (nargs > 2 || nargs < 1) { + PyErr_SetString(PyExc_TypeError, "one or two arguments required"); + return NULL; + } + if (nargs == 1) { + return gmp_fac(self, args[0]); + } + + MPZ_Object *x, *y, *res = MPZ_new(); + + if (!res) { + return NULL; /* LCOV_EXCL_LINE */ + } + CHECK_OP_INT(x, args[0]); + CHECK_OP_INT(y, args[1]); + if (zz_isneg(&x->z) || zz_isneg(&y->z)) { + PyErr_SetString(PyExc_ValueError, + "perm() not defined for negative values"); + goto err; + } + + int64_t n, k; + + if ((zz_get(&x->z, &n) || n > ULONG_MAX) + || (zz_get(&y->z, &k) || k > ULONG_MAX)) + { + PyErr_Format(PyExc_OverflowError, + "perm() arguments should not exceed %ld", + ULONG_MAX); + goto err; + } + Py_XDECREF((PyObject *)x); + Py_XDECREF((PyObject *)y); + if (k > n) { + return (PyObject *)res; + } + + MPZ_Object *den = MPZ_new(); + + if (!den) { + /* LCOV_EXCL_START */ + PyErr_NoMemory(); + goto err; + /* LCOV_EXCL_STOP */ + } + if (zz_fac((zz_digit_t)n, &res->z) + || zz_fac((zz_digit_t)(n-k), &den->z) + || zz_div(&res->z, &den->z, &res->z, NULL)) + { + /* LCOV_EXCL_START */ + Py_DECREF(den); + PyErr_NoMemory(); + goto err; + /* LCOV_EXCL_STOP */ + } + Py_DECREF(den); + return (PyObject *)res; +err: +end: + Py_DECREF(res); + return NULL; +} + +typedef enum { + ZZ_RNDD = 0, + ZZ_RNDN = 1, + ZZ_RNDU = 2, + ZZ_RNDZ = 3, + ZZ_RNDA = 4, +} zz_rnd; +>>>>>>> b6e969a (Add missing type casts for Py_XDECREF (for Limited API)) static zz_rnd get_round_mode(PyObject *rndstr) @@ -1912,8 +2281,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 */ } @@ -1996,7 +2365,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 944e752cd7f3775edff3dc019ad046545827cff5 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 (cherry picked from commit 431c1e4e7ccaedd37de3c5e61453cdd274df7066) --- gmp.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gmp.c b/gmp.c index c77a480f..4134d5e5 100644 --- a/gmp.c +++ b/gmp.c @@ -470,7 +470,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) @@ -482,7 +482,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; } } @@ -493,8 +493,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 bf2d142776e50f61eed97a891f1ff4cdd98b0472 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) (cherry picked from commit 9b4d1c5502b2e842f909f3842fd8f5b84adb60f3) # Conflicts: # gmp.c --- fmt.c | 19 +++++++----------- gmp.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/fmt.c b/fmt.c index 80a17d78..0fc87ff8 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 4134d5e5..25336397 100644 --- a/gmp.c +++ b/gmp.c @@ -446,6 +446,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) { @@ -460,27 +462,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; @@ -566,7 +576,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 */ @@ -598,11 +608,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_LIMBS @@ -612,7 +623,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); + } } } @@ -1316,7 +1341,12 @@ zz_rshift(const zz_t *u, const zz_t *v, zz_t *w) BINOP_INT(lshift) BINOP_INT(rshift) +<<<<<<< HEAD >>>>>>> b6e969a (Add missing type casts for Py_XDECREF (for Limited API)) +======= +typedef PyObject * (*Py_nb_power_func)(PyObject *, PyObject *, PyObject *); + +>>>>>>> 9b4d1c5 (Don't access directly slots (for Limited API)) static PyObject * power(PyObject *self, PyObject *other, PyObject *module) { @@ -1341,7 +1371,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; @@ -2390,10 +2431,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 394a8767cd6bef0c753e221ec7d088161b9db487 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 (cherry picked from commit adcc1bd07e25682c483b494dcb6153b6212225a3) # Conflicts: # gmp.c --- fmt.c | 16 +++++++++------- gmp.c | 31 ++++++++++++++++++++++--------- utils.c | 8 +++++--- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/fmt.c b/fmt.c index 0fc87ff8..17ddc056 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 25336397..3098890c 100644 --- a/gmp.c +++ b/gmp.c @@ -366,8 +366,13 @@ MPZ_to_bytes(MPZ_Object *u, Py_ssize_t length, int is_little, int is_signed) return NULL; /* LCOV_EXCL_LINE */ } +<<<<<<< HEAD uint8_t *buffer = (uint8_t *)PyBytes_AS_STRING(bytes); zz_err ret = zz_to_bytes(&u->z, (size_t)length, is_signed, &buffer); +======= + unsigned char *buffer = (unsigned char *)PyBytes_AsString(bytes); + zz_err ret = zz_get_bytes(&u->z, (size_t)length, is_signed, &buffer); +>>>>>>> adcc1bd (Use Limited API functions) if (ret == ZZ_OK) { if (is_little && length) { @@ -534,10 +539,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); @@ -566,7 +571,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) { @@ -597,7 +602,7 @@ new(PyTypeObject *type, PyObject *args, PyObject *keywds) return (PyObject *)MPZ_new(0); } 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", @@ -1043,8 +1048,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: @@ -1587,7 +1592,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 */ @@ -1646,7 +1651,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 */ @@ -2265,9 +2270,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; @@ -2511,6 +2517,7 @@ gmp_exec(PyObject *m) if (gmp_info == NULL) { return -1; /* LCOV_EXCL_LINE */ } +<<<<<<< HEAD PyStructSequence_SET_ITEM(gmp_info, 0, PyLong_FromLong(info.bits_per_limb)); PyStructSequence_SET_ITEM(gmp_info, 1, @@ -2524,6 +2531,12 @@ gmp_exec(PyObject *m) info.version[0], info.version[1], info.version[2])); +======= + 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())); +>>>>>>> adcc1bd (Use Limited API functions) if (PyErr_Occurred()) { /* LCOV_EXCL_START */ Py_DECREF(gmp_info); diff --git a/utils.c b/utils.c index 995b7aff..fe85d806 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 7774a59f20bfffaabc113afe434d5ea9171e756f 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 (cherry picked from commit 1acdd2458d0f53a1e251d280a27ca3fe4e900037) # Conflicts: # gmp.c --- gmp.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gmp.c b/gmp.c index 3098890c..d8156fc3 100644 --- a/gmp.c +++ b/gmp.c @@ -2567,9 +2567,19 @@ gmp_exec(PyObject *m) "gmp.fac = gmp.factorial\n" "gmp.__all__ = ['factorial', 'gcd', 'isqrt', 'mpz']\n" "gmp.__version__ = imp.version('python-gmp')\n"); +<<<<<<< HEAD PyObject *res = PyRun_String(str, Py_file_input, ns, ns); +======= + PyObject *codeobj = Py_CompileString(str, "", Py_file_input); + PyObject *res; +>>>>>>> 1acdd24 (Use Py_CompileString and PyEval_EvalCode) + if (!codeobj) { + goto fail1; /* LCOV_EXCL_LINE */ + } + res = PyEval_EvalCode(codeobj, ns, NULL); + Py_DECREF(codeobj); Py_DECREF(ns); if (!res) { return -1; /* LCOV_EXCL_LINE */ From c250c015197968abcf42759e6f6d6950309c7a7e 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 (cherry picked from commit f350596cf992e4bb7cc323cfb9a8890c251b9334) # Conflicts: # gmp.c --- gmp.c | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/gmp.c b/gmp.c index d8156fc3..640727a7 100644 --- a/gmp.c +++ b/gmp.c @@ -28,6 +28,12 @@ _Thread_local gmp_global global = { .gmp_cache_size = 0, }; +<<<<<<< HEAD +======= +uint8_t bits_per_digit; +Py_hash_t pyhash_modulus; + +>>>>>>> f350596 (Don't use PyHASH_MODULUS macro) static MPZ_Object * MPZ_new(int64_t size) { @@ -777,17 +783,27 @@ hash(PyObject *self) bool negative = zz_isneg(&u->z); +<<<<<<< HEAD if (negative) { (void)zz_abs(&u->z, &u->z); } +======= + assert((int64_t)INT64_MAX > pyhash_modulus); + (void)zz_div(&u->z, (int64_t)pyhash_modulus, NULL, &w); +>>>>>>> f350596 (Don't use PyHASH_MODULUS macro) Py_hash_t r; +<<<<<<< HEAD assert(-(uint64_t)INT64_MIN > PyHASH_MODULUS); (void)zz_rem_u64(&u->z, (uint64_t)PyHASH_MODULUS, (uint64_t *)&r); if (negative) { (void)zz_neg(&u->z, &u->z); r = -r; +======= + if (zz_isneg(&u->z) && r) { + r = -(pyhash_modulus - r); +>>>>>>> f350596 (Don't use PyHASH_MODULUS macro) } if (r == -1) { r = -2; @@ -2580,11 +2596,36 @@ gmp_exec(PyObject *m) } res = PyEval_EvalCode(codeobj, ns, NULL); Py_DECREF(codeobj); - Py_DECREF(ns); if (!res) { return -1; /* 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 26c8d441f1fb17a1a48e4697c5a5cba78fa8f7b0 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 (cherry picked from commit 62fc751e6666cb6b6a22c7c11cb47e46b6401f7b) # Conflicts: # tests/test_functions.py --- tests/test_functions.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_functions.py b/tests/test_functions.py index 09cd4cf5..1d457013 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -1,4 +1,8 @@ import math +<<<<<<< HEAD +======= +import platform +>>>>>>> 62fc751 (Enable test_func_api() unconditionally) import gmp import pytest @@ -162,3 +166,16 @@ def test_interfaces(): with pytest.raises(ValueError, match="invalid rounding mode specified"): _mpmath_normalize(1, mpz(111), 11, 12, 13, 1j) gmp._free_cache() # just for coverage +<<<<<<< HEAD +======= + + +# 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) +>>>>>>> 62fc751 (Enable test_func_api() unconditionally) From 3f05f3d11946f26a2d045983e3bddb4928844d86 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) (cherry picked from commit b37e4f58d27bd96f87887fcdee5542d1d51f9a41) --- gmp.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/gmp.c b/gmp.c index 640727a7..8021dfe3 100644 --- a/gmp.c +++ b/gmp.c @@ -1886,24 +1886,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" @@ -1916,8 +1924,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 f3c91b231a7cdb7e39d9b6284747a659e8233288 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__ (cherry picked from commit e831dcba62e75b3b5b82c46f7597c8667ad1a013) --- gmp.c | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/gmp.c b/gmp.c index 8021dfe3..807fda09 100644 --- a/gmp.c +++ b/gmp.c @@ -1860,30 +1860,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 @@ -1899,9 +1875,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 e1d51d41999a4d72d9e806d0ea4432b5ea07c0a4 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 (cherry picked from commit 2444e9fce356145bcedb8508b65173745f36fa0b) # Conflicts: # tests/test_mpz.py --- tests/test_mpz.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_mpz.py b/tests/test_mpz.py index fad45e7a..67c00fd6 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -1032,3 +1032,26 @@ def f(n): with ThreadPoolExecutor(max_workers=7) as tpe: futures = [tpe.submit(f, mpz(x)) for x in xs] assert all(f.result() == 1 for f in futures) +<<<<<<< HEAD +======= + + +# 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): + m = getattr(int, meth) + if meth.startswith("_") or not callable(m): + continue + mz = getattr(mpz, meth) + 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 +>>>>>>> 2444e9f (Enable test_int_api() for CPython < 3.13) From 89ddda357b339c2ab2252ba96d889813355759ce 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 (cherry picked from commit 66578169d28714590aa6044d469b9079a6719e8d) --- fmt.c | 7 +++++++ tests/test_mpz.py | 2 ++ 2 files changed, 9 insertions(+) diff --git a/fmt.c b/fmt.c index 17ddc056..8fe6c29e 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 67c00fd6..37e0d1f7 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -116,6 +116,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 7870823677f847c74bf19dc29347fa4ba6306ea3 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 (cherry picked from commit 723a834544115a207f517402687401ff182e3ead) # Conflicts: # mpz.h --- gmp.c | 1 - mpz.h | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gmp.c b/gmp.c index 807fda09..d5ecbd70 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 68d5aaf2..995b0364 100644 --- a/mpz.h +++ b/mpz.h @@ -1,6 +1,7 @@ #ifndef MPZ_H #define MPZ_H +<<<<<<< HEAD #if defined(__clang__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wnewline-eof" /* XXX: pypy/pypy#5312 */ @@ -25,6 +26,10 @@ #endif #include "zz.h" +======= +#include "utils.h" +#include "zz/zz.h" +>>>>>>> 723a834 (Keep Python.h include in utils.h) typedef struct { PyObject_HEAD From 48913b99812fcec31d4561a339154d8a482d4735 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 (cherry picked from commit 942df5090c0ad92d81e702041fb4916257aa2005) # Conflicts: # tests/test_mpz.py --- fmt.c | 6 +++--- gmp.c | 4 ++-- tests/test_mpz.py | 7 +++++++ utils.c | 40 ++++++++++++++++++++++++++++++++++++++++ utils.h | 5 +++++ 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/fmt.c b/fmt.c index 8fe6c29e..6dbddb1e 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 d5ecbd70..7bf4775f 100644 --- a/gmp.c +++ b/gmp.c @@ -489,7 +489,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; } @@ -500,7 +500,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 37e0d1f7..665e7fd8 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -1,4 +1,9 @@ +<<<<<<< HEAD import cmath +======= +import decimal +import inspect +>>>>>>> 942df50 (Use PyType_GetFullyQualifiedName to print types in errors) import locale import math import operator @@ -285,6 +290,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 fe85d806..f436c5ff 100644 --- a/utils.c +++ b/utils.c @@ -114,3 +114,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 a5f112a1..ee5aea0f 100644 --- a/utils.h +++ b/utils.h @@ -38,4 +38,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 */