summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/structures.rst245
-rw-r--r--Doc/data/stable_abi.dat2
-rw-r--r--Doc/extending/newtypes.rst33
-rw-r--r--Doc/extending/newtypes_tutorial.rst15
-rw-r--r--Doc/includes/custom2.c8
-rw-r--r--Doc/includes/custom3.c4
-rw-r--r--Doc/includes/custom4.c4
-rw-r--r--Doc/whatsnew/3.12.rst31
-rw-r--r--Include/descrobject.h55
-rw-r--r--Include/structmember.h95
-rw-r--r--Lib/test/test_capi/test_structmembers.py91
-rw-r--r--Misc/NEWS.d/next/C API/2022-11-02-16-51-24.gh-issue-47146.dsYDtI.rst5
-rw-r--r--Misc/stable_abi.toml49
-rw-r--r--Modules/Setup.stdlib.in2
-rw-r--r--Modules/_testcapi/parts.h1
-rw-r--r--Modules/_testcapi/structmember.c217
-rw-r--r--Modules/_testcapimodule.c151
-rw-r--r--PCbuild/_testcapi.vcxproj1
-rw-r--r--PCbuild/_testcapi.vcxproj.filters3
19 files changed, 667 insertions, 345 deletions
diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst
index 5a20f07..827d624 100644
--- a/Doc/c-api/structures.rst
+++ b/Doc/c-api/structures.rst
@@ -385,86 +385,67 @@ Accessing attributes of extension types
.. c:type:: PyMemberDef
Structure which describes an attribute of a type which corresponds to a C
- struct member. Its fields are:
+ struct member. Its fields are, in order:
- .. c:member:: const char* PyMemberDef.name
+ .. c:member:: const char* name
- Name of the member
+ Name of the member.
+ A NULL value marks the end of a ``PyMemberDef[]`` array.
- .. c:member:: int PyMemberDef.type
-
- The type of the member in the C struct.
+ The string should be static, no copy is made of it.
.. c:member:: Py_ssize_t PyMemberDef.offset
The offset in bytes that the member is located on the type’s object struct.
- .. c:member:: int PyMemberDef.flags
-
- Flag bits indicating if the field should be read-only or writable.
-
- .. c:member:: const char* PyMemberDef.doc
-
- Points to the contents of the docstring.
-
- :c:member:`PyMemberDef.type` can be one of many ``T_`` macros corresponding to various C
- types. When the member is accessed in Python, it will be converted to the
- equivalent Python type.
-
- =============== ==================
- Macro name C type
- =============== ==================
- T_SHORT short
- T_INT int
- T_LONG long
- T_FLOAT float
- T_DOUBLE double
- T_STRING const char \*
- T_OBJECT PyObject \*
- T_OBJECT_EX PyObject \*
- T_CHAR char
- T_BYTE char
- T_UBYTE unsigned char
- T_UINT unsigned int
- T_USHORT unsigned short
- T_ULONG unsigned long
- T_BOOL char
- T_LONGLONG long long
- T_ULONGLONG unsigned long long
- T_PYSSIZET Py_ssize_t
- =============== ==================
-
- :c:macro:`T_OBJECT` and :c:macro:`T_OBJECT_EX` differ in that
- :c:macro:`T_OBJECT` returns ``None`` if the member is ``NULL`` and
- :c:macro:`T_OBJECT_EX` raises an :exc:`AttributeError`. Try to use
- :c:macro:`T_OBJECT_EX` over :c:macro:`T_OBJECT` because :c:macro:`T_OBJECT_EX`
- handles use of the :keyword:`del` statement on that attribute more correctly
- than :c:macro:`T_OBJECT`.
-
- :c:member:`PyMemberDef.flags` can be ``0`` for write and read access or :c:macro:`READONLY` for
- read-only access. Using :c:macro:`T_STRING` for :attr:`type` implies
- :c:macro:`READONLY`. :c:macro:`T_STRING` data is interpreted as UTF-8.
- Only :c:macro:`T_OBJECT` and :c:macro:`T_OBJECT_EX`
- members can be deleted. (They are set to ``NULL``).
+ .. c:member:: int type
+
+ The type of the member in the C struct.
+ See :ref:`PyMemberDef-types` for the possible values.
+
+ .. c:member:: int flags
+
+ Zero or more of the :ref:`PyMemberDef-flags`, combined using bitwise OR.
+
+ .. c:member:: const char* doc
+
+ The docstring, or NULL.
+ The string should be static, no copy is made of it.
+ Typically, it is defined using :c:macro:`PyDoc_STR`.
+
+ By default (when :c:member:`flags` is ``0``), members allow
+ both read and write access.
+ Use the :c:macro:`Py_READONLY` flag for read-only access.
+ Certain types, like :c:macro:`Py_T_STRING`, imply :c:macro:`Py_READONLY`.
+ Only :c:macro:`Py_T_OBJECT_EX` (and legacy :c:macro:`T_OBJECT`) members can
+ be deleted.
.. _pymemberdef-offsets:
- Heap allocated types (created using :c:func:`PyType_FromSpec` or similar),
- ``PyMemberDef`` may contain definitions for the special member
- ``__vectorcalloffset__``, corresponding to
+ For heap-allocated types (created using :c:func:`PyType_FromSpec` or similar),
+ ``PyMemberDef`` may contain a definition for the special member
+ ``"__vectorcalloffset__"``, corresponding to
:c:member:`~PyTypeObject.tp_vectorcall_offset` in type objects.
- These must be defined with ``T_PYSSIZET`` and ``READONLY``, for example::
+ These must be defined with ``Py_T_PYSSIZET`` and ``Py_READONLY``, for example::
static PyMemberDef spam_type_members[] = {
- {"__vectorcalloffset__", T_PYSSIZET, offsetof(Spam_object, vectorcall), READONLY},
+ {"__vectorcalloffset__", Py_T_PYSSIZET,
+ offsetof(Spam_object, vectorcall), Py_READONLY},
{NULL} /* Sentinel */
};
+ (You may need to ``#include <stddef.h>`` for :c:func:`!offsetof`.)
+
The legacy offsets :c:member:`~PyTypeObject.tp_dictoffset` and
- :c:member:`~PyTypeObject.tp_weaklistoffset` are still supported, but extensions are
- strongly encouraged to use ``Py_TPFLAGS_MANAGED_DICT`` and
- ``Py_TPFLAGS_MANAGED_WEAKREF`` instead.
+ :c:member:`~PyTypeObject.tp_weaklistoffset` can be defined similarly using
+ ``"__dictoffset__"`` and ``"__weaklistoffset__"`` members, but extensions
+ are strongly encouraged to use :const:`Py_TPFLAGS_MANAGED_DICT` and
+ :const:`Py_TPFLAGS_MANAGED_WEAKREF` instead.
+ .. versionchanged:: 3.12
+
+ ``PyMemberDef`` is always available.
+ Previously, it required including ``"structmember.h"``.
.. c:function:: PyObject* PyMember_GetOne(const char *obj_addr, struct PyMemberDef *m)
@@ -472,6 +453,10 @@ Accessing attributes of extension types
attribute is described by ``PyMemberDef`` *m*. Returns ``NULL``
on error.
+ .. versionchanged:: 3.12
+
+ ``PyMember_GetOne`` is always available.
+ Previously, it required including ``"structmember.h"``.
.. c:function:: int PyMember_SetOne(char *obj_addr, struct PyMemberDef *m, PyObject *o)
@@ -479,6 +464,144 @@ Accessing attributes of extension types
The attribute to set is described by ``PyMemberDef`` *m*. Returns ``0``
if successful and a negative value on failure.
+ .. versionchanged:: 3.12
+
+ ``PyMember_SetOne`` is always available.
+ Previously, it required including ``"structmember.h"``.
+
+.. _PyMemberDef-flags:
+
+Member flags
+^^^^^^^^^^^^
+
+The following flags can be used with :c:member:`PyMemberDef.flags`:
+
+.. c:macro:: Py_READONLY
+
+ Not writable.
+
+.. c:macro:: Py_AUDIT_READ
+
+ Emit an ``object.__getattr__`` :ref:`audit event <audit-events>`
+ before reading.
+
+.. index::
+ single: READ_RESTRICTED
+ single: WRITE_RESTRICTED
+ single: RESTRICTED
+
+.. versionchanged:: 3.10
+
+ The :const:`!RESTRICTED`, :const:`!READ_RESTRICTED` and
+ :const:`!WRITE_RESTRICTED` macros available with
+ ``#include "structmember.h"`` are deprecated.
+ :const:`!READ_RESTRICTED` and :const:`!RESTRICTED` are equivalent to
+ :const:`Py_AUDIT_READ`; :const:`!WRITE_RESTRICTED` does nothing.
+
+.. index::
+ single: READONLY
+
+.. versionchanged:: 3.12
+
+ The :const:`!READONLY` macro was renamed to :const:`Py_READONLY`.
+ The :const:`!PY_AUDIT_READ` macro was renamed with the ``Py_`` prefix.
+ The new names are now always available.
+ Previously, these required ``#include "structmember.h"``.
+ The header is still available and it provides the old names.
+
+.. _PyMemberDef-types:
+
+Member types
+^^^^^^^^^^^^
+
+:c:member:`PyMemberDef.type` can be one of the following macros corresponding
+to various C types.
+When the member is accessed in Python, it will be converted to the
+equivalent Python type.
+When it is set from Python, it will be converted back to the C type.
+If that is not possible, an exception such as :exc:`TypeError` or
+:exc:`ValueError` is raised.
+
+Unless marked (D), attributes defined this way cannot be deleted
+using e.g. :keyword:`del` or :py:func:`delattr`.
+
+================================ ============================= ======================
+Macro name C type Python type
+================================ ============================= ======================
+.. 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`
+.. c:macro:: Py_T_LONG :c:expr:`long` :py:class:`int`
+.. c:macro:: Py_T_LONGLONG :c:expr:`long long` :py:class:`int`
+.. c:macro:: Py_T_UBYTE :c:expr:`unsigned char` :py:class:`int`
+.. c:macro:: Py_T_UINT :c:expr:`unsigned int` :py:class:`int`
+.. c:macro:: Py_T_USHORT :c:expr:`unsigned short` :py:class:`int`
+.. 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_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`
+ (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_OBJECT_EX :c:expr:`PyObject *` :py:class:`object` (D)
+================================ ============================= ======================
+
+ (*): Zero-terminated, UTF8-encoded C string.
+ With :c:macro:`!Py_T_STRING` the C representation is a pointer;
+ with :c:macro:`!Py_T_STRING_INLINE` the string is stored directly
+ in the structure.
+
+ (**): String of length 1. Only ASCII is accepted.
+
+ (RO): Implies :c:macro:`Py_READONLY`.
+
+ (D): Can be deleted, in which case the pointer is set to ``NULL``.
+ Reading a ``NULL`` pointer raises :py:exc:`AttributeError`.
+
+.. index::
+ single: T_BYTE
+ single: T_SHORT
+ single: T_INT
+ single: T_LONG
+ single: T_LONGLONG
+ single: T_UBYTE
+ single: T_USHORT
+ single: T_UINT
+ single: T_ULONG
+ single: T_ULONGULONG
+ single: T_PYSSIZET
+ single: T_FLOAT
+ single: T_DOUBLE
+ single: T_BOOL
+ single: T_CHAR
+ single: T_STRING
+ single: T_STRING_INPLACE
+ single: T_OBJECT_EX
+ single: structmember.h
+
+.. versionadded:: 3.12
+
+ In previous versions, the macros were only available with
+ ``#include "structmember.h"`` and were named without the ``Py_`` prefix
+ (e.g. as ``T_INT``).
+ The header is still available and contains the old names, along with
+ the following deprecated types:
+
+ .. c:macro:: T_OBJECT
+
+ Like ``Py_T_OBJECT_EX``, but ``NULL`` is converted to ``None``.
+ This results in surprising behavior in Python: deleting the attribute
+ effectively sets it to ``None``.
+
+ .. c:macro:: T_NONE
+
+ Always ``None``. Must be used with :c:macro:`Py_READONLY`.
+
+Defining Getters and Setters
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. c:type:: PyGetSetDef
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index db8fc15..53895bb 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -386,6 +386,8 @@ function,PyMem_Malloc,3.2,,
function,PyMem_Realloc,3.2,,
type,PyMemberDef,3.2,,full-abi
var,PyMemberDescr_Type,3.2,,
+function,PyMember_GetOne,3.2,,
+function,PyMember_SetOne,3.2,,
function,PyMemoryView_FromBuffer,3.11,,
function,PyMemoryView_FromMemory,3.7,,
function,PyMemoryView_FromObject,3.2,,
diff --git a/Doc/extending/newtypes.rst b/Doc/extending/newtypes.rst
index 3de849a..80a1387 100644
--- a/Doc/extending/newtypes.rst
+++ b/Doc/extending/newtypes.rst
@@ -286,36 +286,11 @@ be read-only or read-write. The structures in the table are defined as::
For each entry in the table, a :term:`descriptor` will be constructed and added to the
type which will be able to extract a value from the instance structure. The
-:attr:`type` field should contain one of the type codes defined in the
-:file:`structmember.h` header; the value will be used to determine how to
+:attr:`type` field should contain a type code like :c:macro:`Py_T_INT` or
+:c:macro:`Py_T_DOUBLE`; the value will be used to determine how to
convert Python values to and from C values. The :attr:`flags` field is used to
-store flags which control how the attribute can be accessed.
-
-The following flag constants are defined in :file:`structmember.h`; they may be
-combined using bitwise-OR.
-
-+---------------------------+----------------------------------------------+
-| Constant | Meaning |
-+===========================+==============================================+
-| :const:`READONLY` | Never writable. |
-+---------------------------+----------------------------------------------+
-| :const:`PY_AUDIT_READ` | Emit an ``object.__getattr__`` |
-| | :ref:`audit events <audit-events>` before |
-| | reading. |
-+---------------------------+----------------------------------------------+
-
-.. versionchanged:: 3.10
- :const:`RESTRICTED`, :const:`READ_RESTRICTED` and :const:`WRITE_RESTRICTED`
- are deprecated. However, :const:`READ_RESTRICTED` is an alias for
- :const:`PY_AUDIT_READ`, so fields that specify either :const:`RESTRICTED`
- or :const:`READ_RESTRICTED` will also raise an audit event.
-
-.. index::
- single: READONLY
- single: READ_RESTRICTED
- single: WRITE_RESTRICTED
- single: RESTRICTED
- single: PY_AUDIT_READ
+store flags which control how the attribute can be accessed: you can set it to
+:c:macro:`Py_READONLY` to prevent Python code from setting it.
An interesting advantage of using the :c:member:`~PyTypeObject.tp_members` table to build
descriptors that are used at runtime is that any attribute defined this way can
diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst
index 5d4a3f0..54de3fd 100644
--- a/Doc/extending/newtypes_tutorial.rst
+++ b/Doc/extending/newtypes_tutorial.rst
@@ -239,13 +239,6 @@ adds these capabilities:
This version of the module has a number of changes.
-We've added an extra include::
-
- #include <structmember.h>
-
-This include provides declarations that we use to handle attributes, as
-described a bit later.
-
The :class:`Custom` type now has three data attributes in its C struct,
*first*, *last*, and *number*. The *first* and *last* variables are Python
strings containing first and last names. The *number* attribute is a C integer.
@@ -436,11 +429,11 @@ We want to expose our instance variables as attributes. There are a
number of ways to do that. The simplest way is to define member definitions::
static PyMemberDef Custom_members[] = {
- {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
+ {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
"first name"},
- {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
+ {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
"last name"},
- {"number", T_INT, offsetof(CustomObject, number), 0,
+ {"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
@@ -609,7 +602,7 @@ above. In this case, we aren't using a closure, so we just pass ``NULL``.
We also remove the member definitions for these attributes::
static PyMemberDef Custom_members[] = {
- {"number", T_INT, offsetof(CustomObject, number), 0,
+ {"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
diff --git a/Doc/includes/custom2.c b/Doc/includes/custom2.c
index aee9e1b..6638b9f 100644
--- a/Doc/includes/custom2.c
+++ b/Doc/includes/custom2.c
@@ -1,6 +1,6 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
-#include "structmember.h"
+#include <stddef.h> /* for offsetof() */
typedef struct {
PyObject_HEAD
@@ -63,11 +63,11 @@ Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
}
static PyMemberDef Custom_members[] = {
- {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
+ {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
"first name"},
- {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
+ {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
"last name"},
- {"number", T_INT, offsetof(CustomObject, number), 0,
+ {"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
diff --git a/Doc/includes/custom3.c b/Doc/includes/custom3.c
index 8d88bc2..0faf2bd 100644
--- a/Doc/includes/custom3.c
+++ b/Doc/includes/custom3.c
@@ -1,6 +1,6 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
-#include "structmember.h"
+#include <stddef.h> /* for offsetof() */
typedef struct {
PyObject_HEAD
@@ -63,7 +63,7 @@ Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
}
static PyMemberDef Custom_members[] = {
- {"number", T_INT, offsetof(CustomObject, number), 0,
+ {"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
diff --git a/Doc/includes/custom4.c b/Doc/includes/custom4.c
index ad240ae..b725bc0 100644
--- a/Doc/includes/custom4.c
+++ b/Doc/includes/custom4.c
@@ -1,6 +1,6 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
-#include "structmember.h"
+#include <stddef.h> /* for offsetof() */
typedef struct {
PyObject_HEAD
@@ -79,7 +79,7 @@ Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
}
static PyMemberDef Custom_members[] = {
- {"number", T_INT, offsetof(CustomObject, number), 0,
+ {"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index f8786c1..8e9a4f0 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -849,6 +849,37 @@ Deprecated
* Creating :c:data:`immutable types <Py_TPFLAGS_IMMUTABLETYPE>` with mutable
bases is deprecated and will be disabled in Python 3.14.
+* The ``structmember.h`` header is deprecated, though it continues to be
+ available and there are no plans to remove it.
+
+ Its contents are now available just by including ``Python.h``,
+ with a ``Py`` prefix added if it was missing:
+
+ - :c:struct:`PyMemberDef`, :c:func:`PyMember_GetOne` and
+ :c:func:`PyMember_SetOne`
+ - Type macros like :c:macro:`Py_T_INT`, :c:macro:`Py_T_DOUBLE`, etc.
+ (previously ``T_INT``, ``T_DOUBLE``, etc.)
+ - The flags :c:macro:`Py_READONLY` (previously ``READONLY``) and
+ :c:macro:`Py_AUDIT_READ` (previously all uppercase)
+
+ Several items are not exposed from ``Python.h``:
+
+ - :c:macro:`T_OBJECT` (use :c:macro:`Py_T_OBJECT_EX`)
+ - :c:macro:`T_NONE` (previously undocumented, and pretty quirky)
+ - The macro ``WRITE_RESTRICTED`` which does nothing.
+ - The macros ``RESTRICTED`` and ``READ_RESTRICTED``, equivalents of
+ :c:macro:`Py_AUDIT_READ`.
+ - In some configurations, ``<stddef.h>`` is not included from ``Python.h``.
+ It should be included manually when using ``offsetof()``.
+
+ The deprecated header continues to provide its original
+ contents under the original names.
+ Your old code can stay unchanged, unless the extra include and non-namespaced
+ macros bother you greatly.
+
+ (Contributed in :gh:`47146` by Petr Viktorin, based on
+ earlier work by Alexander Belopolsky and Matthias Braun.)
+
Removed
-------
diff --git a/Include/descrobject.h b/Include/descrobject.h
index 77f221d..0a420b8 100644
--- a/Include/descrobject.h
+++ b/Include/descrobject.h
@@ -32,6 +32,61 @@ PyAPI_FUNC(PyObject *) PyDescr_NewGetSet(PyTypeObject *, PyGetSetDef *);
PyAPI_FUNC(PyObject *) PyDictProxy_New(PyObject *);
PyAPI_FUNC(PyObject *) PyWrapper_New(PyObject *, PyObject *);
+
+/* An array of PyMemberDef structures defines the name, type and offset
+ of selected members of a C structure. These can be read by
+ PyMember_GetOne() and set by PyMember_SetOne() (except if their READONLY
+ flag is set). The array must be terminated with an entry whose name
+ pointer is NULL. */
+struct PyMemberDef {
+ const char *name;
+ int type;
+ Py_ssize_t offset;
+ int flags;
+ const char *doc;
+};
+
+// These constants used to be in structmember.h, not prefixed by Py_.
+// (structmember.h now has aliases to the new names.)
+
+/* Types */
+#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 // Deprecated, use Py_T_OBJECT_EX instead
+/* the ordering here is weird for binary compatibility */
+#define Py_T_CHAR 7 /* 1-character string */
+#define Py_T_BYTE 8 /* 8-bit signed int */
+/* unsigned variants: */
+#define Py_T_UBYTE 9
+#define Py_T_USHORT 10
+#define Py_T_UINT 11
+#define Py_T_ULONG 12
+
+/* Added by Jack: strings contained in the structure */
+#define Py_T_STRING_INPLACE 13
+
+/* Added by Lillo: bools contained in the structure (assumed char) */
+#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 /* Py_ssize_t */
+#define _Py_T_NONE 20 // Deprecated. Value is always None.
+
+/* Flags */
+#define Py_READONLY 1
+#define Py_AUDIT_READ 2 // Added in 3.10, harmless no-op before that
+#define _Py_WRITE_RESTRICTED 4 // Deprecated, no-op. Do not reuse the value.
+
+PyAPI_FUNC(PyObject *) PyMember_GetOne(const char *, PyMemberDef *);
+PyAPI_FUNC(int) PyMember_SetOne(char *, PyMemberDef *, PyObject *);
+
#ifndef Py_LIMITED_API
# define Py_CPYTHON_DESCROBJECT_H
# include "cpython/descrobject.h"
diff --git a/Include/structmember.h b/Include/structmember.h
index 65a777d..f6e8fd8 100644
--- a/Include/structmember.h
+++ b/Include/structmember.h
@@ -5,69 +5,50 @@ extern "C" {
#endif
-/* Interface to map C struct members to Python object attributes */
-
-#include <stddef.h> /* For offsetof */
-
-/* An array of PyMemberDef structures defines the name, type and offset
- of selected members of a C structure. These can be read by
- PyMember_GetOne() and set by PyMember_SetOne() (except if their READONLY
- flag is set). The array must be terminated with an entry whose name
- pointer is NULL. */
-
-struct PyMemberDef {
- const char *name;
- int type;
- Py_ssize_t offset;
- int flags;
- const char *doc;
-};
+/* Interface to map C struct members to Python object attributes
+ *
+ * This header is deprecated: new code should not use stuff from here.
+ * New definitions are in descrobject.h.
+ *
+ * However, there's nothing wrong with old code continuing to use it,
+ * and there's not much mainenance overhead in maintaining a few aliases.
+ * So, don't be too eager to convert old code.
+ *
+ * It uses names not prefixed with Py_.
+ * It is also *not* included from Python.h and must be included individually.
+ */
+
+#include <stddef.h> /* For offsetof (not always provided by Python.h) */
/* Types */
-#define T_SHORT 0
-#define T_INT 1
-#define T_LONG 2
-#define T_FLOAT 3
-#define T_DOUBLE 4
-#define T_STRING 5
-#define T_OBJECT 6
-/* XXX the ordering here is weird for binary compatibility */
-#define T_CHAR 7 /* 1-character string */
-#define T_BYTE 8 /* 8-bit signed int */
-/* unsigned variants: */
-#define T_UBYTE 9
-#define T_USHORT 10
-#define T_UINT 11
-#define T_ULONG 12
-
-/* Added by Jack: strings contained in the structure */
-#define T_STRING_INPLACE 13
-
-/* Added by Lillo: bools contained in the structure (assumed char) */
-#define T_BOOL 14
-
-#define T_OBJECT_EX 16 /* Like T_OBJECT, but raises AttributeError
- when the value is NULL, instead of
- converting to None. */
-#define T_LONGLONG 17
-#define T_ULONGLONG 18
-
-#define T_PYSSIZET 19 /* Py_ssize_t */
-#define T_NONE 20 /* Value is always None */
-
+#define T_SHORT Py_T_SHORT
+#define T_INT Py_T_INT
+#define T_LONG Py_T_LONG
+#define T_FLOAT Py_T_FLOAT
+#define T_DOUBLE Py_T_DOUBLE
+#define T_STRING Py_T_STRING
+#define T_OBJECT _Py_T_OBJECT
+#define T_CHAR Py_T_CHAR
+#define T_BYTE Py_T_BYTE
+#define T_UBYTE Py_T_UBYTE
+#define T_USHORT Py_T_USHORT
+#define T_UINT Py_T_UINT
+#define T_ULONG Py_T_ULONG
+#define T_STRING_INPLACE Py_T_STRING_INPLACE
+#define T_BOOL Py_T_BOOL
+#define T_OBJECT_EX Py_T_OBJECT_EX
+#define T_LONGLONG Py_T_LONGLONG
+#define T_ULONGLONG Py_T_ULONGLONG
+#define T_PYSSIZET Py_T_PYSSIZET
+#define T_NONE _Py_T_NONE
/* Flags */
-#define READONLY 1
-#define READ_RESTRICTED 2
-#define PY_WRITE_RESTRICTED 4
+#define READONLY Py_READONLY
+#define PY_AUDIT_READ Py_AUDIT_READ
+#define READ_RESTRICTED Py_AUDIT_READ
+#define PY_WRITE_RESTRICTED _Py_WRITE_RESTRICTED
#define RESTRICTED (READ_RESTRICTED | PY_WRITE_RESTRICTED)
-#define PY_AUDIT_READ READ_RESTRICTED
-
-/* Current API, use this */
-PyAPI_FUNC(PyObject *) PyMember_GetOne(const char *, PyMemberDef *);
-PyAPI_FUNC(int) PyMember_SetOne(char *, PyMemberDef *, PyObject *);
-
#ifdef __cplusplus
}
diff --git a/Lib/test/test_capi/test_structmembers.py b/Lib/test/test_capi/test_structmembers.py
index 07d2f62..2cf46b2 100644
--- a/Lib/test/test_capi/test_structmembers.py
+++ b/Lib/test/test_capi/test_structmembers.py
@@ -4,32 +4,42 @@ from test.support import warnings_helper
# Skip this test if the _testcapi module isn't available.
import_helper.import_module('_testcapi')
-from _testcapi import _test_structmembersType, \
- CHAR_MAX, CHAR_MIN, UCHAR_MAX, \
- SHRT_MAX, SHRT_MIN, USHRT_MAX, \
- 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
-
-ts=_test_structmembersType(False, # T_BOOL
- 1, # T_BYTE
- 2, # T_UBYTE
- 3, # T_SHORT
- 4, # T_USHORT
- 5, # T_INT
- 6, # T_UINT
- 7, # T_LONG
- 8, # T_ULONG
- 23, # T_PYSSIZET
- 9.99999,# T_FLOAT
- 10.1010101010, # T_DOUBLE
- "hi" # T_STRING_INPLACE
- )
-
-class ReadWriteTests(unittest.TestCase):
+from _testcapi import (_test_structmembersType_OldAPI,
+ _test_structmembersType_NewAPI,
+ CHAR_MAX, CHAR_MIN, UCHAR_MAX,
+ SHRT_MAX, SHRT_MIN, USHRT_MAX,
+ 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,
+ )
+
+# There are two classes: one using <structmember.h> and another using
+# `Py_`-prefixed API. They should behave the same in Python
+
+def _make_test_object(cls):
+ return cls(False, # T_BOOL
+ 1, # T_BYTE
+ 2, # T_UBYTE
+ 3, # T_SHORT
+ 4, # T_USHORT
+ 5, # T_INT
+ 6, # T_UINT
+ 7, # T_LONG
+ 8, # T_ULONG
+ 23, # T_PYSSIZET
+ 9.99999,# T_FLOAT
+ 10.1010101010, # T_DOUBLE
+ "hi", # T_STRING_INPLACE
+ )
+
+
+class ReadWriteTests:
+ def setUp(self):
+ self.ts = _make_test_object(self.cls)
def test_bool(self):
+ ts = self.ts
ts.T_BOOL = True
self.assertEqual(ts.T_BOOL, True)
ts.T_BOOL = False
@@ -37,6 +47,7 @@ class ReadWriteTests(unittest.TestCase):
self.assertRaises(TypeError, setattr, ts, 'T_BOOL', 1)
def test_byte(self):
+ ts = self.ts
ts.T_BYTE = CHAR_MAX
self.assertEqual(ts.T_BYTE, CHAR_MAX)
ts.T_BYTE = CHAR_MIN
@@ -45,6 +56,7 @@ class ReadWriteTests(unittest.TestCase):
self.assertEqual(ts.T_UBYTE, UCHAR_MAX)
def test_short(self):
+ ts = self.ts
ts.T_SHORT = SHRT_MAX
self.assertEqual(ts.T_SHORT, SHRT_MAX)
ts.T_SHORT = SHRT_MIN
@@ -53,6 +65,7 @@ class ReadWriteTests(unittest.TestCase):
self.assertEqual(ts.T_USHORT, USHRT_MAX)
def test_int(self):
+ ts = self.ts
ts.T_INT = INT_MAX
self.assertEqual(ts.T_INT, INT_MAX)
ts.T_INT = INT_MIN
@@ -61,6 +74,7 @@ class ReadWriteTests(unittest.TestCase):
self.assertEqual(ts.T_UINT, UINT_MAX)
def test_long(self):
+ ts = self.ts
ts.T_LONG = LONG_MAX
self.assertEqual(ts.T_LONG, LONG_MAX)
ts.T_LONG = LONG_MIN
@@ -69,13 +83,17 @@ class ReadWriteTests(unittest.TestCase):
self.assertEqual(ts.T_ULONG, ULONG_MAX)
def test_py_ssize_t(self):
+ ts = self.ts
ts.T_PYSSIZET = PY_SSIZE_T_MAX
self.assertEqual(ts.T_PYSSIZET, PY_SSIZE_T_MAX)
ts.T_PYSSIZET = PY_SSIZE_T_MIN
self.assertEqual(ts.T_PYSSIZET, PY_SSIZE_T_MIN)
- @unittest.skipUnless(hasattr(ts, "T_LONGLONG"), "long long not present")
def test_longlong(self):
+ ts = self.ts
+ if not hasattr(ts, "T_LONGLONG"):
+ self.skipTest("long long not present")
+
ts.T_LONGLONG = LLONG_MAX
self.assertEqual(ts.T_LONGLONG, LLONG_MAX)
ts.T_LONGLONG = LLONG_MIN
@@ -91,6 +109,7 @@ class ReadWriteTests(unittest.TestCase):
self.assertEqual(ts.T_ULONGLONG, 4)
def test_bad_assignments(self):
+ ts = self.ts
integer_attributes = [
'T_BOOL',
'T_BYTE', 'T_UBYTE',
@@ -109,37 +128,57 @@ class ReadWriteTests(unittest.TestCase):
self.assertRaises(TypeError, setattr, ts, attr, nonint)
def test_inplace_string(self):
+ ts = self.ts
self.assertEqual(ts.T_STRING_INPLACE, "hi")
self.assertRaises(TypeError, setattr, ts, "T_STRING_INPLACE", "s")
self.assertRaises(TypeError, delattr, ts, "T_STRING_INPLACE")
+class ReadWriteTests_OldAPI(ReadWriteTests, unittest.TestCase):
+ cls = _test_structmembersType_OldAPI
+
+class ReadWriteTests_NewAPI(ReadWriteTests, unittest.TestCase):
+ cls = _test_structmembersType_NewAPI
-class TestWarnings(unittest.TestCase):
+class TestWarnings:
+ def setUp(self):
+ self.ts = _make_test_object(self.cls)
def test_byte_max(self):
+ ts = self.ts
with warnings_helper.check_warnings(('', RuntimeWarning)):
ts.T_BYTE = CHAR_MAX+1
def test_byte_min(self):
+ ts = self.ts
with warnings_helper.check_warnings(('', RuntimeWarning)):
ts.T_BYTE = CHAR_MIN-1
def test_ubyte_max(self):
+ ts = self.ts
with warnings_helper.check_warnings(('', RuntimeWarning)):
ts.T_UBYTE = UCHAR_MAX+1
def test_short_max(self):
+ ts = self.ts
with warnings_helper.check_warnings(('', RuntimeWarning)):
ts.T_SHORT = SHRT_MAX+1
def test_short_min(self):
+ ts = self.ts
with warnings_helper.check_warnings(('', RuntimeWarning)):
ts.T_SHORT = SHRT_MIN-1
def test_ushort_max(self):
+ ts = self.ts
with warnings_helper.check_warnings(('', RuntimeWarning)):
ts.T_USHORT = USHRT_MAX+1
+class TestWarnings_OldAPI(TestWarnings, unittest.TestCase):
+ cls = _test_structmembersType_OldAPI
+
+class TestWarnings_NewAPI(TestWarnings, unittest.TestCase):
+ cls = _test_structmembersType_NewAPI
+
if __name__ == "__main__":
unittest.main()
diff --git a/Misc/NEWS.d/next/C API/2022-11-02-16-51-24.gh-issue-47146.dsYDtI.rst b/Misc/NEWS.d/next/C API/2022-11-02-16-51-24.gh-issue-47146.dsYDtI.rst
new file mode 100644
index 0000000..0f41942
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2022-11-02-16-51-24.gh-issue-47146.dsYDtI.rst
@@ -0,0 +1,5 @@
+The ``structmember.h`` header is deprecated. Its non-deprecated contents are
+now available just by including ``Python.h``, with a ``Py_`` prefix added if
+it was missing. (Deprecated contents are :c:macro:`T_OBJECT`,
+:c:macro:`T_NONE`, and no-op flags.) Patch by Petr Viktorin, based on
+earlier work by Alexander Belopolsky and Matthias Braun.
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index 0ba0f51..aa12bcc 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -94,7 +94,7 @@
added = '3.2'
struct_abi_kind = 'full-abi'
[struct.PyMemberDef]
- added = '3.2'
+ added = '3.2' # Before 3.12, PyMemberDef required #include "structmember.h"
struct_abi_kind = 'full-abi'
[struct.PyGetSetDef]
added = '3.2'
@@ -1777,11 +1777,9 @@
added = '3.2'
abi_only = true
[function.PyMember_GetOne]
- added = '3.2'
- abi_only = true
+ added = '3.2' # Before 3.12, available in "structmember.h"
[function.PyMember_SetOne]
- added = '3.2'
- abi_only = true
+ added = '3.2' # Before 3.12, available in "structmember.h"
# TLS api is deprecated; superseded by TSS API
@@ -2303,3 +2301,44 @@
added = '3.12'
[typedef.releasebufferproc]
added = '3.12'
+
+[const.Py_T_BYTE]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_SHORT]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_INT]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_LONG]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_LONGLONG]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_UBYTE]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_UINT]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_USHORT]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_ULONG]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_ULONGLONG]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_PYSSIZET]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_FLOAT]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_DOUBLE]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_BOOL]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_STRING]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_STRING_INPLACE]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_CHAR]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_OBJECT_EX]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_READONLY]
+ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_AUDIT_READ]
+ added = '3.12' # Before 3.12, available in "structmember.h"
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 7307d37..d64752e 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -169,7 +169,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
# Some testing modules MUST be built as shared libraries.
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index e25314a..7ba3c4e 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -35,6 +35,7 @@ int _PyTestCapi_Init_Mem(PyObject *module);
int _PyTestCapi_Init_Watchers(PyObject *module);
int _PyTestCapi_Init_Long(PyObject *module);
int _PyTestCapi_Init_Float(PyObject *module);
+int _PyTestCapi_Init_Structmember(PyObject *module);
#ifdef LIMITED_API_AVAILABLE
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
diff --git a/Modules/_testcapi/structmember.c b/Modules/_testcapi/structmember.c
new file mode 100644
index 0000000..0fb872a
--- /dev/null
+++ b/Modules/_testcapi/structmember.c
@@ -0,0 +1,217 @@
+#define PY_SSIZE_T_CLEAN
+#include "parts.h"
+#include <stddef.h> // for offsetof()
+
+
+// This defines two classes that contain all the simple member types, one
+// using "new" Py_-prefixed API, and the other using "old" <structmember.h>.
+// They should behave identically in Python.
+
+typedef struct {
+ char bool_member;
+ char byte_member;
+ unsigned char ubyte_member;
+ short short_member;
+ unsigned short ushort_member;
+ int int_member;
+ unsigned int uint_member;
+ long long_member;
+ unsigned long ulong_member;
+ Py_ssize_t pyssizet_member;
+ float float_member;
+ double double_member;
+ char inplace_member[6];
+ long long longlong_member;
+ unsigned long long ulonglong_member;
+} all_structmembers;
+
+typedef struct {
+ PyObject_HEAD
+ all_structmembers structmembers;
+} test_structmembers;
+
+
+static struct PyMemberDef test_members_newapi[] = {
+ {"T_BOOL", Py_T_BOOL, offsetof(test_structmembers, structmembers.bool_member), 0, NULL},
+ {"T_BYTE", Py_T_BYTE, offsetof(test_structmembers, structmembers.byte_member), 0, NULL},
+ {"T_UBYTE", Py_T_UBYTE, offsetof(test_structmembers, structmembers.ubyte_member), 0, NULL},
+ {"T_SHORT", Py_T_SHORT, offsetof(test_structmembers, structmembers.short_member), 0, NULL},
+ {"T_USHORT", Py_T_USHORT, offsetof(test_structmembers, structmembers.ushort_member), 0, NULL},
+ {"T_INT", Py_T_INT, offsetof(test_structmembers, structmembers.int_member), 0, NULL},
+ {"T_UINT", Py_T_UINT, offsetof(test_structmembers, structmembers.uint_member), 0, NULL},
+ {"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_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},
+ {NULL}
+};
+
+static PyObject *
+test_structmembers_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ static char *keywords[] = {
+ "T_BOOL", "T_BYTE", "T_UBYTE", "T_SHORT", "T_USHORT",
+ "T_INT", "T_UINT", "T_LONG", "T_ULONG", "T_PYSSIZET",
+ "T_FLOAT", "T_DOUBLE", "T_STRING_INPLACE",
+ "T_LONGLONG", "T_ULONGLONG",
+ NULL};
+ static const char fmt[] = "|bbBhHiIlknfds#LK";
+ test_structmembers *ob;
+ const char *s = NULL;
+ Py_ssize_t string_len = 0;
+ ob = PyObject_New(test_structmembers, type);
+ if (ob == NULL) {
+ return NULL;
+ }
+ memset(&ob->structmembers, 0, sizeof(all_structmembers));
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, fmt, keywords,
+ &ob->structmembers.bool_member,
+ &ob->structmembers.byte_member,
+ &ob->structmembers.ubyte_member,
+ &ob->structmembers.short_member,
+ &ob->structmembers.ushort_member,
+ &ob->structmembers.int_member,
+ &ob->structmembers.uint_member,
+ &ob->structmembers.long_member,
+ &ob->structmembers.ulong_member,
+ &ob->structmembers.pyssizet_member,
+ &ob->structmembers.float_member,
+ &ob->structmembers.double_member,
+ &s, &string_len,
+ &ob->structmembers.longlong_member,
+ &ob->structmembers.ulonglong_member))
+ {
+ Py_DECREF(ob);
+ return NULL;
+ }
+ if (s != NULL) {
+ if (string_len > 5) {
+ Py_DECREF(ob);
+ PyErr_SetString(PyExc_ValueError, "string too long");
+ return NULL;
+ }
+ strcpy(ob->structmembers.inplace_member, s);
+ }
+ else {
+ strcpy(ob->structmembers.inplace_member, "");
+ }
+ return (PyObject *)ob;
+}
+
+static PyType_Slot test_structmembers_slots[] = {
+ {Py_tp_new, test_structmembers_new},
+ {Py_tp_members, test_members_newapi},
+ {0},
+};
+
+static PyType_Spec test_structmembers_spec = {
+ .name = "_testcapi._test_structmembersType_NewAPI",
+ .flags = Py_TPFLAGS_DEFAULT,
+ .basicsize = sizeof(test_structmembers),
+ .slots = test_structmembers_slots,
+};
+
+#include <structmember.h>
+
+static struct PyMemberDef test_members[] = {
+ {"T_BOOL", T_BOOL, offsetof(test_structmembers, structmembers.bool_member), 0, NULL},
+ {"T_BYTE", T_BYTE, offsetof(test_structmembers, structmembers.byte_member), 0, NULL},
+ {"T_UBYTE", T_UBYTE, offsetof(test_structmembers, structmembers.ubyte_member), 0, NULL},
+ {"T_SHORT", T_SHORT, offsetof(test_structmembers, structmembers.short_member), 0, NULL},
+ {"T_USHORT", T_USHORT, offsetof(test_structmembers, structmembers.ushort_member), 0, NULL},
+ {"T_INT", T_INT, offsetof(test_structmembers, structmembers.int_member), 0, NULL},
+ {"T_UINT", T_UINT, offsetof(test_structmembers, structmembers.uint_member), 0, NULL},
+ {"T_LONG", T_LONG, offsetof(test_structmembers, structmembers.long_member), 0, NULL},
+ {"T_ULONG", T_ULONG, offsetof(test_structmembers, structmembers.ulong_member), 0, NULL},
+ {"T_PYSSIZET", T_PYSSIZET, offsetof(test_structmembers, structmembers.pyssizet_member), 0, NULL},
+ {"T_FLOAT", T_FLOAT, offsetof(test_structmembers, structmembers.float_member), 0, NULL},
+ {"T_DOUBLE", T_DOUBLE, offsetof(test_structmembers, structmembers.double_member), 0, NULL},
+ {"T_STRING_INPLACE", T_STRING_INPLACE, offsetof(test_structmembers, structmembers.inplace_member), 0, NULL},
+ {"T_LONGLONG", T_LONGLONG, offsetof(test_structmembers, structmembers.longlong_member), 0, NULL},
+ {"T_ULONGLONG", T_ULONGLONG, offsetof(test_structmembers, structmembers.ulonglong_member), 0, NULL},
+ {NULL}
+};
+
+
+static void
+test_structmembers_free(PyObject *ob)
+{
+ PyObject_Free(ob);
+}
+
+/* Designated initializers would work too, but this does test the *old* API */
+static PyTypeObject test_structmembersType_OldAPI= {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "test_structmembersType_OldAPI",
+ sizeof(test_structmembers), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ test_structmembers_free, /* destructor tp_dealloc */
+ 0, /* tp_vectorcall_offset */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_as_async */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ PyObject_GenericGetAttr, /* tp_getattro */
+ PyObject_GenericSetAttr, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ 0, /* tp_flags */
+ "Type containing all structmember types",
+ 0, /* traverseproc tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ test_members, /* tp_members */
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ test_structmembers_new, /* tp_new */
+};
+
+
+int
+_PyTestCapi_Init_Structmember(PyObject *m)
+{
+ int res;
+ res = PyType_Ready(&test_structmembersType_OldAPI);
+ if (res < 0) {
+ return -1;
+ }
+ res = PyModule_AddObject(
+ m,
+ "_test_structmembersType_OldAPI",
+ (PyObject *)&test_structmembersType_OldAPI);
+ if (res < 0) {
+ return -1;
+ }
+
+ PyObject *test_structmembersType_NewAPI = PyType_FromModuleAndSpec(
+ m, &test_structmembers_spec, NULL);
+ if (!test_structmembersType_NewAPI) {
+ return -1;
+ }
+ res = PyModule_AddType(m, (PyTypeObject*)test_structmembersType_NewAPI);
+ Py_DECREF(test_structmembersType_NewAPI);
+ if (res < 0) {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 9dd09f6..83eef73 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -21,7 +21,7 @@
#include "Python.h"
#include "marshal.h" // PyMarshal_WriteLongToFile
-#include "structmember.h" // PyMemberDef
+#include "structmember.h" // for offsetof(), T_OBJECT
#include <float.h> // FLT_MAX
#include <signal.h>
@@ -3371,147 +3371,6 @@ static PyMethodDef TestMethods[] = {
{NULL, NULL} /* sentinel */
};
-typedef struct {
- char bool_member;
- char byte_member;
- unsigned char ubyte_member;
- short short_member;
- unsigned short ushort_member;
- int int_member;
- unsigned int uint_member;
- long long_member;
- unsigned long ulong_member;
- Py_ssize_t pyssizet_member;
- float float_member;
- double double_member;
- char inplace_member[6];
- long long longlong_member;
- unsigned long long ulonglong_member;
-} all_structmembers;
-
-typedef struct {
- PyObject_HEAD
- all_structmembers structmembers;
-} test_structmembers;
-
-static struct PyMemberDef test_members[] = {
- {"T_BOOL", T_BOOL, offsetof(test_structmembers, structmembers.bool_member), 0, NULL},
- {"T_BYTE", T_BYTE, offsetof(test_structmembers, structmembers.byte_member), 0, NULL},
- {"T_UBYTE", T_UBYTE, offsetof(test_structmembers, structmembers.ubyte_member), 0, NULL},
- {"T_SHORT", T_SHORT, offsetof(test_structmembers, structmembers.short_member), 0, NULL},
- {"T_USHORT", T_USHORT, offsetof(test_structmembers, structmembers.ushort_member), 0, NULL},
- {"T_INT", T_INT, offsetof(test_structmembers, structmembers.int_member), 0, NULL},
- {"T_UINT", T_UINT, offsetof(test_structmembers, structmembers.uint_member), 0, NULL},
- {"T_LONG", T_LONG, offsetof(test_structmembers, structmembers.long_member), 0, NULL},
- {"T_ULONG", T_ULONG, offsetof(test_structmembers, structmembers.ulong_member), 0, NULL},
- {"T_PYSSIZET", T_PYSSIZET, offsetof(test_structmembers, structmembers.pyssizet_member), 0, NULL},
- {"T_FLOAT", T_FLOAT, offsetof(test_structmembers, structmembers.float_member), 0, NULL},
- {"T_DOUBLE", T_DOUBLE, offsetof(test_structmembers, structmembers.double_member), 0, NULL},
- {"T_STRING_INPLACE", T_STRING_INPLACE, offsetof(test_structmembers, structmembers.inplace_member), 0, NULL},
- {"T_LONGLONG", T_LONGLONG, offsetof(test_structmembers, structmembers.longlong_member), 0, NULL},
- {"T_ULONGLONG", T_ULONGLONG, offsetof(test_structmembers, structmembers.ulonglong_member), 0, NULL},
- {NULL}
-};
-
-
-static PyObject *
-test_structmembers_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
-{
- static char *keywords[] = {
- "T_BOOL", "T_BYTE", "T_UBYTE", "T_SHORT", "T_USHORT",
- "T_INT", "T_UINT", "T_LONG", "T_ULONG", "T_PYSSIZET",
- "T_FLOAT", "T_DOUBLE", "T_STRING_INPLACE",
- "T_LONGLONG", "T_ULONGLONG",
- NULL};
- static const char fmt[] = "|bbBhHiIlknfds#LK";
- test_structmembers *ob;
- const char *s = NULL;
- Py_ssize_t string_len = 0;
- ob = PyObject_New(test_structmembers, type);
- if (ob == NULL)
- return NULL;
- memset(&ob->structmembers, 0, sizeof(all_structmembers));
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, fmt, keywords,
- &ob->structmembers.bool_member,
- &ob->structmembers.byte_member,
- &ob->structmembers.ubyte_member,
- &ob->structmembers.short_member,
- &ob->structmembers.ushort_member,
- &ob->structmembers.int_member,
- &ob->structmembers.uint_member,
- &ob->structmembers.long_member,
- &ob->structmembers.ulong_member,
- &ob->structmembers.pyssizet_member,
- &ob->structmembers.float_member,
- &ob->structmembers.double_member,
- &s, &string_len
- , &ob->structmembers.longlong_member,
- &ob->structmembers.ulonglong_member
- )) {
- Py_DECREF(ob);
- return NULL;
- }
- if (s != NULL) {
- if (string_len > 5) {
- Py_DECREF(ob);
- PyErr_SetString(PyExc_ValueError, "string too long");
- return NULL;
- }
- strcpy(ob->structmembers.inplace_member, s);
- }
- else {
- strcpy(ob->structmembers.inplace_member, "");
- }
- return (PyObject *)ob;
-}
-
-static void
-test_structmembers_free(PyObject *ob)
-{
- PyObject_Free(ob);
-}
-
-static PyTypeObject test_structmembersType = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "test_structmembersType",
- sizeof(test_structmembers), /* tp_basicsize */
- 0, /* tp_itemsize */
- test_structmembers_free, /* destructor tp_dealloc */
- 0, /* tp_vectorcall_offset */
- 0, /* tp_getattr */
- 0, /* tp_setattr */
- 0, /* tp_as_async */
- 0, /* tp_repr */
- 0, /* tp_as_number */
- 0, /* tp_as_sequence */
- 0, /* tp_as_mapping */
- 0, /* tp_hash */
- 0, /* tp_call */
- 0, /* tp_str */
- PyObject_GenericGetAttr, /* tp_getattro */
- PyObject_GenericSetAttr, /* tp_setattro */
- 0, /* tp_as_buffer */
- 0, /* tp_flags */
- "Type containing all structmember types",
- 0, /* traverseproc tp_traverse */
- 0, /* tp_clear */
- 0, /* tp_richcompare */
- 0, /* tp_weaklistoffset */
- 0, /* tp_iter */
- 0, /* tp_iternext */
- 0, /* tp_methods */
- test_members, /* tp_members */
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- test_structmembers_new, /* tp_new */
-};
-
typedef struct {
PyObject_HEAD
@@ -4064,11 +3923,6 @@ PyInit__testcapi(void)
Py_SET_TYPE(&_HashInheritanceTester_Type, &PyType_Type);
- Py_SET_TYPE(&test_structmembersType, &PyType_Type);
- Py_INCREF(&test_structmembersType);
- /* don't use a name starting with "test", since we don't want
- test_capi to automatically call this */
- PyModule_AddObject(m, "_test_structmembersType", (PyObject *)&test_structmembersType);
if (PyType_Ready(&matmulType) < 0)
return NULL;
Py_INCREF(&matmulType);
@@ -4197,6 +4051,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_Float(m) < 0) {
return NULL;
}
+ if (_PyTestCapi_Init_Structmember(m) < 0) {
+ return NULL;
+ }
#ifndef LIMITED_API_AVAILABLE
PyModule_AddObjectRef(m, "LIMITED_API_AVAILABLE", Py_False);
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index d91cdfe..58bf4e1 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -106,6 +106,7 @@
<ClCompile Include="..\Modules\_testcapi\watchers.c" />
<ClCompile Include="..\Modules\_testcapi\float.c" />
<ClCompile Include="..\Modules\_testcapi\long.c" />
+ <ClCompile Include="..\Modules\_testcapi\structmember.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index 1b112b1..101c532 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -48,6 +48,9 @@
<ClCompile Include="..\Modules\_testcapi\long.c">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\Modules\_testcapi\structmember.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc">