]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-100554: Add ``Py_tp_vectorcall`` slot to set ``PyTypeObject.tp_vectorcall`` using...
authorWenzel Jakob <wenzel.jakob@epfl.ch>
Fri, 13 Sep 2024 15:40:25 +0000 (00:40 +0900)
committerGitHub <noreply@github.com>
Fri, 13 Sep 2024 15:40:25 +0000 (17:40 +0200)
Doc/c-api/type.rst
Doc/c-api/typeobj.rst
Include/typeslots.h
Lib/test/test_capi/test_misc.py
Misc/NEWS.d/next/C_API/2024-08-26-13-01-20.gh-issue-100554.0ku85o.rst [new file with mode: 0644]
Misc/stable_abi.toml
Modules/_testcapi/heaptype.c
Objects/typeslots.inc

index fa04d6c0ad5da59c8ce620f6c66b384b54710765..04aaf7f46f2e3c3218a9aaf22b80912c74839bef 100644 (file)
@@ -504,11 +504,8 @@ The following functions and structs are used to create
          See :ref:`PyMemberDef documentation <pymemberdef-offsets>`
          for details.
 
-      The following fields cannot be set at all when creating a heap type:
-
-      * :c:member:`~PyTypeObject.tp_vectorcall`
-        (use :c:member:`~PyTypeObject.tp_new` and/or
-        :c:member:`~PyTypeObject.tp_init`)
+      The following internal fields cannot be set at all when creating a heap
+      type:
 
       * Internal fields:
         :c:member:`~PyTypeObject.tp_dict`,
@@ -531,6 +528,12 @@ The following functions and structs are used to create
         :c:member:`~PyBufferProcs.bf_releasebuffer` are now available
         under the :ref:`limited API <limited-c-api>`.
 
+      .. versionchanged:: 3.14
+
+         The field :c:member:`~PyTypeObject.tp_vectorcall` can now set
+         using ``Py_tp_vectorcall``.  See the field's documentation
+         for details.
+
    .. c:member:: void *pfunc
 
       The desired value of the slot. In most cases, this is a pointer
index b7b1418df513c65edf31c19c4e5ef714b5754e45..cfe4563d744b8a24e1cf17815f2a067212129330 100644 (file)
@@ -2137,11 +2137,40 @@ and :c:data:`PyType_Type` effectively act as defaults.)
 
 .. c:member:: vectorcallfunc PyTypeObject.tp_vectorcall
 
-   Vectorcall function to use for calls of this type object.
-   In other words, it is used to implement
-   :ref:`vectorcall <vectorcall>` for ``type.__call__``.
-   If ``tp_vectorcall`` is ``NULL``, the default call implementation
-   using :meth:`~object.__new__` and :meth:`~object.__init__` is used.
+   A :ref:`vectorcall function <vectorcall>` to use for calls of this type
+   object (rather than instances).
+   In other words, ``tp_vectorcall`` can be used to optimize ``type.__call__``,
+   which typically returns a new instance of *type*.
+
+   As with any vectorcall function, if ``tp_vectorcall`` is ``NULL``,
+   the *tp_call* protocol (``Py_TYPE(type)->tp_call``) is used instead.
+
+   .. note::
+
+      The :ref:`vectorcall protocol <vectorcall>` requires that the vectorcall
+      function has the same behavior as the corresponding ``tp_call``.
+      This means that ``type->tp_vectorcall`` must match the behavior of
+      ``Py_TYPE(type)->tp_call``.
+
+      Specifically, if *type* uses the default metaclass,
+      ``type->tp_vectorcall`` must behave the same as
+      :c:expr:`PyType_Type->tp_call`, which:
+
+      - calls ``type->tp_new``,
+
+      - if the result is a subclass of *type*, calls ``type->tp_init``
+        on the result of ``tp_new``, and
+
+      - returns the result of ``tp_new``.
+
+      Typically, ``tp_vectorcall`` is overridden to optimize this process
+      for specific :c:member:`~PyTypeObject.tp_new` and
+      :c:member:`~PyTypeObject.tp_init`.
+      When doing this for user-subclassable types, note that both can be
+      overridden (using :py:func:`~object.__new__` and
+      :py:func:`~object.__init__`, respectively).
+
+
 
    **Inheritance:**
 
index 506b05580de146bbcae3e25e1e9ec18c773e9014..e91caa1509c34b80cb32e13f545c24e5e55fda54 100644 (file)
@@ -86,3 +86,7 @@
 /* New in 3.10 */
 #define Py_am_send 81
 #endif
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000
+/* New in 3.14 */
+#define Py_tp_vectorcall 82
+#endif
index 18392c4fce5dc1c0dd879f30219da338352bb6f7..ebc0a8a3d4efb5f6553562a3cf055d7434247651 100644 (file)
@@ -733,6 +733,14 @@ class CAPITest(unittest.TestCase):
         with self.assertRaisesRegex(TypeError, msg):
             sub = _testcapi.make_type_with_base(Base)
 
+    def test_heaptype_with_tp_vectorcall(self):
+        tp = _testcapi.HeapCTypeVectorcall
+        v0 = tp.__new__(tp)
+        v0.__init__()
+        v1 = tp()
+        self.assertEqual(v0.value, 2)
+        self.assertEqual(v1.value, 1)
+
     def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):
         for weakref_cls in (_testcapi.HeapCTypeWithWeakref,
                             _testlimitedcapi.HeapCTypeWithRelativeWeakref):
diff --git a/Misc/NEWS.d/next/C_API/2024-08-26-13-01-20.gh-issue-100554.0ku85o.rst b/Misc/NEWS.d/next/C_API/2024-08-26-13-01-20.gh-issue-100554.0ku85o.rst
new file mode 100644 (file)
index 0000000..97138de
--- /dev/null
@@ -0,0 +1,4 @@
+Added a slot ``Py_tp_vectorcall`` to set
+:c:member:`~PyTypeObject.tp_vectorcall` via the :c:func:`PyType_FromSpec`
+function family. Limited API extensions can use this feature to provide more
+efficient vector call-based implementation of ``__new__`` and ``__init__``.
index 8bf638c473c712f0c5e35b703a2ad075e88257ae..6036fc96fdd99552e35d5ecf7a7257ecf94ca1af 100644 (file)
     added = '3.14'
 [function.PyLong_AsUInt64]
     added = '3.14'
+[const.Py_tp_vectorcall]
+    added = '3.14'
\ No newline at end of file
index b45b890b88d81fdec07a22cbc7d6275620890ff0..b3fb9ec056c6c29752b9a1d1fdb4927f2a4d9824 100644 (file)
@@ -1008,6 +1008,89 @@ static PyType_Spec HeapCTypeSetattr_spec = {
     HeapCTypeSetattr_slots
 };
 
+/*
+ * The code below is for a test that uses PyType_FromSpec API to create a heap
+ * type that simultaneously exposes
+ *
+ * - A regular __new__ / __init__ constructor pair
+ * - A vector call handler in the type object
+ *
+ * A general requirement of vector call implementations is that they should
+ * behave identically (except being potentially faster). The example below
+ * deviates from this rule by initializing the instance with a different value.
+ * This is only done here only so that we can see which path was taken and is
+ * strongly discouraged in other cases.
+ */
+
+typedef struct {
+    PyObject_HEAD
+    long value;
+} HeapCTypeVectorcallObject;
+
+static PyObject *heapctype_vectorcall_vectorcall(PyObject *self,
+                                                 PyObject *const *args_in,
+                                                 size_t nargsf,
+                                                 PyObject *kwargs_in)
+{
+    if (kwargs_in || PyVectorcall_NARGS(nargsf)) {
+        return PyErr_Format(PyExc_IndexError, "HeapCTypeVectorcall() takes no arguments!");
+    }
+
+    HeapCTypeVectorcallObject *r =
+        PyObject_New(HeapCTypeVectorcallObject, (PyTypeObject *) self);
+
+    if (!r) {
+        return NULL;
+    }
+
+    r->value = 1;
+
+    return (PyObject *) r;
+}
+
+static PyObject *
+heapctype_vectorcall_new(PyTypeObject* type, PyObject* args, PyObject *kwargs)
+{
+    if (PyTuple_GET_SIZE(args) || kwargs) {
+        return PyErr_Format(PyExc_IndexError, "HeapCTypeVectorcall() takes no arguments!");
+    }
+
+    return (PyObject *) PyObject_New(HeapCTypeVectorcallObject, type);
+}
+
+static int
+heapctype_vectorcall_init(PyObject *self, PyObject *args, PyObject *kwargs) {
+    if (PyTuple_GET_SIZE(args) || kwargs) {
+        PyErr_Format(PyExc_IndexError, "HeapCTypeVectorcall() takes no arguments!");
+        return -1;
+    }
+
+    HeapCTypeVectorcallObject *o = (HeapCTypeVectorcallObject *) self;
+    o->value = 2;
+    return 0;
+}
+
+static struct PyMemberDef heapctype_vectorcall_members[] = {
+    {"value", Py_T_LONG, offsetof(HeapCTypeVectorcallObject, value), 0, NULL},
+    {NULL}
+};
+
+static PyType_Slot HeapCTypeVectorcall_slots[] = {
+    {Py_tp_new, heapctype_vectorcall_new},
+    {Py_tp_init, heapctype_vectorcall_init},
+    {Py_tp_vectorcall, heapctype_vectorcall_vectorcall},
+    {Py_tp_members, heapctype_vectorcall_members},
+    {0, 0},
+};
+
+static PyType_Spec HeapCTypeVectorcall_spec = {
+    "_testcapi.HeapCTypeVectorcall",
+    sizeof(HeapCTypeVectorcallObject),
+    0,
+    Py_TPFLAGS_DEFAULT,
+    HeapCTypeVectorcall_slots
+};
+
 PyDoc_STRVAR(HeapCCollection_doc,
 "Tuple-like heap type that uses PyObject_GetItemData for items.");
 
@@ -1180,6 +1263,9 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
     PyObject *HeapCTypeSetattr = PyType_FromSpec(&HeapCTypeSetattr_spec);
     ADD("HeapCTypeSetattr", HeapCTypeSetattr);
 
+    PyObject *HeapCTypeVectorcall = PyType_FromSpec(&HeapCTypeVectorcall_spec);
+    ADD("HeapCTypeVectorcall", HeapCTypeVectorcall);
+
     PyObject *subclass_with_finalizer_bases = PyTuple_Pack(1, HeapCTypeSubclass);
     if (subclass_with_finalizer_bases == NULL) {
         return -1;
index 896daa7d8066b765f18b1c39b238c4a0087a294a..ffb85ff56adff12a03c6b84d2921581b66022725 100644 (file)
@@ -80,3 +80,4 @@
 {offsetof(PyAsyncMethods, am_anext), offsetof(PyTypeObject, tp_as_async)},
 {-1, offsetof(PyTypeObject, tp_finalize)},
 {offsetof(PyAsyncMethods, am_send), offsetof(PyTypeObject, tp_as_async)},
+{-1, offsetof(PyTypeObject, tp_vectorcall)},