diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 9163d1a4421a8c..5a62193185e126 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -1313,13 +1313,23 @@ iterations of the loop. .. versionadded:: 3.11 -.. opcode:: SEND +.. opcode:: SEND (delta) - Sends ``None`` to the sub-generator of this generator. - Used in ``yield from`` and ``await`` statements. + Equivalent to ``TOS = TOS1.send(TOS)``. Used in ``yield from`` and ``await`` + statements. + + If the call raises :exc:`StopIteration`, pop both values, push its return + value, and increment the bytecode counter by *delta*. + + If TOS1 is ``NULL`` (set when a ``throw()`` through the current frame + returns), pop both values, push TOS (its return value) back, and increment + the bytecode counter by *delta*. .. versionadded:: 3.11 + .. versionchanged:: 3.12 + Added ``NULL`` handling for subiterators that return during ``throw()``. + .. opcode:: ASYNC_GEN_WRAP diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-03-17-11-40-20.bpo-46841.cU7e6B.rst b/Misc/NEWS.d/next/Core and Builtins/2022-03-17-11-40-20.bpo-46841.cU7e6B.rst new file mode 100644 index 00000000000000..a71ba26c107d55 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-03-17-11-40-20.bpo-46841.cU7e6B.rst @@ -0,0 +1,3 @@ +When a sub-iterator returns a value during a ``throw()`` call, perform the +resulting jump during the next :opcode:`SEND` instruction (rather than as +part of the ``throw()`` implementation). diff --git a/Objects/genobject.c b/Objects/genobject.c index 2b45e28cbf16df..fdfd9894a22706 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -491,14 +491,8 @@ _gen_throw(PyGenObject *gen, int close_on_genexit, ret = _PyFrame_StackPop((_PyInterpreterFrame *)gen->gi_iframe); assert(ret == yf); Py_DECREF(ret); - // XXX: Performing this jump ourselves is awkward and problematic. - // See https://github.com/python/cpython/pull/31968. - /* Termination repetition of SEND loop */ - assert(_PyInterpreterFrame_LASTI(frame) >= 0); - /* Backup to SEND */ - assert(_Py_OPCODE(frame->prev_instr[-1]) == SEND); - int jump = _Py_OPARG(frame->prev_instr[-1]); - frame->prev_instr += jump - 1; + // NULL tells SEND to quit sending: + _PyFrame_StackPush((_PyInterpreterFrame *)gen->gi_iframe, NULL); if (_PyGen_FetchStopIterationValue(&val) == 0) { ret = gen_send(gen, val); Py_DECREF(val); diff --git a/Python/ceval.c b/Python/ceval.c index 0e8186347cd895..7ff650f81397f5 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2591,6 +2591,12 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int assert(STACK_LEVEL() >= 2); PyObject *v = POP(); PyObject *receiver = TOP(); + if (receiver == NULL) { + // Receiver returned during a throw(). v is its return value: + SET_TOP(v); + JUMPBY(oparg); + DISPATCH(); + } PySendResult gen_status; PyObject *retval; if (tstate->c_tracefunc == NULL) {