Skip to content

Commit 8cb5166

Browse files
committed
Support return values in async yield from.
1 parent c37cb05 commit 8cb5166

File tree

10 files changed

+129
-21
lines changed

10 files changed

+129
-21
lines changed

Include/cpython/pyerrors.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ typedef struct {
7373
PyObject *value;
7474
} PyStopIterationObject;
7575

76+
typedef struct {
77+
PyException_HEAD
78+
PyObject *value;
79+
} PyStopAsyncIterationObject;
80+
7681
typedef struct {
7782
PyException_HEAD
7883
PyObject *name;

Include/internal/pycore_opcode_metadata.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_ids.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/_testinternalcapi/test_cases.c.h

Lines changed: 9 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/exceptions.c

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -720,8 +720,68 @@ SimpleExtendsException(PyExc_Exception, TypeError,
720720
/*
721721
* StopAsyncIteration extends Exception
722722
*/
723-
SimpleExtendsException(PyExc_Exception, StopAsyncIteration,
724-
"Signal the end from iterator.__anext__().");
723+
724+
static PyMemberDef StopAsyncIteration_members[] = {
725+
{"value", _Py_T_OBJECT, offsetof(PyStopAsyncIterationObject, value), 0,
726+
PyDoc_STR("async generator return value")},
727+
{NULL} /* Sentinel */
728+
};
729+
730+
static inline PyStopAsyncIterationObject *
731+
PyStopAsyncIterationObject_CAST(PyObject *self)
732+
{
733+
assert(self != NULL);
734+
assert(PyObject_TypeCheck(self, (PyTypeObject *)PyExc_StopAsyncIteration));
735+
return (PyStopAsyncIterationObject *)self;
736+
}
737+
738+
static int
739+
StopAsyncIteration_init(PyObject *op, PyObject *args, PyObject *kwds)
740+
{
741+
Py_ssize_t size = PyTuple_GET_SIZE(args);
742+
743+
if (BaseException_init(op, args, kwds) < 0) {
744+
return -1;
745+
}
746+
PyStopAsyncIterationObject *self = PyStopAsyncIterationObject_CAST(op);
747+
Py_CLEAR(self->value);
748+
PyObject *value;
749+
if (size > 0) {
750+
self->value = PyTuple_GET_ITEM(args, 0);
751+
}
752+
else {
753+
self->value = Py_None;
754+
};
755+
return 0;
756+
}
757+
758+
static int
759+
StopAsyncIteration_clear(PyObject *op)
760+
{
761+
PyStopAsyncIterationObject *self = PyStopAsyncIterationObject_CAST(op);
762+
Py_CLEAR(self->value);
763+
return BaseException_clear(op);
764+
}
765+
766+
static void
767+
StopAsyncIteration_dealloc(PyObject *self)
768+
{
769+
_PyObject_GC_UNTRACK(self);
770+
(void)StopAsyncIteration_clear(self);
771+
Py_TYPE(self)->tp_free(self);
772+
}
773+
774+
static int
775+
StopAsyncIteration_traverse(PyObject *op, visitproc visit, void *arg)
776+
{
777+
PyStopAsyncIterationObject *self = PyStopAsyncIterationObject_CAST(op);
778+
Py_VISIT(self->value);
779+
return BaseException_traverse(op, visit, arg);
780+
}
781+
782+
ComplexExtendsException(PyExc_Exception, StopAsyncIteration, StopAsyncIteration,
783+
0, 0, StopAsyncIteration_members, 0, 0, 0,
784+
"Signal the end from iterator.__anext__().");
725785

726786

727787
/*

Objects/genobject.c

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,7 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult, int exc)
307307
/* If the generator just returned (as opposed to yielding), signal
308308
* that the generator is exhausted. */
309309
if (result) {
310-
assert(result == Py_None || !PyAsyncGen_CheckExact(gen));
311-
if (result == Py_None && !PyAsyncGen_CheckExact(gen) && !arg) {
310+
if (result == Py_None && !arg) {
312311
/* Return NULL if called by gen_iternext() */
313312
Py_CLEAR(result);
314313
}
@@ -380,12 +379,14 @@ PyGen_am_send(PyObject *self, PyObject *arg, PyObject **result)
380379
return gen_send_ex(gen, arg, result);
381380
}
382381

382+
int
383+
_PyAsyncGen_SetStopIterationValue(PyObject *value);
384+
383385
static PyObject *
384386
gen_set_stop_iteration(PyGenObject *gen, PyObject *result)
385387
{
386388
if (PyAsyncGen_CheckExact(gen)) {
387-
assert(result == Py_None);
388-
PyErr_SetNone(PyExc_StopAsyncIteration);
389+
_PyAsyncGen_SetStopIterationValue(result);
389390
}
390391
else if (result == Py_None) {
391392
PyErr_SetNone(PyExc_StopIteration);
@@ -776,24 +777,39 @@ gen_iternext(PyObject *self)
776777
* Returns 0 if StopIteration is set and -1 if any other exception is set.
777778
*/
778779
int
779-
_PyGen_SetStopIterationValue(PyObject *value)
780+
_PyAnyGen_SetStopIterationValue(PyObject *exc_class, PyObject *value)
780781
{
782+
assert(exc_class != NULL);
783+
assert(PyType_Check(exc_class));
784+
assert(value != NULL);
781785
assert(!PyErr_Occurred());
782786
// Construct an exception instance manually with PyObject_CallOneArg()
783787
// but use PyErr_SetRaisedException() instead of PyErr_SetObject() as
784788
// PyErr_SetObject(exc_type, value) has a fast path when 'value'
785789
// is a tuple, where the value of the StopIteration exception would be
786790
// set to 'value[0]' instead of 'value'.
787791
PyObject *exc = value == NULL
788-
? PyObject_CallNoArgs(PyExc_StopIteration)
789-
: PyObject_CallOneArg(PyExc_StopIteration, value);
792+
? PyObject_CallNoArgs(exc_class)
793+
: PyObject_CallOneArg(exc_class, value);
790794
if (exc == NULL) {
791795
return -1;
792796
}
793797
PyErr_SetRaisedException(exc /* stolen */);
794798
return 0;
795799
}
796800

801+
int
802+
_PyGen_SetStopIterationValue(PyObject *value)
803+
{
804+
return _PyAnyGen_SetStopIterationValue(PyExc_StopIteration, value);
805+
}
806+
807+
int
808+
_PyAsyncGen_SetStopIterationValue(PyObject *value)
809+
{
810+
return _PyAnyGen_SetStopIterationValue(PyExc_StopAsyncIteration, value);
811+
}
812+
797813
/*
798814
* If StopIteration exception is set, fetches its 'value'
799815
* attribute if any, otherwise sets pvalue to None.
@@ -2645,6 +2661,7 @@ async_gen_yield_from_iternext(PyObject *op)
26452661
{
26462662
assert(op != NULL);
26472663
_PyAsyncGenYieldFrom *self = _PyAsyncGenYieldFrom_CAST(op);
2664+
assert(self->agyf_iterator != NULL);
26482665
PyObject *result = PyIter_Next(self->agyf_iterator);
26492666
if (result == NULL) {
26502667
return NULL;
@@ -2708,6 +2725,17 @@ _PyAsyncGenYieldFrom_New(PyThreadState *tstate, PyObject *iterator)
27082725
if (yield_from == NULL) {
27092726
return NULL;
27102727
}
2728+
if (!PyIter_Check(iterator)) {
2729+
if (PyAsyncGen_CheckExact(iterator)) {
2730+
_PyErr_Format(tstate, PyExc_TypeError,
2731+
"%T object is not iterable. Did you mean 'async yield from'?",
2732+
iterator);
2733+
} else {
2734+
_PyErr_Format(tstate, PyExc_TypeError,
2735+
"%T object is not iterable", iterator);
2736+
}
2737+
return NULL;
2738+
}
27112739
yield_from->agyf_iterator = Py_NewRef(iterator);
27122740
_PyObject_GC_TRACK((PyObject *)yield_from);
27132741
return (PyObject *)yield_from;

Objects/object.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2577,6 +2577,7 @@ static PyTypeObject* static_types[] = {
25772577
&_PyAsyncGenASend_Type,
25782578
&_PyAsyncGenAThrow_Type,
25792579
&_PyAsyncGenWrappedValue_Type,
2580+
&_PyAsyncGenYieldFrom_Type,
25802581
&_PyBufferWrapper_Type,
25812582
&_PyContextTokenMissing_Type,
25822583
&_PyCoroWrapper_Type,

Python/bytecodes.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,14 +1583,17 @@ dummy_func(
15831583
}
15841584
}
15851585

1586-
inst(CLEANUP_ASYNC_THROW, (exc_value_st -- )) {
1586+
inst(CLEANUP_ASYNC_THROW, (iter, exc_value_st -- value)) {
15871587
PyObject *exc_value = PyStackRef_AsPyObjectSteal(exc_value_st);
15881588
assert(exc_value != NULL);
15891589
assert(PyExceptionInstance_Check(exc_value));
15901590
INPUTS_DEAD();
15911591

15921592
int matches = PyErr_GivenExceptionMatches(exc_value, PyExc_StopAsyncIteration);
1593-
if (!matches) {
1593+
if (matches) {
1594+
value = PyStackRef_FromPyObjectNew(((PyStopAsyncIterationObject *)exc_value)->value);
1595+
}
1596+
else {
15941597
_PyErr_SetRaisedException(tstate, exc_value);
15951598
monitor_reraise(tstate, frame, this_instr);
15961599
goto exception_unwind;

Python/codegen.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,9 +2275,6 @@ codegen_return(compiler *c, stmt_ty s)
22752275
if (!_PyST_IsFunctionLike(ste)) {
22762276
return _PyCompile_Error(c, loc, "'return' outside function");
22772277
}
2278-
if (s->v.Return.value != NULL && ste->ste_coroutine && ste->ste_generator) {
2279-
return _PyCompile_Error(c, loc, "'return' with value in async generator");
2280-
}
22812278

22822279
if (preserve_tos) {
22832280
VISIT(c, expr, s->v.Return.value);

Python/generated_cases.c.h

Lines changed: 9 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)