diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index af313f42f9bff6b..b196b070184b7dc 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -891,11 +891,25 @@ For example:: >>> {**d for d in configuration_sets} {'color': 'yellow', 'count': 5} +In a generator expression, a starred expression is delegated to using +:keyword:`yield from ` semantics: values sent into the generator with +:meth:`~generator.send` and exceptions thrown in with :meth:`~generator.throw` +are forwarded to the sub-iterator currently being unpacked. The same applies +to asynchronous generator expressions, where :meth:`~agen.asend` is forwarded +to the (synchronous) sub-iterator's :meth:`~generator.send`, and +:meth:`~agen.athrow` and :meth:`~agen.aclose` to its :meth:`~generator.throw` +(:meth:`~agen.aclose` throwing :exc:`GeneratorExit`). + .. versionadded:: 3.15 Unpacking in comprehensions using the ``*`` and ``**`` operators was introduced in :pep:`798`. +.. versionchanged:: 3.16 + + Unpacking a starred expression in a generator expression delegates to the + sub-iterator using :keyword:`yield from ` semantics. + .. index:: single: async for; in comprehensions diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index e215d4ddfdf41b7..72986e7caee4ae1 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -75,6 +75,18 @@ New features Other language changes ====================== +* Unpacking a sub-iterable with ``*`` in a generator expression + (:pep:`798`) now delegates to the sub-iterable using + :keyword:`yield from ` semantics. Values sent with + :meth:`~generator.send` and exceptions thrown with + :meth:`~generator.throw` are forwarded to the sub-iterator, and the same + applies to asynchronous generator expressions: :meth:`~agen.asend` is + forwarded to the (synchronous) sub-iterator's :meth:`~generator.send`, + while :meth:`~agen.athrow` and :meth:`~agen.aclose` are forwarded to its + :meth:`~generator.throw` (:meth:`~agen.aclose` throwing + :exc:`GeneratorExit`, as :meth:`generator.close` does). + (Contributed in :gh:`143055`.) + New modules diff --git a/Include/internal/pycore_genobject.h b/Include/internal/pycore_genobject.h index c86ae242feac1ed..7e3bde51fffa3dc 100644 --- a/Include/internal/pycore_genobject.h +++ b/Include/internal/pycore_genobject.h @@ -32,6 +32,7 @@ PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **); PyAPI_FUNC(PyObject *)_PyCoro_GetAwaitableIter(PyObject *o); PyAPI_FUNC(PyObject *)_PyAsyncGenValueWrapperNew(PyThreadState *state, PyObject *); +PyAPI_FUNC(PyObject *)_PyAsyncGenUnpack_New(PyThreadState *state, PyObject *); // Exported for external JIT support PyAPI_FUNC(PyObject *) _PyCoro_ComputeOrigin(int origin_depth, _PyInterpreterFrame *current_frame); @@ -39,6 +40,7 @@ PyAPI_FUNC(PyObject *) _PyCoro_ComputeOrigin(int origin_depth, _PyInterpreterFra extern PyTypeObject _PyCoroWrapper_Type; extern PyTypeObject _PyAsyncGenWrappedValue_Type; extern PyTypeObject _PyAsyncGenAThrow_Type; +extern PyTypeObject _PyAsyncGenUnpack_Type; PyAPI_FUNC(PySendResult) _PyAsyncGenASend_Send(PyObject *iter, PyObject *arg, PyObject **result); diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 5500c70a3b0aad9..214845b3ec5fe3a 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -532,7 +532,7 @@ struct _py_func_state { If you add a new static type to the standard library, you may have to update one of these numbers. */ -#define _Py_NUM_MANAGED_PREINITIALIZED_TYPES 120 +#define _Py_NUM_MANAGED_PREINITIALIZED_TYPES 121 #define _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES \ (_Py_NUM_MANAGED_PREINITIALIZED_TYPES + 83) #define _Py_MAX_MANAGED_STATIC_EXT_TYPES 10 diff --git a/Include/internal/pycore_intrinsics.h b/Include/internal/pycore_intrinsics.h index 59a7b16073f886c..b021c8140b62287 100644 --- a/Include/internal/pycore_intrinsics.h +++ b/Include/internal/pycore_intrinsics.h @@ -19,8 +19,9 @@ #define INTRINSIC_SUBSCRIPT_GENERIC 10 #define INTRINSIC_TYPEALIAS 11 #define INTRINSIC_BUILD_FROZENSET 12 +#define INTRINSIC_ASYNC_GEN_UNPACK 13 -#define MAX_INTRINSIC_1 12 +#define MAX_INTRINSIC_1 13 /* Binary Functions: */ diff --git a/Include/internal/pycore_magic_number.h b/Include/internal/pycore_magic_number.h index 6a8fa124ba38a7c..3ecff54013bbc0b 100644 --- a/Include/internal/pycore_magic_number.h +++ b/Include/internal/pycore_magic_number.h @@ -299,6 +299,7 @@ Known values: Python 3.15b1 3666 (Add SEND_VIRTUAL and SEND_ASYNC_GEN specializations) Python 3.16a0 3700 (Initial version) Python 3.16a0 3701 (Add CONSTANT_EMPTY_TUPLE to LOAD_COMMON_CONSTANT) + Python 3.16a0 3702 (Delegate to subiterators when unpacking in generator expressions) Python 3.17 will start with 3750 @@ -312,7 +313,7 @@ PC/launcher.c must also be updated. */ -#define PYC_MAGIC_NUMBER 3701 +#define PYC_MAGIC_NUMBER 3702 /* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes (little-endian) and then appending b'\r\n'. */ #define PYC_MAGIC_NUMBER_TOKEN \ diff --git a/Lib/test/test_unpack_ex.py b/Lib/test/test_unpack_ex.py index 33c96b84964b591..581390e347a3a0c 100644 --- a/Lib/test/test_unpack_ex.py +++ b/Lib/test/test_unpack_ex.py @@ -748,6 +748,183 @@ def test_errors_in_getitem(): __test__ = {'doctests' : doctests} + +class TestGeneratorExpressionDelegation(unittest.TestCase): + # Unpacking a sub-iterable with ``*`` in a generator expression delegates + # to the sub-iterable using ``yield from`` semantics, so that values sent + # to (and exceptions thrown into) the generator are forwarded. + + def test_flatten(self): + lists = [[1, 2], [3], [], [4, 5]] + self.assertEqual(list((*sub for sub in lists)), [1, 2, 3, 4, 5]) + + def test_yields_from_multiple_iterables(self): + gen = (*(0, 1) for i in range(3)) + self.assertEqual(list(gen), [0, 1, 0, 1, 0, 1]) + + def test_send_is_forwarded(self): + received = [] + + def sub(): + while True: + received.append((yield 'value')) + + gen = (*sub() for _ in range(1)) + self.assertEqual(next(gen), 'value') + self.assertEqual(gen.send(42), 'value') + self.assertEqual(gen.send(7), 'value') + self.assertEqual(received, [42, 7]) + + def test_throw_is_forwarded(self): + caught = [] + + def sub(): + try: + yield 1 + yield 2 + except ValueError as exc: + caught.append(str(exc)) + yield 'after-catch' + + gen = (*sub() for _ in range(1)) + self.assertEqual(next(gen), 1) + self.assertEqual(gen.throw(ValueError('boom')), 'after-catch') + self.assertEqual(caught, ['boom']) + + def test_close_is_forwarded(self): + closed = [] + + def sub(): + try: + yield 1 + yield 2 + except GeneratorExit: + closed.append(True) + raise + + gen = (*sub() for _ in range(1)) + self.assertEqual(next(gen), 1) + gen.close() + self.assertEqual(closed, [True]) + + def test_subiterator_return_value_is_discarded(self): + def sub(n): + yield n + return 'ignored' + + self.assertEqual(list((*sub(i) for i in range(3))), [0, 1, 2]) + + +class TestAsyncGeneratorExpressionDelegation(unittest.TestCase): + # Unpacking a (synchronous) sub-iterable with ``*`` in an asynchronous + # generator expression also delegates with ``yield from`` semantics: + # asend() forwards to the sub-iterator's send(), athrow() to throw() and + # aclose() to close(). + # + # These are low-level language tests, so (like test_asyncgen) the async + # generators are driven by hand rather than through an event loop. + + @staticmethod + async def _aiter(seq): + for item in seq: + yield item + + @staticmethod + def _run(coro): + # Drive a coroutine that is not expected to await anything real, and + # return its result. Any StopAsyncIteration (e.g. an exhausted + # asend()) is allowed to propagate. + try: + coro.send(None) + except StopIteration as exc: + return exc.value + coro.close() + raise AssertionError("coroutine awaited unexpectedly") + + def _collect(self, agen): + result = [] + while True: + try: + result.append(self._run(agen.asend(None))) + except StopAsyncIteration: + break + return result + + def test_flatten(self): + lists = [[1, 2], [3], [], [4, 5]] + agen = (*sub async for sub in self._aiter(lists)) + self.assertEqual(self._collect(agen), [1, 2, 3, 4, 5]) + + def test_asend_forwards_to_send(self): + received = [] + + def sub(): + while True: + received.append((yield 'value')) + + agen = (*sub() async for _ in self._aiter([0])) + self.assertEqual(self._run(agen.asend(None)), 'value') + self.assertEqual(self._run(agen.asend(99)), 'value') + self.assertEqual(self._run(agen.asend(123)), 'value') + self.assertEqual(received, [99, 123]) + + def test_athrow_forwards_to_throw(self): + caught = [] + + def sub(): + try: + yield 1 + yield 2 + except ValueError as exc: + caught.append(str(exc)) + yield 'after-catch' + + agen = (*sub() async for _ in self._aiter([0])) + self.assertEqual(self._run(agen.asend(None)), 1) + self.assertEqual(self._run(agen.athrow(ValueError('boom'))), + 'after-catch') + self.assertEqual(caught, ['boom']) + + def test_aclose_forwards_to_close(self): + closed = [] + + def sub(): + try: + yield 1 + yield 2 + except GeneratorExit: + closed.append(True) + raise + + agen = (*sub() async for _ in self._aiter([0])) + self.assertEqual(self._run(agen.asend(None)), 1) + self._run(agen.aclose()) + self.assertEqual(closed, [True]) + + def test_unpack_helper_throw_requires_argument(self): + # The internal sync-iterable wrapper is reachable via ag_await while + # suspended at the delegation; throw() must validate its arity rather + # than crash (gh-143055). + agen = (*[1, 2, 3] async for _ in self._aiter([0])) + self._run(agen.asend(None)) + wrapper = agen.ag_await + self.assertIsNotNone(wrapper) + with self.assertRaises(TypeError): + wrapper.throw() + # A valid exception is still accepted and propagates out. + with self.assertRaises(ValueError): + wrapper.throw(ValueError('boom')) + + def test_unpacking_async_iterable_is_a_type_error(self): + # ``*`` unpacking is synchronous; async iterables cannot be unpacked. + async def agen_fn(): + yield 1 + + agen = (*agen_fn() async for _ in self._aiter([0])) + with self.assertRaises(TypeError): + self._run(agen.asend(None)) + + def load_tests(loader, tests, pattern): tests.addTest(doctest.DocTestSuite()) return tests diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-29-12-00-00.gh-issue-143055.Yd8fG2.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-29-12-00-00.gh-issue-143055.Yd8fG2.rst new file mode 100644 index 000000000000000..ab298259a25bb3f --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-29-12-00-00.gh-issue-143055.Yd8fG2.rst @@ -0,0 +1,9 @@ +Unpacking a sub-iterable with ``*`` in a generator expression (:pep:`798`) +now delegates to the sub-iterable using :keyword:`yield from ` +semantics, so that values sent with :meth:`~generator.send` and exceptions +thrown with :meth:`~generator.throw` are forwarded to the sub-iterator. This +also works in asynchronous generator expressions, where +:meth:`~agen.asend` is forwarded to the (synchronous) sub-iterator's +:meth:`~generator.send`, and :meth:`~agen.athrow` and :meth:`~agen.aclose` +are forwarded to its :meth:`~generator.throw` (:meth:`~agen.aclose` throwing +:exc:`GeneratorExit`). diff --git a/Objects/genobject.c b/Objects/genobject.c index 3cdc06733363d3e..495ac36db602ae2 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -2262,6 +2262,212 @@ _PyAsyncGenValueWrapperNew(PyThreadState *tstate, PyObject *val) } +/* ---------- Async Generator unpacking helper (PEP 798) ------------ */ + +/* Wraps a *synchronous* iterable so that it can be delegated to with + `yield from` from inside an asynchronous generator. This is used to + implement unpacking a sync iterable with `*` in an async generator + expression, e.g. `(*it async for it in its)`. + + Every value produced by the underlying iterator -- whether through plain + iteration, send(), or throw() -- is wrapped as an async-generator value, so + that the async generator machinery delivers it to the consumer as an async + yield rather than treating it as an awaited value. The result of the + delegation (the StopIteration value) and any propagating exception are + passed through unwrapped. */ + +typedef struct { + PyObject_HEAD + PyObject *agu_iterator; +} PyAsyncGenUnpack; + +#define _PyAsyncGenUnpack_CAST(op) \ + (assert(Py_IS_TYPE((op), &_PyAsyncGenUnpack_Type)), \ + _Py_CAST(PyAsyncGenUnpack*, (op))) + +static int +async_gen_unpack_traverse(PyObject *op, visitproc visit, void *arg) +{ + PyAsyncGenUnpack *o = _PyAsyncGenUnpack_CAST(op); + Py_VISIT(o->agu_iterator); + return 0; +} + +static int +async_gen_unpack_clear(PyObject *op) +{ + PyAsyncGenUnpack *o = _PyAsyncGenUnpack_CAST(op); + Py_CLEAR(o->agu_iterator); + return 0; +} + +static void +async_gen_unpack_dealloc(PyObject *op) +{ + _PyObject_GC_UNTRACK(op); + (void)async_gen_unpack_clear(op); + PyObject_GC_Del(op); +} + +/* Wrap a value yielded by the underlying iterator. Steals a reference to + *value* (which may be NULL, in which case NULL is returned and any pending + exception is left set). */ +static PyObject * +async_gen_unpack_wrap(PyObject *value) +{ + if (value == NULL) { + return NULL; + } + PyObject *wrapped = _PyAsyncGenValueWrapperNew(_PyThreadState_GET(), value); + Py_DECREF(value); + return wrapped; +} + +static PyObject * +async_gen_unpack_iternext(PyObject *op) +{ + PyAsyncGenUnpack *o = _PyAsyncGenUnpack_CAST(op); + return async_gen_unpack_wrap(PyIter_Next(o->agu_iterator)); +} + +static PySendResult +async_gen_unpack_am_send(PyObject *op, PyObject *arg, PyObject **presult) +{ + PyAsyncGenUnpack *o = _PyAsyncGenUnpack_CAST(op); + PySendResult res = PyIter_Send(o->agu_iterator, arg, presult); + if (res == PYGEN_NEXT) { + /* A yielded value must be wrapped. The return value of the + delegation (PYGEN_RETURN) is passed through unwrapped. */ + PyObject *wrapped = async_gen_unpack_wrap(*presult); + if (wrapped == NULL) { + *presult = NULL; + return PYGEN_ERROR; + } + *presult = wrapped; + } + return res; +} + +static PyObject * +async_gen_unpack_throw(PyObject *op, PyObject *const *args, Py_ssize_t nargs) +{ + if (!_PyArg_CheckPositional("throw", nargs, 1, 3)) { + return NULL; + } + PyAsyncGenUnpack *o = _PyAsyncGenUnpack_CAST(op); + PyObject *meth; + if (PyObject_GetOptionalAttr(o->agu_iterator, &_Py_ID(throw), &meth) < 0) { + return NULL; + } + if (meth == NULL) { + /* The underlying iterator cannot handle a thrown exception, so raise + it here. This mirrors how `yield from` behaves when throwing into + a subiterator that does not define throw(): the exception + propagates out of the delegation. */ + PyObject *typ = args[0]; + PyObject *val = nargs >= 2 ? args[1] : NULL; + PyObject *tb = nargs >= 3 ? args[2] : NULL; + gen_set_exception(typ, val, tb); + return NULL; + } + PyObject *res = PyObject_Vectorcall(meth, args, (size_t)nargs, NULL); + Py_DECREF(meth); + /* If throw() raised (e.g. StopIteration when the subiterator returns, or + any other exception), let it propagate unwrapped. */ + return async_gen_unpack_wrap(res); +} + +static PyObject * +async_gen_unpack_close(PyObject *op, PyObject *Py_UNUSED(ignored)) +{ + PyAsyncGenUnpack *o = _PyAsyncGenUnpack_CAST(op); + PyObject *meth; + if (PyObject_GetOptionalAttr(o->agu_iterator, &_Py_ID(close), &meth) < 0) { + return NULL; + } + if (meth == NULL) { + Py_RETURN_NONE; + } + PyObject *res = _PyObject_CallNoArgs(meth); + Py_DECREF(meth); + return res; +} + +static PyMethodDef async_gen_unpack_methods[] = { + {"throw", _PyCFunction_CAST(async_gen_unpack_throw), METH_FASTCALL, NULL}, + {"close", async_gen_unpack_close, METH_NOARGS, NULL}, + {NULL, NULL} /* Sentinel */ +}; + +static PyAsyncMethods async_gen_unpack_as_async = { + 0, /* am_await */ + 0, /* am_aiter */ + 0, /* am_anext */ + async_gen_unpack_am_send, /* am_send */ +}; + +PyTypeObject _PyAsyncGenUnpack_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "async_generator_unpack", /* tp_name */ + sizeof(PyAsyncGenUnpack), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + async_gen_unpack_dealloc, /* tp_dealloc */ + 0, /* tp_vectorcall_offset */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + &async_gen_unpack_as_async, /* tp_as_async */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + async_gen_unpack_traverse, /* tp_traverse */ + async_gen_unpack_clear, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + async_gen_unpack_iternext, /* tp_iternext */ + async_gen_unpack_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + +PyObject * +_PyAsyncGenUnpack_New(PyThreadState *tstate, PyObject *iterable) +{ + assert(iterable != NULL); + PyObject *iterator = PyObject_GetIter(iterable); + if (iterator == NULL) { + return NULL; + } + PyAsyncGenUnpack *o = PyObject_GC_New(PyAsyncGenUnpack, + &_PyAsyncGenUnpack_Type); + if (o == NULL) { + Py_DECREF(iterator); + return NULL; + } + o->agu_iterator = iterator; + _PyObject_GC_TRACK((PyObject *)o); + return (PyObject *)o; +} + + /* ---------- Async Generator AThrow awaitable ------------ */ #define _PyAsyncGenAThrow_CAST(op) \ diff --git a/Objects/object.c b/Objects/object.c index bd23c2e23881949..1aa426ece37c4d3 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2615,6 +2615,7 @@ static PyTypeObject* static_types[_Py_NUM_MANAGED_PREINITIALIZED_TYPES] = { &_PyAnextAwaitable_Type, &_PyAsyncGenASend_Type, &_PyAsyncGenAThrow_Type, + &_PyAsyncGenUnpack_Type, &_PyAsyncGenWrappedValue_Type, &_PyBufferWrapper_Type, &_PyContextTokenMissing_Type, diff --git a/Python/codegen.c b/Python/codegen.c index e65c308617df5ee..acda02517d2d70a 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -4536,6 +4536,34 @@ codegen_call_helper(compiler *c, location loc, */ +/* Emit code for a starred element in a generator expression, e.g. the `*it` + in `(*it for it in its)`. This delegates to the sub-iterable using + `yield from` semantics, so that values sent to (and exceptions thrown into) + the generator are forwarded to the sub-iterator. + + When the surrounding comprehension is an async generator, the (synchronous) + sub-iterable is first wrapped via INTRINSIC_ASYNC_GEN_UNPACK so that every + value it produces -- including values produced in response to asend() and + athrow() -- is wrapped as an async-generator value. Otherwise the async + generator machinery would treat those values as awaited values rather than + async yields. */ +static int +codegen_genexp_starred_element(compiler *c, location elt_loc, expr_ty elt) +{ + PySTEntryObject *ste = SYMTABLE_ENTRY(c); + int async_gen = ste->ste_generator && ste->ste_coroutine; + VISIT(c, expr, elt->v.Starred.value); + if (async_gen) { + ADDOP_I(c, elt_loc, CALL_INTRINSIC_1, INTRINSIC_ASYNC_GEN_UNPACK); + } + ADDOP_I(c, elt_loc, GET_ITER, GET_ITER_YIELD_FROM); + ADDOP_LOAD_CONST(c, elt_loc, Py_None); + ADD_YIELD_FROM(c, elt_loc, 0); + ADDOP(c, elt_loc, POP_TOP); + return SUCCESS; +} + + static int codegen_comprehension_generator(compiler *c, location loc, asdl_comprehension_seq *generators, int gen_index, @@ -4640,18 +4668,8 @@ codegen_sync_comprehension_generator(compiler *c, location loc, switch (type) { case COMP_GENEXP: if (elt->kind == Starred_kind) { - NEW_JUMP_TARGET_LABEL(c, unpack_start); - NEW_JUMP_TARGET_LABEL(c, unpack_end); - VISIT(c, expr, elt->v.Starred.value); - ADDOP_I(c, elt_loc, GET_ITER, 0); - USE_LABEL(c, unpack_start); - ADDOP_JUMP(c, elt_loc, FOR_ITER, unpack_end); - ADDOP_YIELD(c, elt_loc); - ADDOP(c, elt_loc, POP_TOP); - ADDOP_JUMP(c, NO_LOCATION, JUMP, unpack_start); - USE_LABEL(c, unpack_end); - ADDOP(c, NO_LOCATION, END_FOR); - ADDOP(c, NO_LOCATION, POP_ITER); + RETURN_IF_ERROR( + codegen_genexp_starred_element(c, elt_loc, elt)); } else { VISIT(c, expr, elt); @@ -4783,18 +4801,8 @@ codegen_async_comprehension_generator(compiler *c, location loc, switch (type) { case COMP_GENEXP: if (elt->kind == Starred_kind) { - NEW_JUMP_TARGET_LABEL(c, unpack_start); - NEW_JUMP_TARGET_LABEL(c, unpack_end); - VISIT(c, expr, elt->v.Starred.value); - ADDOP_I(c, elt_loc, GET_ITER, 0); - USE_LABEL(c, unpack_start); - ADDOP_JUMP(c, elt_loc, FOR_ITER, unpack_end); - ADDOP_YIELD(c, elt_loc); - ADDOP(c, elt_loc, POP_TOP); - ADDOP_JUMP(c, NO_LOCATION, JUMP, unpack_start); - USE_LABEL(c, unpack_end); - ADDOP(c, NO_LOCATION, END_FOR); - ADDOP(c, NO_LOCATION, POP_ITER); + RETURN_IF_ERROR( + codegen_genexp_starred_element(c, elt_loc, elt)); } else { VISIT(c, expr, elt); diff --git a/Python/intrinsics.c b/Python/intrinsics.c index f081f33cc83b88c..aeb708e390ef1b7 100644 --- a/Python/intrinsics.c +++ b/Python/intrinsics.c @@ -235,6 +235,7 @@ _PyIntrinsics_UnaryFunctions[] = { INTRINSIC_FUNC_ENTRY(INTRINSIC_SUBSCRIPT_GENERIC, _Py_subscript_generic) INTRINSIC_FUNC_ENTRY(INTRINSIC_TYPEALIAS, _Py_make_typealias) INTRINSIC_FUNC_ENTRY(INTRINSIC_BUILD_FROZENSET, make_frozenset) + INTRINSIC_FUNC_ENTRY(INTRINSIC_ASYNC_GEN_UNPACK, _PyAsyncGenUnpack_New) }; diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index db575d870be5c53..b0f0e60d3e15ee3 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -53,6 +53,7 @@ Objects/genobject.c - PyCoro_Type - Objects/genobject.c - PyGen_Type - Objects/genobject.c - _PyAsyncGenASend_Type - Objects/genobject.c - _PyAsyncGenAThrow_Type - +Objects/genobject.c - _PyAsyncGenUnpack_Type - Objects/genobject.c - _PyAsyncGenWrappedValue_Type - Objects/genobject.c - _PyCoroWrapper_Type - Objects/interpolationobject.c - _PyInterpolation_Type -