summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWenzel Jakob <wenzel.jakob@epfl.ch>2022-05-27 08:27:39 (GMT)
committerGitHub <noreply@github.com>2022-05-27 08:27:39 (GMT)
commit5e34b494a08015e9b5a3deade23943bdba284a93 (patch)
tree829c824df2d307b14d0cf9efc51e2bbfcca92589
parent20d30ba2ccf9182e4f08db112f428c909148a40b (diff)
downloadcpython-5e34b494a08015e9b5a3deade23943bdba284a93.zip
cpython-5e34b494a08015e9b5a3deade23943bdba284a93.tar.gz
cpython-5e34b494a08015e9b5a3deade23943bdba284a93.tar.bz2
gh-60074: add new stable API function PyType_FromMetaclass (GH-93012)
Added a new stable API function ``PyType_FromMetaclass``, which mirrors the behavior of ``PyType_FromModuleAndSpec`` except that it takes an additional metaclass argument. This is, e.g., useful for language binding tools that need to store additional information in the type object.
-rw-r--r--Doc/c-api/type.rst20
-rw-r--r--Doc/c-api/typeobj.rst2
-rw-r--r--Doc/data/stable_abi.dat1
-rw-r--r--Doc/whatsnew/3.12.rst5
-rw-r--r--Include/object.h3
-rw-r--r--Lib/test/test_capi.py13
-rw-r--r--Lib/test/test_stable_abi_ctypes.py1
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2022-05-20-13-32-24.gh-issue-93012.e9B-pv.rst8
-rw-r--r--Misc/stable_abi.toml2
-rw-r--r--Modules/_testcapimodule.c73
-rw-r--r--Objects/typeobject.c35
-rwxr-xr-xPC/python3dll.c1
12 files changed, 150 insertions, 14 deletions
diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst
index d740e4e..99b3845 100644
--- a/Doc/c-api/type.rst
+++ b/Doc/c-api/type.rst
@@ -190,11 +190,16 @@ Creating Heap-Allocated Types
The following functions and structs are used to create
:ref:`heap types <heap-types>`.
-.. c:function:: PyObject* PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
+.. c:function:: PyObject* PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module, PyType_Spec *spec, PyObject *bases)
- Creates and returns a :ref:`heap type <heap-types>` from the *spec*
+ Create and return a :ref:`heap type <heap-types>` from the *spec*
(:const:`Py_TPFLAGS_HEAPTYPE`).
+ The metaclass *metaclass* is used to construct the resulting type object.
+ When *metaclass* is ``NULL``, the default :c:type:`PyType_Type` is used
+ instead. Note that metaclasses that override
+ :c:member:`~PyTypeObject.tp_new` are not supported.
+
The *bases* argument can be used to specify base classes; it can either
be only one class or a tuple of classes.
If *bases* is ``NULL``, the *Py_tp_bases* slot is used instead.
@@ -210,6 +215,12 @@ The following functions and structs are used to create
This function calls :c:func:`PyType_Ready` on the new type.
+ .. versionadded:: 3.12
+
+.. c:function:: PyObject* PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
+
+ Equivalent to ``PyType_FromMetaclass(NULL, module, spec, bases)``.
+
.. versionadded:: 3.9
.. versionchanged:: 3.10
@@ -217,15 +228,16 @@ The following functions and structs are used to create
The function now accepts a single class as the *bases* argument and
``NULL`` as the ``tp_doc`` slot.
+
.. c:function:: PyObject* PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
- Equivalent to ``PyType_FromModuleAndSpec(NULL, spec, bases)``.
+ Equivalent to ``PyType_FromMetaclass(NULL, NULL, spec, bases)``.
.. versionadded:: 3.3
.. c:function:: PyObject* PyType_FromSpec(PyType_Spec *spec)
- Equivalent to ``PyType_FromSpecWithBases(spec, NULL)``.
+ Equivalent to ``PyType_FromMetaclass(NULL, NULL, spec, NULL)``.
.. c:type:: PyType_Spec
diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst
index b3f371b..df47904 100644
--- a/Doc/c-api/typeobj.rst
+++ b/Doc/c-api/typeobj.rst
@@ -2071,7 +2071,7 @@ flag set.
This is done by filling a :c:type:`PyType_Spec` structure and calling
:c:func:`PyType_FromSpec`, :c:func:`PyType_FromSpecWithBases`,
-or :c:func:`PyType_FromModuleAndSpec`.
+:c:func:`PyType_FromModuleAndSpec`, or :c:func:`PyType_FromMetaclass`.
.. _number-structs:
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 3912a7c..82cd579 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -653,6 +653,7 @@ function,PyTuple_Size,3.2,,
var,PyTuple_Type,3.2,,
type,PyTypeObject,3.2,,opaque
function,PyType_ClearCache,3.2,,
+function,PyType_FromMetaclass,3.12,,
function,PyType_FromModuleAndSpec,3.10,,
function,PyType_FromSpec,3.2,,
function,PyType_FromSpecWithBases,3.3,,
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index 033de17..fd48784 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -151,6 +151,11 @@ C API Changes
New Features
------------
+* Added the new limited C API function :c:func:`PyType_FromMetaclass`,
+ which generalizes the existing :c:func:`PyType_FromModuleAndSpec` using
+ an additional metaclass argument.
+ (Contributed by Wenzel Jakob in :gh:`93012`.)
+
Porting to Python 3.12
----------------------
diff --git a/Include/object.h b/Include/object.h
index f01b9fa..a3c6bd4 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -257,6 +257,9 @@ PyAPI_FUNC(void *) PyType_GetModuleState(PyTypeObject *);
PyAPI_FUNC(PyObject *) PyType_GetName(PyTypeObject *);
PyAPI_FUNC(PyObject *) PyType_GetQualName(PyTypeObject *);
#endif
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030C0000
+PyAPI_FUNC(PyObject *) PyType_FromMetaclass(PyTypeObject*, PyObject*, PyType_Spec*, PyObject*);
+#endif
/* Generic type check */
PyAPI_FUNC(int) PyType_IsSubtype(PyTypeObject *, PyTypeObject *);
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 49cd821..95930ba 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -605,6 +605,19 @@ class CAPITest(unittest.TestCase):
del obj.value
self.assertEqual(obj.pvalue, 0)
+ def test_heaptype_with_custom_metaclass(self):
+ self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclass, type))
+ self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclassCustomNew, type))
+
+ t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclass)
+ self.assertIsInstance(t, type)
+ self.assertEqual(t.__name__, "HeapCTypeViaMetaclass")
+ self.assertIs(type(t), _testcapi.HeapCTypeMetaclass)
+
+ msg = "Metaclasses with custom tp_new are not supported."
+ with self.assertRaisesRegex(TypeError, msg):
+ t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew)
+
def test_pynumber_tobase(self):
from _testcapi import pynumber_tobase
self.assertEqual(pynumber_tobase(123, 2), '0b1111011')
diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py
index 18c8506..53e93ab 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -660,6 +660,7 @@ SYMBOL_NAMES = (
"PyTuple_Size",
"PyTuple_Type",
"PyType_ClearCache",
+ "PyType_FromMetaclass",
"PyType_FromModuleAndSpec",
"PyType_FromSpec",
"PyType_FromSpecWithBases",
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-20-13-32-24.gh-issue-93012.e9B-pv.rst b/Misc/NEWS.d/next/Core and Builtins/2022-05-20-13-32-24.gh-issue-93012.e9B-pv.rst
new file mode 100644
index 0000000..8de0f00
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-05-20-13-32-24.gh-issue-93012.e9B-pv.rst
@@ -0,0 +1,8 @@
+Added the new function :c:func:`PyType_FromMetaclass`, which generalizes the
+existing :c:func:`PyType_FromModuleAndSpec` using an additional metaclass
+argument. This is useful for language binding tools, where it can be used to
+intercept type-related operations like subclassing or static attribute access
+by specifying a metaclass with custom slots.
+
+Importantly, :c:func:`PyType_FromMetaclass` is available in the Limited API,
+which provides a path towards migrating more binding tools onto the Stable ABI.
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index d848f18..84bec82 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -2275,3 +2275,5 @@
added = '3.11'
[function.PyErr_SetHandledException]
added = '3.11'
+[function.PyType_FromMetaclass]
+ added = '3.12'
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 3bc7761..37f4ded 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -308,6 +308,32 @@ test_dict_inner(int count)
}
}
+static PyObject *pytype_fromspec_meta(PyObject* self, PyObject *meta)
+{
+ if (!PyType_Check(meta)) {
+ PyErr_SetString(
+ TestError,
+ "pytype_fromspec_meta: must be invoked with a type argument!");
+ return NULL;
+ }
+
+ PyType_Slot HeapCTypeViaMetaclass_slots[] = {
+ {0},
+ };
+
+ PyType_Spec HeapCTypeViaMetaclass_spec = {
+ "_testcapi.HeapCTypeViaMetaclass",
+ sizeof(PyObject),
+ 0,
+ Py_TPFLAGS_DEFAULT,
+ HeapCTypeViaMetaclass_slots
+ };
+
+ return PyType_FromMetaclass(
+ (PyTypeObject *) meta, NULL, &HeapCTypeViaMetaclass_spec, NULL);
+}
+
+
static PyObject*
test_dict_iteration(PyObject* self, PyObject *Py_UNUSED(ignored))
{
@@ -5886,6 +5912,7 @@ static PyMethodDef TestMethods[] = {
{"test_long_numbits", test_long_numbits, METH_NOARGS},
{"test_k_code", test_k_code, METH_NOARGS},
{"test_empty_argparse", test_empty_argparse, METH_NOARGS},
+ {"pytype_fromspec_meta", pytype_fromspec_meta, METH_O},
{"parse_tuple_and_keywords", parse_tuple_and_keywords, METH_VARARGS},
{"pyobject_repr_from_null", pyobject_repr_from_null, METH_NOARGS},
{"pyobject_str_from_null", pyobject_str_from_null, METH_NOARGS},
@@ -7078,6 +7105,38 @@ static PyType_Spec HeapCTypeSubclassWithFinalizer_spec = {
HeapCTypeSubclassWithFinalizer_slots
};
+static PyType_Slot HeapCTypeMetaclass_slots[] = {
+ {0},
+};
+
+static PyType_Spec HeapCTypeMetaclass_spec = {
+ "_testcapi.HeapCTypeMetaclass",
+ sizeof(PyHeapTypeObject),
+ sizeof(PyMemberDef),
+ Py_TPFLAGS_DEFAULT,
+ HeapCTypeMetaclass_slots
+};
+
+static PyObject *
+heap_ctype_metaclass_custom_tp_new(PyTypeObject *tp, PyObject *args, PyObject *kwargs)
+{
+ return PyType_Type.tp_new(tp, args, kwargs);
+}
+
+static PyType_Slot HeapCTypeMetaclassCustomNew_slots[] = {
+ { Py_tp_new, heap_ctype_metaclass_custom_tp_new },
+ {0},
+};
+
+static PyType_Spec HeapCTypeMetaclassCustomNew_spec = {
+ "_testcapi.HeapCTypeMetaclassCustomNew",
+ sizeof(PyHeapTypeObject),
+ sizeof(PyMemberDef),
+ Py_TPFLAGS_DEFAULT,
+ HeapCTypeMetaclassCustomNew_slots
+};
+
+
typedef struct {
PyObject_HEAD
PyObject *dict;
@@ -7591,6 +7650,20 @@ PyInit__testcapi(void)
Py_DECREF(subclass_with_finalizer_bases);
PyModule_AddObject(m, "HeapCTypeSubclassWithFinalizer", HeapCTypeSubclassWithFinalizer);
+ PyObject *HeapCTypeMetaclass = PyType_FromMetaclass(
+ &PyType_Type, m, &HeapCTypeMetaclass_spec, (PyObject *) &PyType_Type);
+ if (HeapCTypeMetaclass == NULL) {
+ return NULL;
+ }
+ PyModule_AddObject(m, "HeapCTypeMetaclass", HeapCTypeMetaclass);
+
+ PyObject *HeapCTypeMetaclassCustomNew = PyType_FromMetaclass(
+ &PyType_Type, m, &HeapCTypeMetaclassCustomNew_spec, (PyObject *) &PyType_Type);
+ if (HeapCTypeMetaclassCustomNew == NULL) {
+ return NULL;
+ }
+ PyModule_AddObject(m, "HeapCTypeMetaclassCustomNew", HeapCTypeMetaclassCustomNew);
+
if (PyType_Ready(&ContainerNoGC_type) < 0) {
return NULL;
}
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 1daf2b8..ff5196c 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -3366,13 +3366,8 @@ static const PySlot_Offset pyslot_offsets[] = {
};
PyObject *
-PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
-{
- return PyType_FromModuleAndSpec(NULL, spec, bases);
-}
-
-PyObject *
-PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
+PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
+ PyType_Spec *spec, PyObject *bases)
{
PyHeapTypeObject *res;
PyObject *modname;
@@ -3384,6 +3379,16 @@ PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
char *res_start;
short slot_offset, subslot_offset;
+ if (!metaclass) {
+ metaclass = &PyType_Type;
+ }
+
+ if (metaclass->tp_new != PyType_Type.tp_new) {
+ PyErr_SetString(PyExc_TypeError,
+ "Metaclasses with custom tp_new are not supported.");
+ return NULL;
+ }
+
nmembers = weaklistoffset = dictoffset = vectorcalloffset = 0;
for (slot = spec->slots; slot->slot; slot++) {
if (slot->slot == Py_tp_members) {
@@ -3412,7 +3417,7 @@ PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
}
}
- res = (PyHeapTypeObject*)PyType_GenericAlloc(&PyType_Type, nmembers);
+ res = (PyHeapTypeObject*)metaclass->tp_alloc(metaclass, nmembers);
if (res == NULL)
return NULL;
res_start = (char*)res;
@@ -3640,9 +3645,21 @@ PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
}
PyObject *
+PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
+{
+ return PyType_FromMetaclass(NULL, module, spec, bases);
+}
+
+PyObject *
+PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
+{
+ return PyType_FromMetaclass(NULL, NULL, spec, bases);
+}
+
+PyObject *
PyType_FromSpec(PyType_Spec *spec)
{
- return PyType_FromSpecWithBases(spec, NULL);
+ return PyType_FromMetaclass(NULL, NULL, spec, NULL);
}
PyObject *
diff --git a/PC/python3dll.c b/PC/python3dll.c
index 50e7a96..024ec49 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -599,6 +599,7 @@ EXPORT_FUNC(PyTuple_Pack)
EXPORT_FUNC(PyTuple_SetItem)
EXPORT_FUNC(PyTuple_Size)
EXPORT_FUNC(PyType_ClearCache)
+EXPORT_FUNC(PyType_FromMetaclass)
EXPORT_FUNC(PyType_FromModuleAndSpec)
EXPORT_FUNC(PyType_FromSpec)
EXPORT_FUNC(PyType_FromSpecWithBases)