Skip to content

gh-117031: Add support for new member types for PyMemberDef.type #117032

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 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
27 changes: 27 additions & 0 deletions Doc/c-api/structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,22 @@ Macro name C type Python type
.. c:macro:: Py_T_ULONG :c:expr:`unsigned long` :py:class:`int`
.. c:macro:: Py_T_ULONGLONG :c:expr:`unsigned long long` :py:class:`int`
.. c:macro:: Py_T_PYSSIZET :c:expr:`Py_ssize_t` :py:class:`int`
.. c:macro:: Py_T_SSIZE :c:expr:`Py_ssize_t` :py:class:`int`
.. c:macro:: Py_T_SIZE :c:expr:`size_t` :py:class:`int`
.. c:macro:: Py_T_INT8 :c:expr:`int8_t` :py:class:`int`
.. c:macro:: Py_T_UINT8 :c:expr:`uint8_t` :py:class:`int`
.. c:macro:: Py_T_INT16 :c:expr:`int16_t` :py:class:`int`
.. c:macro:: Py_T_UINT16 :c:expr:`uint16_t` :py:class:`int`
.. c:macro:: Py_T_INT32 :c:expr:`int32_t` :py:class:`int`
.. c:macro:: Py_T_UINT32 :c:expr:`uint32_t` :py:class:`int`
.. c:macro:: Py_T_INT64 :c:expr:`int64_t` :py:class:`int`
.. c:macro:: Py_T_UINT64 :c:expr:`uint64_t` :py:class:`int`
.. c:macro:: Py_T_INTPTR :c:expr:`intptr_t` :py:class:`int`
.. c:macro:: Py_T_UINTPTR :c:expr:`uintptr_t` :py:class:`int`
.. c:macro:: Py_T_OFF :c:expr:`off_t` or :py:class:`int`
:c:expr:`long long`
(on Windows)
.. c:macro:: Py_T_PID :c:expr:`pid_t` :py:class:`int`
.. c:macro:: Py_T_FLOAT :c:expr:`float` :py:class:`float`
.. c:macro:: Py_T_DOUBLE :c:expr:`double` :py:class:`float`
.. c:macro:: Py_T_BOOL :c:expr:`char` :py:class:`bool`
Expand Down Expand Up @@ -687,6 +703,17 @@ Macro name C type Python type

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

.. versionadded:: next

Added :c:macro:`Py_T_SSIZE` (as alias of :c:macro:`Py_T_PYSSIZET`),
:c:macro:`Py_T_SIZE`, :c:macro:`Py_T_INT8`, :c:macro:`Py_T_UINT8`,
:c:macro:`Py_T_INT16`, :c:macro:`Py_T_UINT16`,
:c:macro:`Py_T_INT32`, :c:macro:`Py_T_UINT32`,
:c:macro:`Py_T_INT64`, :c:macro:`Py_T_UINT64`,
:c:macro:`Py_T_INTPTR`, :c:macro:`Py_T_UINTPTR`,
:c:macro:`Py_T_OFF`, and :c:macro:`Py_T_PID`.


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
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1818,6 +1818,16 @@ New features

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

* Add support of new member types for :c:member:`PyMemberDef.type`:
:c:macro:`Py_T_SSIZE` (as alias of :c:macro:`Py_T_PYSSIZET`),
:c:macro:`Py_T_SIZE`, :c:macro:`Py_T_INT8`, :c:macro:`Py_T_UINT8`,
:c:macro:`Py_T_INT16`, :c:macro:`Py_T_UINT16`,
:c:macro:`Py_T_INT32`, :c:macro:`Py_T_UINT32`,
:c:macro:`Py_T_INT64`, :c:macro:`Py_T_UINT64`,
:c:macro:`Py_T_INTPTR`, :c:macro:`Py_T_UINTPTR`,
:c:macro:`Py_T_OFF`, and :c:macro:`Py_T_PID`.
(Contributed by Serhiy Storchaka 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
20 changes: 20 additions & 0 deletions Include/descrobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,26 @@ struct PyMemberDef {
#define Py_T_PYSSIZET 19 /* Py_ssize_t */
#define _Py_T_NONE 20 // Deprecated. Value is always None.

#define Py_T_SSIZE Py_T_PYSSIZET
#define Py_T_SIZE 21
#define Py_T_INTPTR 22
#define Py_T_UINTPTR 23
#ifdef MS_WINDOWS
# define Py_T_OFF Py_T_LONGLONG
#else
# define Py_T_OFF 24
#endif
#define Py_T_PID 25

#define Py_T_INT8 0x100|0x1
#define Py_T_UINT8 0x100|0x2
#define Py_T_INT16 0x104|0x1
#define Py_T_UINT16 0x104|0x2
#define Py_T_INT32 0x108|0x1
#define Py_T_UINT32 0x108|0x2
#define Py_T_INT64 0x110|0x1
#define Py_T_UINT64 0x110|0x2

/* Flags */
#define Py_READONLY 1
#define Py_AUDIT_READ 2 // Added in 3.10, harmless no-op before that
Expand Down
61 changes: 51 additions & 10 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, wrap=False):
if hardlimit is None:
hardlimit = (minval, maxval)
ts = self.ts
self._test_write(name, minval)
self._test_write(name, maxval)
self._test_write(name, maxval, -1 if wrap else 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 @@ -88,11 +92,11 @@ def _test_int_range(self, name, minval, maxval, *, hardlimit=None,
self.assertRaises(TypeError, setattr, ts, name, Index(maxval))
else:
self._test_write(name, Index(minval), minval)
self._test_write(name, Index(maxval), maxval)
self._test_overflow(name, Index(hardminval-1))
self._test_write(name, Index(maxval), -1 if wrap else maxval)
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,43 @@ 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_SSIZE', PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, indexlimit=False)
self._test_int_range('T_SIZE', 0, SIZE_MAX, indexlimit=False)

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)
self._test_int_range('T_XINT8', -2**7, 2**8-1, wrap=True)

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)
self._test_int_range('T_XINT16', -2**15, 2**16-1, wrap=True)

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)
self._test_int_range('T_XINT32', -2**31, 2**32-1, wrap=True)

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)
self._test_int_range('T_XINT64', -2**63, 2**64-1, wrap=True)

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,7 @@
Add support for new member types for :c:member:`PyMemberDef.type`:
:c:macro:`Py_T_SSIZE` (as alias of :c:macro:`Py_T_PYSSIZET`),
:c:macro:`Py_T_SIZE`, :c:macro:`Py_T_INT8`, :c:macro:`Py_T_UINT8`,
:c:macro:`Py_T_INT16`, :c:macro:`Py_T_UINT16`, :c:macro:`Py_T_INT32`,
:c:macro:`Py_T_UINT32`, :c:macro:`Py_T_INT64`, :c:macro:`Py_T_UINT64`,
:c:macro:`Py_T_INTPTR`, :c:macro:`Py_T_UINTPTR`, :c:macro:`Py_T_OFF`, and
:c:macro:`Py_T_PID`.
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_UINTPTR

#ifdef MS_WINDOWS
# define F_HANDLE F_POINTER
Expand Down
39 changes: 39 additions & 0 deletions Modules/_testcapi/structmember.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,33 @@ 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;
uint8_t xint8_member;
int16_t int16_member;
uint16_t uint16_member;
uint16_t xint16_member;
int32_t int32_member;
uint32_t uint32_member;
uint32_t xint32_member;
int64_t int64_member;
uint64_t uint64_member;
uint64_t xint64_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 +63,30 @@ 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_SSIZE", Py_T_SSIZE, offsetof(test_structmembers, structmembers.pyssizet_member), 0, NULL},
{"T_SIZE", Py_T_SIZE, 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_INT8, offsetof(test_structmembers, structmembers.int8_member), 0, NULL},
{"T_UINT8", Py_T_UINT8, offsetof(test_structmembers, structmembers.uint8_member), 0, NULL},
{"T_XINT8", Py_T_INT8|Py_T_UINT8, offsetof(test_structmembers, structmembers.xint8_member), 0, NULL},
{"T_INT16", Py_T_INT16, offsetof(test_structmembers, structmembers.int16_member), 0, NULL},
{"T_UINT16", Py_T_UINT16, offsetof(test_structmembers, structmembers.uint16_member), 0, NULL},
{"T_XINT16", Py_T_INT16|Py_T_UINT16, offsetof(test_structmembers, structmembers.xint16_member), 0, NULL},
{"T_INT32", Py_T_INT32, offsetof(test_structmembers, structmembers.int32_member), 0, NULL},
{"T_UINT32", Py_T_UINT32, offsetof(test_structmembers, structmembers.uint32_member), 0, NULL},
{"T_XINT32", Py_T_INT32|Py_T_UINT32, offsetof(test_structmembers, structmembers.xint32_member), 0, NULL},
{"T_INT64", Py_T_INT64, offsetof(test_structmembers, structmembers.int64_member), 0, NULL},
{"T_UINT64", Py_T_UINT64, offsetof(test_structmembers, structmembers.uint64_member), 0, NULL},
{"T_XINT64", Py_T_INT64|Py_T_UINT64, offsetof(test_structmembers, structmembers.xint64_member), 0, NULL},
{"T_INTPTR", Py_T_INTPTR, offsetof(test_structmembers, structmembers.intptr_member), 0, NULL},
{"T_UINTPTR", Py_T_UINTPTR, offsetof(test_structmembers, structmembers.uintptr_member), 0, NULL},
{"T_OFF", Py_T_OFF, offsetof(test_structmembers, structmembers.off_member), 0, NULL},
{"T_PID", Py_T_PID, 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)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's no longer used.

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
Loading
Loading