Skip to content

gh-117031: Add support for arbitrary C integer types for PyMemberDef.type #132550

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion Doc/c-api/hash.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ See also the :c:member:`PyTypeObject.tp_hash` member and :ref:`numeric-hash`.
.. c:function:: Py_hash_t Py_HashPointer(const void *ptr)

Hash a pointer value: process the pointer value as an integer (cast it to
``uintptr_t`` internally). The pointer is not dereferenced.
:c:expr:`uintptr_t` internally). The pointer is not dereferenced.

The function cannot fail: it cannot return ``-1``.

Expand Down
18 changes: 13 additions & 5 deletions Doc/c-api/structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ using e.g. :keyword:`del` or :py:func:`delattr`.
================================ ============================= ======================
Macro name C type Python type
================================ ============================= ======================
.. c:macro:: Py_T_INTEGER(type) *type* (INT) :py:class:`int`
.. c:macro:: Py_T_BYTE :c:expr:`char` :py:class:`int`
.. c:macro:: Py_T_SHORT :c:expr:`short` :py:class:`int`
.. c:macro:: Py_T_INT :c:expr:`int` :py:class:`int`
Expand All @@ -630,18 +631,21 @@ Macro name C type Python type
.. c:macro:: Py_T_DOUBLE :c:expr:`double` :py:class:`float`
.. c:macro:: Py_T_BOOL :c:expr:`char` :py:class:`bool`
(written as 0 or 1)
.. c:macro:: Py_T_STRING :c:expr:`const char *` (*) :py:class:`str` (RO)
.. c:macro:: Py_T_STRING_INPLACE :c:expr:`const char[]` (*) :py:class:`str` (RO)
.. c:macro:: Py_T_CHAR :c:expr:`char` (0-127) :py:class:`str` (**)
.. c:macro:: Py_T_STRING :c:expr:`const char *` (STR) :py:class:`str` (RO)
.. c:macro:: Py_T_STRING_INPLACE :c:expr:`const char[]` (STR) :py:class:`str` (RO)
.. c:macro:: Py_T_CHAR :c:expr:`char` (0-127) :py:class:`str` (CH)
.. c:macro:: Py_T_OBJECT_EX :c:expr:`PyObject *` :py:class:`object` (D)
================================ ============================= ======================

(*): Zero-terminated, UTF8-encoded C string.
(INT): Macro :c:macro:`Py_T_INTEGER(type) <Py_T_INTEGER>` represents arbitrary
C integer type *type*.

(STR): Zero-terminated, UTF8-encoded C string.
With :c:macro:`!Py_T_STRING` the C representation is a pointer;
with :c:macro:`!Py_T_STRING_INPLACE` the string is stored directly
in the structure.

(**): String of length 1. Only ASCII is accepted.
(CH): String of length 1. Only ASCII is accepted.

(RO): Implies :c:macro:`Py_READONLY`.

Expand Down Expand Up @@ -687,6 +691,10 @@ Macro name C type Python type

Always ``None``. Must be used with :c:macro:`Py_READONLY`.

.. versionadded:: next
Added :c:macro:`Py_T_INTEGER()`.


Defining Getters and Setters
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
2 changes: 2 additions & 0 deletions Doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@
('c:type', 'int32_t'),
('c:type', 'int64_t'),
('c:type', 'intmax_t'),
('c:type', 'intptr_t'),
('c:type', 'off_t'),
('c:type', 'pid_t'),
('c:type', 'ptrdiff_t'),
('c:type', 'siginfo_t'),
('c:type', 'size_t'),
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1818,6 +1818,10 @@ New features

(Contributed by Victor Stinner in :gh:`120389`.)

* Add support of arbitrary C integer types in :c:member:`PyMemberDef.type`.
Macro :c:macro:`Py_T_INTEGER(type) <Py_T_INTEGER>` represents a C type *type*.
(Contributed by Serhiy Storchaka and Petr Viktorin in :gh:`117031`.)

* Add :c:func:`PyBytes_Join(sep, iterable) <PyBytes_Join>` function,
similar to ``sep.join(iterable)`` in Python.
(Contributed by Victor Stinner in :gh:`121645`.)
Expand Down
2 changes: 2 additions & 0 deletions Include/descrobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ struct PyMemberDef {
#define Py_T_PYSSIZET 19 /* Py_ssize_t */
#define _Py_T_NONE 20 // Deprecated. Value is always None.

#define Py_T_INTEGER(type) ((sizeof(type) << 8) | (_Py_IS_TYPE_SIGNED(type) ? 0 : 1))

/* Flags */
#define Py_READONLY 1
#define Py_AUDIT_READ 2 // Added in 3.10, harmless no-op before that
Expand Down
52 changes: 44 additions & 8 deletions Lib/test/test_capi/test_structmembers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
INT_MAX, INT_MIN, UINT_MAX,
LONG_MAX, LONG_MIN, ULONG_MAX,
LLONG_MAX, LLONG_MIN, ULLONG_MAX,
PY_SSIZE_T_MAX, PY_SSIZE_T_MIN,
PY_SSIZE_T_MAX, PY_SSIZE_T_MIN, SIZE_MAX,
SIZEOF_INTMAX_T, SIZEOF_INTPTR_T, SIZEOF_PTRDIFF_T, SIZEOF_OFF_T,
SIZEOF_PID_T, SIZEOF_INT,
)


Expand All @@ -19,6 +21,8 @@ def __init__(self, value):
self.value = value
def __index__(self):
return self.value
def __repr__(self):
return f'Index({self.value!r})'

# There are two classes: one using <structmember.h> and another using
# `Py_`-prefixed API. They should behave the same in Python
Expand Down Expand Up @@ -60,22 +64,22 @@ def _test_warn(self, name, value, expected=None):
if expected is not None:
self.assertEqual(getattr(ts, name), expected)

def _test_overflow(self, name, value):
def _test_overflow(self, name, value, error=OverflowError):
ts = self.ts
self.assertRaises(OverflowError, setattr, ts, name, value)
self.assertRaises(error, setattr, ts, name, value)

def _test_int_range(self, name, minval, maxval, *, hardlimit=None,
indexlimit=None):
indexlimit=None, negvalueerror=OverflowError):
if hardlimit is None:
hardlimit = (minval, maxval)
ts = self.ts
self._test_write(name, minval)
self._test_write(name, maxval)
hardminval, hardmaxval = hardlimit
self._test_overflow(name, hardminval-1)
self._test_overflow(name, hardminval-1, error=negvalueerror)
self._test_overflow(name, hardmaxval+1)
self._test_overflow(name, 2**1000)
self._test_overflow(name, -2**1000)
self._test_overflow(name, -2**1000, error=negvalueerror)
if hardminval < minval:
self._test_warn(name, hardminval)
self._test_warn(name, minval-1, maxval)
Expand All @@ -89,10 +93,10 @@ def _test_int_range(self, name, minval, maxval, *, hardlimit=None,
else:
self._test_write(name, Index(minval), minval)
self._test_write(name, Index(maxval), maxval)
self._test_overflow(name, Index(hardminval-1))
self._test_overflow(name, Index(hardminval-1), error=negvalueerror)
self._test_overflow(name, Index(hardmaxval+1))
self._test_overflow(name, Index(2**1000))
self._test_overflow(name, Index(-2**1000))
self._test_overflow(name, Index(-2**1000), error=negvalueerror)
if hardminval < minval:
self._test_warn(name, Index(hardminval))
self._test_warn(name, Index(minval-1), maxval)
Expand Down Expand Up @@ -181,6 +185,38 @@ class ReadWriteTests_OldAPI(ReadWriteTests, unittest.TestCase):
class ReadWriteTests_NewAPI(ReadWriteTests, unittest.TestCase):
cls = _test_structmembersType_NewAPI

def test_size(self):
self._test_int_range('T_SIZE', 0, SIZE_MAX, negvalueerror=ValueError)

def test_int8(self):
self._test_int_range('T_INT8', -2**7, 2**7-1)
self._test_int_range('T_UINT8', 0, 2**8-1, negvalueerror=ValueError)

def test_int16(self):
self._test_int_range('T_INT16', -2**15, 2**15-1)
self._test_int_range('T_UINT16', 0, 2**16-1, negvalueerror=ValueError)

def test_int32(self):
self._test_int_range('T_INT32', -2**31, 2**31-1)
self._test_int_range('T_UINT32', 0, 2**32-1, negvalueerror=ValueError)

def test_int64(self):
self._test_int_range('T_INT64', -2**63, 2**63-1)
self._test_int_range('T_UINT64', 0, 2**64-1, negvalueerror=ValueError)

def test_intptr(self):
bits = 8*SIZEOF_INTPTR_T
self._test_int_range('T_INTPTR', -2**(bits-1), 2**(bits-1)-1)
self._test_int_range('T_UINTPTR', 0, 2**bits-1, negvalueerror=ValueError)

def test_off(self):
bits = 8*SIZEOF_OFF_T
self._test_int_range('T_OFF', -2**(bits-1), 2**(bits-1)-1)

def test_pid(self):
bits = 8*SIZEOF_PID_T
self._test_int_range('T_PID', -2**(bits-1), 2**(bits-1)-1)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add support for arbitrary C integer types for :c:member:`PyMemberDef.type`.
Macro :c:macro:`Py_T_INTEGER(type)` represents a C type *type*.
11 changes: 2 additions & 9 deletions Modules/_multiprocessing/multiprocessing.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,8 @@
* Format codes
*/

#if SIZEOF_VOID_P == SIZEOF_LONG
# define F_POINTER "k"
# define T_POINTER T_ULONG
#elif SIZEOF_VOID_P == SIZEOF_LONG_LONG
# define F_POINTER "K"
# define T_POINTER T_ULONGLONG
#else
# error "can't find format code for unsigned integer of same size as void*"
#endif
#define F_POINTER _Py_PARSE_UINTPTR
#define T_POINTER Py_T_INTEGER(uintptr_t)

#ifdef MS_WINDOWS
# define F_HANDLE F_POINTER
Expand Down
34 changes: 34 additions & 0 deletions Modules/_testcapi/structmember.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,29 @@ typedef struct {
long long_member;
unsigned long ulong_member;
Py_ssize_t pyssizet_member;
size_t size_member;
float float_member;
double double_member;
char inplace_member[6];
long long longlong_member;
unsigned long long ulonglong_member;
char char_member;
int8_t int8_member;
uint8_t uint8_member;
int16_t int16_member;
uint16_t uint16_member;
int32_t int32_member;
uint32_t uint32_member;
int64_t int64_member;
uint64_t uint64_member;
intptr_t intptr_member;
uintptr_t uintptr_member;
#ifdef MS_WINDOWS
long long off_member;
#else
off_t off_member;
#endif
pid_t pid_member;
} all_structmembers;

typedef struct {
Expand All @@ -42,12 +59,29 @@ static struct PyMemberDef test_members_newapi[] = {
{"T_LONG", Py_T_LONG, offsetof(test_structmembers, structmembers.long_member), 0, NULL},
{"T_ULONG", Py_T_ULONG, offsetof(test_structmembers, structmembers.ulong_member), 0, NULL},
{"T_PYSSIZET", Py_T_PYSSIZET, offsetof(test_structmembers, structmembers.pyssizet_member), 0, NULL},
{"T_SIZE", Py_T_INTEGER(size_t), offsetof(test_structmembers, structmembers.size_member), 0, NULL},
{"T_FLOAT", Py_T_FLOAT, offsetof(test_structmembers, structmembers.float_member), 0, NULL},
{"T_DOUBLE", Py_T_DOUBLE, offsetof(test_structmembers, structmembers.double_member), 0, NULL},
{"T_STRING_INPLACE", Py_T_STRING_INPLACE, offsetof(test_structmembers, structmembers.inplace_member), 0, NULL},
{"T_LONGLONG", Py_T_LONGLONG, offsetof(test_structmembers, structmembers.longlong_member), 0, NULL},
{"T_ULONGLONG", Py_T_ULONGLONG, offsetof(test_structmembers, structmembers.ulonglong_member), 0, NULL},
{"T_CHAR", Py_T_CHAR, offsetof(test_structmembers, structmembers.char_member), 0, NULL},
{"T_INT8", Py_T_INTEGER(int8_t), offsetof(test_structmembers, structmembers.int8_member), 0, NULL},
{"T_UINT8", Py_T_INTEGER(uint8_t), offsetof(test_structmembers, structmembers.uint8_member), 0, NULL},
{"T_INT16", Py_T_INTEGER(int16_t), offsetof(test_structmembers, structmembers.int16_member), 0, NULL},
{"T_UINT16", Py_T_INTEGER(uint16_t), offsetof(test_structmembers, structmembers.uint16_member), 0, NULL},
{"T_INT32", Py_T_INTEGER(int32_t), offsetof(test_structmembers, structmembers.int32_member), 0, NULL},
{"T_UINT32", Py_T_INTEGER(uint32_t), offsetof(test_structmembers, structmembers.uint32_member), 0, NULL},
{"T_INT64", Py_T_INTEGER(int64_t), offsetof(test_structmembers, structmembers.int64_member), 0, NULL},
{"T_UINT64", Py_T_INTEGER(uint64_t), offsetof(test_structmembers, structmembers.uint64_member), 0, NULL},
{"T_INTPTR", Py_T_INTEGER(intptr_t), offsetof(test_structmembers, structmembers.intptr_member), 0, NULL},
{"T_UINTPTR", Py_T_INTEGER(uintptr_t), offsetof(test_structmembers, structmembers.uintptr_member), 0, NULL},
#ifdef MS_WINDOWS
{"T_OFF", Py_T_INTEGER(long long), offsetof(test_structmembers, structmembers.off_member), 0, NULL},
#else
{"T_OFF", Py_T_INTEGER(off_t), offsetof(test_structmembers, structmembers.off_member), 0, NULL},
#endif
{"T_PID", Py_T_INTEGER(pid_t), offsetof(test_structmembers, structmembers.pid_member), 0, NULL},
{NULL}
};

Expand Down
9 changes: 9 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3272,6 +3272,15 @@ PyInit__testcapi(void)
PyModule_AddObject(m, "SIZEOF_WCHAR_T", PyLong_FromSsize_t(sizeof(wchar_t)));
PyModule_AddObject(m, "SIZEOF_VOID_P", PyLong_FromSsize_t(sizeof(void*)));
PyModule_AddObject(m, "SIZEOF_TIME_T", PyLong_FromSsize_t(sizeof(time_t)));
PyModule_AddObject(m, "SIZEOF_INTMAX_T", PyLong_FromSsize_t(sizeof(intmax_t)));
PyModule_AddObject(m, "SIZEOF_INTPTR_T", PyLong_FromSsize_t(sizeof(intptr_t)));
PyModule_AddObject(m, "SIZEOF_PTRDIFF_T", PyLong_FromSsize_t(sizeof(ptrdiff_t)));
#ifdef MS_WINDOWS
PyModule_AddObject(m, "SIZEOF_OFF_T", PyLong_FromSsize_t(sizeof(long long)));
#else
PyModule_AddObject(m, "SIZEOF_OFF_T", PyLong_FromSsize_t(sizeof(off_t)));
#endif
PyModule_AddObject(m, "SIZEOF_INT", PyLong_FromSsize_t(sizeof(int)));
PyModule_AddObject(m, "SIZEOF_PID_T", PyLong_FromSsize_t(sizeof(pid_t)));
PyModule_AddObject(m, "Py_Version", PyLong_FromUnsignedLong(Py_Version));
Py_INCREF(&PyInstanceMethod_Type);
Expand Down
47 changes: 10 additions & 37 deletions Modules/selectmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1799,48 +1799,21 @@ typedef struct kqueue_queue_Object {

#define kqueue_queue_Object_CAST(op) ((kqueue_queue_Object *)(op))

#if (SIZEOF_UINTPTR_T != SIZEOF_VOID_P)
# error uintptr_t does not match void *!
#elif (SIZEOF_UINTPTR_T == SIZEOF_LONG_LONG)
# define T_UINTPTRT Py_T_ULONGLONG
# define T_INTPTRT Py_T_LONGLONG
# define UINTPTRT_FMT_UNIT "K"
# define INTPTRT_FMT_UNIT "L"
#elif (SIZEOF_UINTPTR_T == SIZEOF_LONG)
# define T_UINTPTRT Py_T_ULONG
# define T_INTPTRT Py_T_LONG
# define UINTPTRT_FMT_UNIT "k"
# define INTPTRT_FMT_UNIT "l"
#elif (SIZEOF_UINTPTR_T == SIZEOF_INT)
# define T_UINTPTRT Py_T_UINT
# define T_INTPTRT Py_T_INT
# define UINTPTRT_FMT_UNIT "I"
# define INTPTRT_FMT_UNIT "i"
#else
# error uintptr_t does not match int, long, or long long!
#endif

#if SIZEOF_LONG_LONG == 8
# define T_INT64 Py_T_LONGLONG
# define INT64_FMT_UNIT "L"
#elif SIZEOF_LONG == 8
# define T_INT64 Py_T_LONG
# define INT64_FMT_UNIT "l"
#elif SIZEOF_INT == 8
# define T_INT64 Py_T_INT
# define INT64_FMT_UNIT "i"
#else
# define INT64_FMT_UNIT "_"
#endif

#if SIZEOF_LONG_LONG == 4
# define T_UINT32 Py_T_ULONGLONG
# define UINT32_FMT_UNIT "K"
#elif SIZEOF_LONG == 4
# define T_UINT32 Py_T_ULONG
# define UINT32_FMT_UNIT "k"
#elif SIZEOF_INT == 4
# define T_UINT32 Py_T_UINT
# define UINT32_FMT_UNIT "I"
#else
# define UINT32_FMT_UNIT "_"
Expand All @@ -1850,11 +1823,11 @@ typedef struct kqueue_queue_Object {
* kevent is not standard and its members vary across BSDs.
*/
#ifdef __NetBSD__
# define FILTER_TYPE T_UINT32
# define FILTER_TYPE Py_T_INTEGER(uint32_t)
# define FILTER_FMT_UNIT UINT32_FMT_UNIT
# define FLAGS_TYPE T_UINT32
# define FLAGS_TYPE Py_T_INTEGER(uint32_t)
# define FLAGS_FMT_UNIT UINT32_FMT_UNIT
# define FFLAGS_TYPE T_UINT32
# define FFLAGS_TYPE Py_T_INTEGER(uint32_t)
# define FFLAGS_FMT_UNIT UINT32_FMT_UNIT
#else
# define FILTER_TYPE Py_T_SHORT
Expand All @@ -1866,11 +1839,11 @@ typedef struct kqueue_queue_Object {
#endif

#if defined(__NetBSD__) || defined(__OpenBSD__)
# define DATA_TYPE T_INT64
# define DATA_TYPE Py_T_INTEGER(int64_t)
# define DATA_FMT_UNIT INT64_FMT_UNIT
#else
# define DATA_TYPE T_INTPTRT
# define DATA_FMT_UNIT INTPTRT_FMT_UNIT
# define DATA_TYPE Py_T_INTEGER(intptr_t)
# define DATA_FMT_UNIT _Py_PARSE_INTPTR
#endif

/* Unfortunately, we can't store python objects in udata, because
Expand All @@ -1880,12 +1853,12 @@ typedef struct kqueue_queue_Object {

#define KQ_OFF(x) offsetof(kqueue_event_Object, x)
static struct PyMemberDef kqueue_event_members[] = {
{"ident", T_UINTPTRT, KQ_OFF(e.ident)},
{"ident", Py_T_INTEGER(uintptr_t), KQ_OFF(e.ident)},
{"filter", FILTER_TYPE, KQ_OFF(e.filter)},
{"flags", FLAGS_TYPE, KQ_OFF(e.flags)},
{"fflags", Py_T_UINT, KQ_OFF(e.fflags)},
{"fflags", Py_T_UINT, KQ_OFF(e.fflags)},
{"data", DATA_TYPE, KQ_OFF(e.data)},
{"udata", T_UINTPTRT, KQ_OFF(e.udata)},
{"udata", Py_T_INTEGER(uintptr_t), KQ_OFF(e.udata)},
{NULL} /* Sentinel */
};
#undef KQ_OFF
Expand All @@ -1909,7 +1882,7 @@ kqueue_event_init(PyObject *op, PyObject *args, PyObject *kwds)
"data", "udata", NULL};
static const char fmt[] = "O|"
FILTER_FMT_UNIT FLAGS_FMT_UNIT FFLAGS_FMT_UNIT DATA_FMT_UNIT
UINTPTRT_FMT_UNIT ":kevent";
_Py_PARSE_UINTPTR ":kevent";

kqueue_event_Object *self = kqueue_event_Object_CAST(op);
EV_SET(&(self->e), 0, EVFILT_READ, EV_ADD, 0, 0, 0); /* defaults */
Expand Down
Loading
Loading