From 1e996c3a3b51e9c6f1f4cea8a6dbcf3bcb865060 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 10 Nov 2020 12:09:55 -0800 Subject: [PATCH] bpo-42085: Introduce dedicated entry in PyAsyncMethods for sending values (#22780) --- Doc/c-api/typeobj.rst | 16 ++++++ Include/abstract.h | 5 -- Include/cpython/object.h | 3 + Include/object.h | 14 +++++ Include/typeslots.h | 4 ++ Lib/test/test_sys.py | 2 +- .../2020-10-19-15-58-16.bpo-42085.NhEf3W.rst | 1 + Modules/_asynciomodule.c | 56 ++++++++++++++---- Modules/_testcapimodule.c | 3 +- Objects/abstract.c | 26 +++++++++ Objects/genobject.c | 57 +++++++++---------- Objects/typeobject.c | 7 +++ Objects/typeslots.inc | 1 + 13 files changed, 146 insertions(+), 49 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2020-10-19-15-58-16.bpo-42085.NhEf3W.rst diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index 32bbc7ba0a1..6a67bfe9010 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -199,6 +199,8 @@ sub-slots +---------------------------------------------------------+-----------------------------------+--------------+ | :c:member:`~PyAsyncMethods.am_anext` | :c:type:`unaryfunc` | __anext__ | +---------------------------------------------------------+-----------------------------------+--------------+ + | :c:member:`~PyAsyncMethods.am_send` | :c:type:`sendfunc` | | + +---------------------------------------------------------+-----------------------------------+--------------+ | | +---------------------------------------------------------+-----------------------------------+--------------+ | :c:member:`~PyNumberMethods.nb_add` | :c:type:`binaryfunc` | __add__ | @@ -2304,6 +2306,7 @@ Async Object Structures unaryfunc am_await; unaryfunc am_aiter; unaryfunc am_anext; + sendfunc am_send; } PyAsyncMethods; .. c:member:: unaryfunc PyAsyncMethods.am_await @@ -2337,6 +2340,15 @@ Async Object Structures Must return an :term:`awaitable` object. See :meth:`__anext__` for details. This slot may be set to ``NULL``. +.. c:member:: sendfunc PyAsyncMethods.am_send + + The signature of this function is:: + + PySendResult am_send(PyObject *self, PyObject *arg, PyObject **result); + + See :c:func:`PyIter_Send` for details. + This slot may be set to ``NULL``. + .. _slot-typedefs: @@ -2432,6 +2444,10 @@ Slot Type typedefs .. c:type:: PyObject *(*binaryfunc)(PyObject *, PyObject *) +.. c:type:: PySendResult (*sendfunc)(PyObject *, PyObject *, PyObject **) + + See :c:member:`~PyAsyncMethods.am_send`. + .. c:type:: PyObject *(*ternaryfunc)(PyObject *, PyObject *, PyObject *) .. c:type:: PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t) diff --git a/Include/abstract.h b/Include/abstract.h index 28e576b9293..0bd1ca93684 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -339,11 +339,6 @@ PyAPI_FUNC(int) PyIter_Check(PyObject *); PyAPI_FUNC(PyObject *) PyIter_Next(PyObject *); #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000 -typedef enum { - PYGEN_RETURN = 0, - PYGEN_ERROR = -1, - PYGEN_NEXT = 1, -} PySendResult; /* Takes generator, coroutine or iterator object and sends the value into it. Returns: diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 0db53c312f0..ec6a3647677 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -167,10 +167,13 @@ typedef struct { objobjargproc mp_ass_subscript; } PyMappingMethods; +typedef PySendResult (*sendfunc)(PyObject *iter, PyObject *value, PyObject **result); + typedef struct { unaryfunc am_await; unaryfunc am_aiter; unaryfunc am_anext; + sendfunc am_send; } PyAsyncMethods; typedef struct { diff --git a/Include/object.h b/Include/object.h index eab3228f3ab..dd1b2176867 100644 --- a/Include/object.h +++ b/Include/object.h @@ -356,6 +356,11 @@ given type object has a specified feature. /* Type is abstract and cannot be instantiated */ #define Py_TPFLAGS_IS_ABSTRACT (1UL << 20) +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000 +/* Type has am_send entry in tp_as_async slot */ +#define Py_TPFLAGS_HAVE_AM_SEND (1UL << 21) +#endif + /* These flags are used to determine if a type is a subclass. */ #define Py_TPFLAGS_LONG_SUBCLASS (1UL << 24) #define Py_TPFLAGS_LIST_SUBCLASS (1UL << 25) @@ -582,6 +587,15 @@ PyAPI_DATA(PyObject) _Py_NotImplementedStruct; /* Don't use this directly */ #define Py_GT 4 #define Py_GE 5 +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000 +/* Result of calling PyIter_Send */ +typedef enum { + PYGEN_RETURN = 0, + PYGEN_ERROR = -1, + PYGEN_NEXT = 1, +} PySendResult; +#endif + /* * Macro for implementing rich comparisons * diff --git a/Include/typeslots.h b/Include/typeslots.h index 64f6fff5144..5800d0158bc 100644 --- a/Include/typeslots.h +++ b/Include/typeslots.h @@ -88,3 +88,7 @@ /* New in 3.5 */ #define Py_tp_finalize 80 #endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000 +/* New in 3.10 */ +#define Py_am_send 81 +#endif diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 332ed8f550c..173ef9ebb4c 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1407,7 +1407,7 @@ class SizeofTest(unittest.TestCase): check(int, s) # class s = vsize(fmt + # PyTypeObject - '3P' # PyAsyncMethods + '4P' # PyAsyncMethods '36P' # PyNumberMethods '3P' # PyMappingMethods '10P' # PySequenceMethods diff --git a/Misc/NEWS.d/next/C API/2020-10-19-15-58-16.bpo-42085.NhEf3W.rst b/Misc/NEWS.d/next/C API/2020-10-19-15-58-16.bpo-42085.NhEf3W.rst new file mode 100644 index 00000000000..53338fb4f44 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2020-10-19-15-58-16.bpo-42085.NhEf3W.rst @@ -0,0 +1 @@ +Add dedicated entry to PyAsyncMethods for sending values diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 90d288f7393..d1d0f6bc75e 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -1479,7 +1479,8 @@ future_cls_getitem(PyObject *cls, PyObject *type) static PyAsyncMethods FutureType_as_async = { (unaryfunc)future_new_iter, /* am_await */ 0, /* am_aiter */ - 0 /* am_anext */ + 0, /* am_anext */ + 0, /* am_send */ }; static PyMethodDef FutureType_methods[] = { @@ -1597,37 +1598,60 @@ FutureIter_dealloc(futureiterobject *it) } } -static PyObject * -FutureIter_iternext(futureiterobject *it) +static PySendResult +FutureIter_am_send(futureiterobject *it, + PyObject *Py_UNUSED(arg), + PyObject **result) { + /* arg is unused, see the comment on FutureIter_send for clarification */ + PyObject *res; FutureObj *fut = it->future; + *result = NULL; if (fut == NULL) { - return NULL; + return PYGEN_ERROR; } if (fut->fut_state == STATE_PENDING) { if (!fut->fut_blocking) { fut->fut_blocking = 1; Py_INCREF(fut); - return (PyObject *)fut; + *result = (PyObject *)fut; + return PYGEN_NEXT; } PyErr_SetString(PyExc_RuntimeError, "await wasn't used with future"); - return NULL; + return PYGEN_ERROR; } it->future = NULL; res = _asyncio_Future_result_impl(fut); if (res != NULL) { - /* The result of the Future is not an exception. */ - (void)_PyGen_SetStopIterationValue(res); - Py_DECREF(res); + *result = res; + return PYGEN_RETURN; } Py_DECREF(fut); - return NULL; + return PYGEN_ERROR; +} + +static PyObject * +FutureIter_iternext(futureiterobject *it) +{ + PyObject *result; + switch (FutureIter_am_send(it, Py_None, &result)) { + case PYGEN_RETURN: + (void)_PyGen_SetStopIterationValue(result); + Py_DECREF(result); + return NULL; + case PYGEN_NEXT: + return result; + case PYGEN_ERROR: + return NULL; + default: + Py_UNREACHABLE(); + } } static PyObject * @@ -1716,14 +1740,24 @@ static PyMethodDef FutureIter_methods[] = { {NULL, NULL} /* Sentinel */ }; +static PyAsyncMethods FutureIterType_as_async = { + 0, /* am_await */ + 0, /* am_aiter */ + 0, /* am_anext */ + (sendfunc)FutureIter_am_send, /* am_send */ +}; + + static PyTypeObject FutureIterType = { PyVarObject_HEAD_INIT(NULL, 0) "_asyncio.FutureIter", .tp_basicsize = sizeof(futureiterobject), .tp_itemsize = 0, .tp_dealloc = (destructor)FutureIter_dealloc, + .tp_as_async = &FutureIterType_as_async, .tp_getattro = PyObject_GenericGetAttr, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_HAVE_AM_SEND, .tp_traverse = (traverseproc)FutureIter_traverse, .tp_iter = PyObject_SelfIter, .tp_iternext = (iternextfunc)FutureIter_iternext, diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 22d20d220d4..4382b642dca 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -6142,7 +6142,8 @@ awaitObject_await(awaitObject *ao) static PyAsyncMethods awaitType_as_async = { (unaryfunc)awaitObject_await, /* am_await */ 0, /* am_aiter */ - 0 /* am_anext */ + 0, /* am_anext */ + 0, /* am_send */ }; diff --git a/Objects/abstract.c b/Objects/abstract.c index 562549876be..44ed5b3932b 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -2669,6 +2669,32 @@ PyIter_Next(PyObject *iter) return result; } +PySendResult +PyIter_Send(PyObject *iter, PyObject *arg, PyObject **result) +{ + _Py_IDENTIFIER(send); + assert(arg != NULL); + assert(result != NULL); + if (PyType_HasFeature(Py_TYPE(iter), Py_TPFLAGS_HAVE_AM_SEND)) { + assert (Py_TYPE(iter)->tp_as_async != NULL); + assert (Py_TYPE(iter)->tp_as_async->am_send != NULL); + return Py_TYPE(iter)->tp_as_async->am_send(iter, arg, result); + } + if (arg == Py_None && PyIter_Check(iter)) { + *result = Py_TYPE(iter)->tp_iternext(iter); + } + else { + *result = _PyObject_CallMethodIdOneArg(iter, &PyId_send, arg); + } + if (*result != NULL) { + return PYGEN_NEXT; + } + if (_PyGen_FetchStopIterationValue(result) == 0) { + return PYGEN_RETURN; + } + return PYGEN_ERROR; +} + /* * Flatten a sequence of bytes() objects into a C array of * NULL terminated string pointers with a NULL char* terminating the array. diff --git a/Objects/genobject.c b/Objects/genobject.c index c1b26e9da33..bde92b462da 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -268,30 +268,10 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult, return result ? PYGEN_RETURN : PYGEN_ERROR; } -PySendResult -PyIter_Send(PyObject *iter, PyObject *arg, PyObject **result) +static PySendResult +PyGen_am_send(PyGenObject *gen, PyObject *arg, PyObject **result) { - _Py_IDENTIFIER(send); - assert(arg != NULL); - assert(result != NULL); - - if (PyGen_CheckExact(iter) || PyCoro_CheckExact(iter)) { - return gen_send_ex2((PyGenObject *)iter, arg, result, 0, 0); - } - - if (arg == Py_None && PyIter_Check(iter)) { - *result = Py_TYPE(iter)->tp_iternext(iter); - } - else { - *result = _PyObject_CallMethodIdOneArg(iter, &PyId_send, arg); - } - if (*result != NULL) { - return PYGEN_NEXT; - } - if (_PyGen_FetchStopIterationValue(result) == 0) { - return PYGEN_RETURN; - } - return PYGEN_ERROR; + return gen_send_ex2(gen, arg, result, 0, 0); } static PyObject * @@ -788,6 +768,14 @@ static PyMethodDef gen_methods[] = { {NULL, NULL} /* Sentinel */ }; +static PyAsyncMethods gen_as_async = { + 0, /* am_await */ + 0, /* am_aiter */ + 0, /* am_anext */ + (sendfunc)PyGen_am_send, /* am_send */ +}; + + PyTypeObject PyGen_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "generator", /* tp_name */ @@ -798,7 +786,7 @@ PyTypeObject PyGen_Type = { 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ - 0, /* tp_as_async */ + &gen_as_async, /* tp_as_async */ (reprfunc)gen_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ @@ -809,7 +797,8 @@ PyTypeObject PyGen_Type = { PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_HAVE_AM_SEND, /* tp_flags */ 0, /* tp_doc */ (traverseproc)gen_traverse, /* tp_traverse */ 0, /* tp_clear */ @@ -1031,7 +1020,8 @@ static PyMethodDef coro_methods[] = { static PyAsyncMethods coro_as_async = { (unaryfunc)coro_await, /* am_await */ 0, /* am_aiter */ - 0 /* am_anext */ + 0, /* am_anext */ + (sendfunc)PyGen_am_send, /* am_send */ }; PyTypeObject PyCoro_Type = { @@ -1055,7 +1045,8 @@ PyTypeObject PyCoro_Type = { PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_HAVE_AM_SEND, /* tp_flags */ 0, /* tp_doc */ (traverseproc)gen_traverse, /* tp_traverse */ 0, /* tp_clear */ @@ -1413,7 +1404,8 @@ static PyMethodDef async_gen_methods[] = { static PyAsyncMethods async_gen_as_async = { 0, /* am_await */ PyObject_SelfIter, /* am_aiter */ - (unaryfunc)async_gen_anext /* am_anext */ + (unaryfunc)async_gen_anext, /* am_anext */ + (sendfunc)PyGen_am_send, /* am_send */ }; @@ -1438,7 +1430,8 @@ PyTypeObject PyAsyncGen_Type = { PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_HAVE_AM_SEND, /* tp_flags */ 0, /* tp_doc */ (traverseproc)async_gen_traverse, /* tp_traverse */ 0, /* tp_clear */ @@ -1676,7 +1669,8 @@ static PyMethodDef async_gen_asend_methods[] = { static PyAsyncMethods async_gen_asend_as_async = { PyObject_SelfIter, /* am_await */ 0, /* am_aiter */ - 0 /* am_anext */ + 0, /* am_anext */ + 0, /* am_send */ }; @@ -2084,7 +2078,8 @@ static PyMethodDef async_gen_athrow_methods[] = { static PyAsyncMethods async_gen_athrow_as_async = { PyObject_SelfIter, /* am_await */ 0, /* am_aiter */ - 0 /* am_anext */ + 0, /* am_anext */ + 0, /* am_send */ }; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 55bf9b3f389..b4188b8bcaf 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5427,6 +5427,13 @@ PyType_Ready(PyTypeObject *type) _PyObject_ASSERT((PyObject *)type, type->tp_vectorcall_offset > 0); _PyObject_ASSERT((PyObject *)type, type->tp_call != NULL); } + /* Consistency check for Py_TPFLAGS_HAVE_AM_SEND - flag requires + * type->tp_as_async->am_send to be present. + */ + if (type->tp_flags & Py_TPFLAGS_HAVE_AM_SEND) { + _PyObject_ASSERT((PyObject *)type, type->tp_as_async != NULL); + _PyObject_ASSERT((PyObject *)type, type->tp_as_async->am_send != NULL); + } type->tp_flags |= Py_TPFLAGS_READYING; diff --git a/Objects/typeslots.inc b/Objects/typeslots.inc index ffc9bb2e1c7..cc4ef1170fd 100644 --- a/Objects/typeslots.inc +++ b/Objects/typeslots.inc @@ -79,3 +79,4 @@ offsetof(PyHeapTypeObject, as_async.am_await), offsetof(PyHeapTypeObject, as_async.am_aiter), offsetof(PyHeapTypeObject, as_async.am_anext), offsetof(PyHeapTypeObject, ht_type.tp_finalize), +offsetof(PyHeapTypeObject, as_async.am_send),