--- original_generator.c +++ modified_generator.c @@ -1,6 +1,7 @@ /* Generator object implementation */ #define _PY_INTERPRETER +#include "pycore_exceptions.h" // For accessing exception attributes #include "Python.h" #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_ceval.h" // _PyEval_EvalFrame() @@ -349,6 +350,31 @@ /* No need to visit cr_origin, because it's just tuples/str/int, so can't participate in a reference cycle. */ Py_VISIT(gen->gi_exc_state.exc_value); return 0; +} + +/* + * Set StopAsyncIteration with specified value. Value can be any object + * or NULL. + * + * Returns 0 if StopAsyncIteration is set and -1 if any other exception is set. + */ +int +_PyAsyncGen_SetStopAsyncIterationValue(PyObject *value) +{ + PyObject *e; + + if (value == NULL) { + PyErr_SetNone(PyExc_StopAsyncIteration); + return 0; + } + + /* Construct StopAsyncIteration with the provided value */ + e = PyObject_CallFunctionObjArgs(PyExc_StopAsyncIteration, value, NULL); + if (e == NULL) { + return -1; + } + PyErr_SetObject(PyExc_StopAsyncIteration, e); + Py_DECREF(e); + return 0; +} + +/* + * If StopAsyncIteration exception is set, fetches its 'value' + * attribute if any, otherwise sets pvalue to None. + * + * Returns 0 if StopAsyncIteration is set and value is fetched, + * -1 otherwise. + */ +int +_PyAsyncGen_FetchStopAsyncIterationValue(PyObject **pvalue) +{ + if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) { + PyObject *exc = PyErr_GetRaisedException(); + PyObject *value = PyObject_GetAttr(exc, &_Py_ID(value)); + Py_DECREF(exc); + if (value == NULL) { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + *pvalue = Py_NewRef(Py_None); + return 0; + } + return -1; + } + *pvalue = value; + return 0; + } + return -1; } PyCodeObject * PyGen_GetCode(PyGenObject *gen) { @@ -1133,6 +1169,33 @@ return result ? PYGEN_RETURN : PYGEN_ERROR; } +/* + * If StopAsyncIteration exception is set, fetches its 'value' + * attribute if any, otherwise sets pvalue to None. + * + * Returns 0 if StopAsyncIteration is set and value is fetched, + * -1 otherwise. + */ +int +_PyAsyncGen_FetchStopAsyncIterationValue(PyObject **pvalue) +{ + if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) { + PyObject *exc = PyErr_GetRaisedException(); + PyObject *value = PyObject_GetAttr(exc, &_Py_ID(value)); + Py_DECREF(exc); + if (value == NULL) { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + *pvalue = Py_NewRef(Py_None); + return 0; + } + return -1; + } + *pvalue = value; + return 0; + } + return -1; +} + static PySendResult gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult, int exc, int closing) { PyThreadState *tstate = _PyThreadState_GET(); _PyInterpreterFrame *frame = &gen->gi_iframe; *presult = NULL; if (gen->gi_frame_state == FRAME_CREATED && arg && arg != Py_None) { const char *msg = "can't send non-None value to a " "just-started generator"; if (PyCoro_CheckExact(gen)) { msg = NON_INIT_CORO_MSG; } else if (PyAsyncGen_CheckExact(gen)) { msg = "can't send non-None value to a " "just-started async generator"; } PyErr_SetString(PyExc_TypeError, msg); return PYGEN_ERROR; } if (gen->gi_frame_state == FRAME_EXECUTING) { const char *msg = "generator already executing"; if (PyCoro_CheckExact(gen)) { msg = "coroutine already executing"; } else if (PyAsyncGen_CheckExact(gen)) { msg = "async generator already executing"; } PyErr_SetString(PyExc_ValueError, msg); return PYGEN_ERROR; } if (FRAME_STATE_FINISHED(gen->gi_frame_state)) { if (PyCoro_CheckExact(gen) && !closing) { /* `gen` is an exhausted coroutine: raise an error, except when called from gen_close(), which should always be a silent method. */ PyErr_SetString( PyExc_RuntimeError, "cannot reuse already awaited coroutine"); } else if (arg && !exc) { /* `gen` is an exhausted generator: only return value if called from send(). */ *presult = Py_NewRef(Py_None); return PYGEN_RETURN; } return PYGEN_ERROR; } assert((gen->gi_frame_state == FRAME_CREATED) || FRAME_STATE_SUSPENDED(gen->gi_frame_state)); /* Push arg onto the frame's value stack */ PyObject *arg_obj = arg ? arg : Py_None; _PyFrame_StackPush(frame, PyStackRef_FromPyObjectNew(arg_obj)); _PyErr_StackItem *prev_exc_info = tstate->exc_info; gen->gi_exc_state.previous_item = prev_exc_info; tstate->exc_info = &gen->gi_exc_state; if (exc) { assert(_PyErr_Occurred(tstate)); _PyErr_ChainStackItem(); } gen->gi_frame_state = FRAME_EXECUTING; EVAL_CALL_STAT_INC(EVAL_CALL_GENERATOR); PyObject *result = _PyEval_EvalFrame(tstate, frame, exc); assert(tstate->exc_info == prev_exc_info); assert(gen->gi_exc_state.previous_item == NULL); assert(gen->gi_frame_state != FRAME_EXECUTING); assert(frame->previous == NULL); /* If the generator just returned (as opposed to yielding), signal * that the generator is exhausted. */ if (result) { if (FRAME_STATE_SUSPENDED(gen->gi_frame_state)) { *presult = result; return PYGEN_NEXT; } if (PyAsyncGen_CheckExact(gen)) { /* For async generators, set StopAsyncIteration with the return value */ if (_PyAsyncGen_SetStopAsyncIterationValue(result) < 0) { /* Failed to set StopAsyncIteration */ return PYGEN_ERROR; } Py_DECREF(result); *presult = NULL; return PYGEN_RETURN; } assert(result == Py_None || !PyAsyncGen_CheckExact(gen)); if (result == Py_None && !PyAsyncGen_CheckExact(gen) && !arg) { /* Return NULL if called by gen_iternext() */ Py_CLEAR(result); } } else { assert(!PyErr_ExceptionMatches(PyExc_StopIteration)); assert(!PyAsyncGen_CheckExact(gen) || !PyErr_ExceptionMatches(PyExc_StopAsyncIteration)); } assert(gen->gi_exc_state.exc_value == NULL); assert(gen->gi_frame_state == FRAME_CLEARED); *presult = result; return result ? PYGEN_RETURN : PYGEN_ERROR; } @@ -1354,6 +1491,31 @@ return result ? PYGEN_RETURN : PYGEN_ERROR; } /* Coroutine Object */ +/* + * If StopAsyncIteration exception is set, fetches its 'value' + * attribute if any, otherwise sets pvalue to None. + * + * Returns 0 if StopAsyncIteration is set and value is fetched, + * -1 otherwise. + */ +int +_PyAsyncGen_FetchStopAsyncIterationValue(PyObject **pvalue) +{ + if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) { + PyObject *exc = PyErr_GetRaisedException(); + PyObject *value = PyObject_GetAttr(exc, &_Py_ID(value)); + Py_DECREF(exc); + if (value == NULL) { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + *pvalue = Py_NewRef(Py_None); + return 0; + } + return -1; + } + *pvalue = value; + return 0; + } + return -1; +} + static PyObject * gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) { PyObject *result; if (gen_send_ex2(gen, arg, &result, exc, closing) == PYGEN_RETURN) { if (PyAsyncGen_CheckExact(gen)) { /* For async generators, retrieve the return value from StopAsyncIteration */ - PyObject *exc = PyErr_GetRaisedException(); - if (_PyGen_FetchStopIterationValue(&result) == 0) { - /* Attach the value to StopAsyncIteration */ - PyErr_SetObject(PyExc_StopAsyncIteration, result); - Py_DECREF(result); + /* Fetch the value from StopAsyncIteration */ + if (_PyAsyncGen_FetchStopAsyncIterationValue(&result) == 0) { + /* The value is already set in the exception */ + /* Return the value to the caller */ + return result; + } + /* If fetching the value failed, propagate the exception */ + return NULL; } else if (result == Py_None) { PyErr_SetNone(PyExc_StopIteration); Py_CLEAR(result); } else { _PyGen_SetStopIterationValue(result); } Py_CLEAR(result); } return result; } @@ -1663,6 +1767,23 @@ return NULL; } +/* + * Finalize function for async generators to handle return values. + */ +void +_PyAsyncGen_Finalize(PyObject *self) +{ + PyAsyncGenObject *ag = (PyAsyncGenObject *)self; + + if (FRAME_STATE_FINISHED(ag->ag_frame_state)) { + /* Async generator isn't paused, so no need to close */ + return; + } + + /* Save the current exception, if any. */ + PyObject *exc = PyErr_GetRaisedException(); + + /* Close the generator */ + PyObject *res = gen_close((PyObject*)ag, NULL); + if (res == NULL) { + if (PyErr_Occurred()) { + PyErr_WriteUnraisable(self); + } + } + else { + Py_DECREF(res); + } + + /* Restore the saved exception. */ + PyErr_SetRaisedException(exc); +} + void _PyGen_Finalize(PyObject *self) { @@ -1748,6 +1890,7 @@ } } + if (PyAsyncGen_CheckExact(self)) { /* Save the current exception, if any. */ PyObject *exc = PyErr_GetRaisedException(); @@ -1758,6 +1901,16 @@ PyErr_SetRaisedException(exc); } else { + if (PyAsyncGen_CheckExact(self)) { + /* Call the specific async generator finalizer */ + _PyAsyncGen_Finalize(self); + } + + /* Existing finalization logic for regular generators and coroutines */ PyObject *res = gen_close((PyObject*)gen, NULL); if (res == NULL) { if (PyErr_Occurred()) { PyErr_WriteUnraisable(self); } else { Py_DECREF(res); } } } } @@ -2333,6 +2456,32 @@ /* Normal finalization steps */ Py_CLEAR(ags->ags_gen); Py_CLEAR(ags->ags_sendval); + + /* Finalize return value if stored (optional) */ + if (ags->ags_gen->ag_return_value) { + Py_CLEAR(ags->ags_gen->ag_return_value); + } + _PyObject_GC_UNTRACK((PyObject*)ags); PyObject_GC_Del(ags); } +/* Async Generator AThrow awaitable modifications */ +static void +async_gen_athrow_finalize(PyObject *self) +{ + PyAsyncGenAThrow *agt = _PyAsyncGenAThrow_CAST(self); + if (agt->agt_state == AWAITABLE_STATE_INIT) { + PyObject *method = agt->agt_args ? &_Py_ID(athrow) : &_Py_ID(aclose); + _PyErr_WarnUnawaitedAgenMethod(agt->agt_gen, method); + } +} + +/* + * Modify async_gen_asend_send to handle return values. + */ +static PyObject * +async_gen_asend_send(PyObject *self, PyObject *arg) +{ + PyAsyncGenASend *o = _PyAsyncGenASend_CAST(self); + if (o->ags_state == AWAITABLE_STATE_CLOSED) { + PyErr_SetString( + PyExc_RuntimeError, + "cannot reuse already awaited __anext__()/asend()"); + return NULL; + } + + if (o->ags_state == AWAITABLE_STATE_INIT) { + if (o->ags_gen->ag_running_async) { + o->ags_state = AWAITABLE_STATE_CLOSED; + PyErr_SetString( + PyExc_RuntimeError, + "anext(): asynchronous generator is already running"); + return NULL; + } + + if (arg == NULL || arg == Py_None) { + arg = o->ags_sendval; + } + o->ags_state = AWAITABLE_STATE_ITER; + } + + o->ags_gen->ag_running_async = 1; + PyObject *result = gen_send((PyObject*)o->ags_gen, arg); + result = async_gen_unwrap_value(o->ags_gen, result); + + if (result == NULL) { + o->ags_state = AWAITABLE_STATE_CLOSED; + } + + return result; +} + /* * Set StopIteration with specified value. Value can be arbitrary object * or NULL. @@ -2450,6 +2623,13 @@ return NULL; } +/* Fetch the value from StopAsyncIteration */ +static int +async_gen_unwrap_stopasynciteration(PyObject *result) +{ + if (_PyAsyncGen_FetchStopAsyncIterationValue(&result) == 0) { + /* The value is already set in the exception */ + return 0; + } + return -1; +} + static PyObject * async_gen_aclose(PyAsyncGenObject *o, PyObject *arg) { @@ -2548,6 +2738,7 @@ o->agt_gen->ag_running_async = 0; o->agt_state = AWAITABLE_STATE_CLOSED; } +/* Async generator throw modifications */ static PyObject * async_gen_athrow(PyAsyncGenObject *o, PyObject *args) {