diff -r af793c7580f1 Doc/c-api/concrete.rst --- a/Doc/c-api/concrete.rst Wed Jun 17 10:09:24 2015 -0500 +++ b/Doc/c-api/concrete.rst Fri Jun 19 12:23:55 2015 -0400 @@ -112,5 +112,6 @@ weakref.rst capsule.rst gen.rst + coro.rst datetime.rst diff -r af793c7580f1 Doc/c-api/coro.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Doc/c-api/coro.rst Fri Jun 19 12:23:55 2015 -0400 @@ -0,0 +1,40 @@ +.. highlightlang:: c + +.. _coro-objects: + +Coroutine Objects +----------------- + +Coroutine objects are what functions declared with an ``async`` keyword +return. + + +.. c:type:: PyCoroObject + + The C structure used for coroutine objects. + + .. versionadded:: 3.5 + + +.. c:var:: PyTypeObject PyCoro_Type + + The type object corresponding to coroutine objects. + + .. versionadded:: 3.5 + + +.. c:function:: int PyCoro_CheckExact(PyObject *ob) + + Return true if *ob*'s type is *PyCoro_Type*; *ob* must not be *NULL*. + + .. versionadded:: 3.5 + + +.. c:function:: PyObject* PyCoro_New(PyFrameObject *frame, PyObject *name, PyObject *qualname) + + Create and return a new coroutine object based on the *frame* object, + with ``__name__`` and ``__qualname__`` set to *name* and *qualname*. + A reference to *frame* is stolen by this function. *frame* must not be + *NULL*. + + .. versionadded:: 3.5 diff -r af793c7580f1 Doc/c-api/gen.rst --- a/Doc/c-api/gen.rst Wed Jun 17 10:09:24 2015 -0500 +++ b/Doc/c-api/gen.rst Fri Jun 19 12:23:55 2015 -0400 @@ -7,7 +7,7 @@ Generator objects are what Python uses to implement generator iterators. They are normally created by iterating over a function that yields values, rather -than explicitly calling :c:func:`PyGen_New`. +than explicitly calling :c:func:`PyGen_New` or :c:func:`PyGen_NewWithQualName`. .. c:type:: PyGenObject @@ -20,19 +20,25 @@ The type object corresponding to generator objects -.. c:function:: int PyGen_Check(ob) +.. c:function:: int PyGen_Check(PyObject *ob) Return true if *ob* is a generator object; *ob* must not be *NULL*. -.. c:function:: int PyGen_CheckExact(ob) +.. c:function:: int PyGen_CheckExact(PyObject *ob) - Return true if *ob*'s type is *PyGen_Type* is a generator object; *ob* must not - be *NULL*. + Return true if *ob*'s type is *PyGen_Type*; *ob* must not be *NULL*. .. c:function:: PyObject* PyGen_New(PyFrameObject *frame) - Create and return a new generator object based on the *frame* object. A - reference to *frame* is stolen by this function. The parameter must not be + Create and return a new generator object based on the *frame* object. + A reference to *frame* is stolen by this function. The parameter must not be *NULL*. + +.. c:function:: PyObject* PyGen_NewWithQualName(PyFrameObject *frame, PyObject *name, PyObject *qualname) + + Create and return a new generator object based on the *frame* object, + with ``__name__`` and ``__qualname__`` set to *name* and *qualname*. + A reference to *frame* is stolen by this function. *frame* must not be + *NULL*. diff -r af793c7580f1 Doc/data/refcounts.dat --- a/Doc/data/refcounts.dat Wed Jun 17 10:09:24 2015 -0500 +++ b/Doc/data/refcounts.dat Fri Jun 19 12:23:55 2015 -0400 @@ -491,6 +491,12 @@ PyGen_New:PyObject*::+1: PyGen_New:PyFrameObject*:frame:0: +PyGen_NewWithQualName:PyObject*::+1: +PyGen_NewWithQualName:PyFrameObject*:frame:0: + +PyCoro_New:PyObject*::+1: +PyCoro_New:PyFrameObject*:frame:0: + Py_InitModule:PyObject*::0: Py_InitModule:const char*:name:: Py_InitModule:PyMethodDef[]:methods:: diff -r af793c7580f1 Doc/library/dis.rst --- a/Doc/library/dis.rst Wed Jun 17 10:09:24 2015 -0500 +++ b/Doc/library/dis.rst Fri Jun 19 12:23:55 2015 -0400 @@ -346,6 +346,14 @@ Implements ``TOS = iter(TOS)``. +.. opcode:: GET_YIELD_FROM_ITER + + If ``TOS`` is a :term:`generator` or :term:`coroutine` object it is left + as is. Otherwise, implements ``TOS = iter(TOS)``. + + .. versionadded:: 3.5 + + **Binary operations** Binary operations remove the top of the stack (TOS) and the second top-most diff -r af793c7580f1 Doc/library/inspect.rst --- a/Doc/library/inspect.rst Wed Jun 17 10:09:24 2015 -0500 +++ b/Doc/library/inspect.rst Fri Jun 19 12:23:55 2015 -0400 @@ -178,6 +178,16 @@ +-----------+-----------------+---------------------------+ | | gi_code | code | +-----------+-----------------+---------------------------+ +| coroutine | __name__ | name | ++-----------+-----------------+---------------------------+ +| | __qualname__ | qualified name | ++-----------+-----------------+---------------------------+ +| | cr_frame | frame | ++-----------+-----------------+---------------------------+ +| | cr_running | is the coroutine running? | ++-----------+-----------------+---------------------------+ +| | cr_code | code | ++-----------+-----------------+---------------------------+ | builtin | __doc__ | documentation string | +-----------+-----------------+---------------------------+ | | __name__ | original name of this | @@ -1116,8 +1126,8 @@ pass -Current State of a Generator ----------------------------- +Current State of Generators and Coroutines +------------------------------------------ When implementing coroutine schedulers and for other advanced uses of generators, it is useful to determine whether a generator is currently @@ -1137,6 +1147,18 @@ .. versionadded:: 3.2 +.. function:: getcoroutinestate(coroutine) + + Get current state of a coroutine object. + + Possible states are: + * CORO_CREATED: Waiting to start execution. + * CORO_RUNNING: Currently being executed by the interpreter. + * CORO_SUSPENDED: Currently suspended at an await expression. + * CORO_CLOSED: Execution has completed. + + .. versionadded:: 3.5 + The current internal state of the generator can also be queried. This is mostly useful for testing purposes, to ensure that internal state is being updated as expected: @@ -1161,6 +1183,13 @@ .. versionadded:: 3.3 +.. function:: getcorotuinelocals(coroutine) + + This function is analogous to :func:`~inspect.getgeneratorlocals`, but + works for coroutine objects created by :keyword:`async def` functions. + + .. versionadded:: 3.5 + .. _inspect-module-cli: diff -r af793c7580f1 Doc/library/sys.rst --- a/Doc/library/sys.rst Wed Jun 17 10:09:24 2015 -0500 +++ b/Doc/library/sys.rst Fri Jun 19 12:23:55 2015 -0400 @@ -1075,7 +1075,9 @@ .. function:: set_coroutine_wrapper(wrapper) - Allows to intercept creation of :term:`coroutine` objects. + Allows to intercept creation of :term:`coroutine` objects (only ones that + are created by an ``async def`` function; generators decorated with + :func:`types.coroutine` will not be intercepted). *wrapper* must be either: diff -r af793c7580f1 Doc/library/types.rst --- a/Doc/library/types.rst Wed Jun 17 10:09:24 2015 -0500 +++ b/Doc/library/types.rst Fri Jun 19 12:23:55 2015 -0400 @@ -90,6 +90,14 @@ generator function. +.. data:: CoroutineType + + The type of :term:`coroutine` objects, produced by calling a + function defined with an :keyword:`async def` keyword. + + .. versionadded:: 3.5 + + .. data:: CodeType .. index:: builtin: compile diff -r af793c7580f1 Doc/whatsnew/3.5.rst --- a/Doc/whatsnew/3.5.rst Wed Jun 17 10:09:24 2015 -0500 +++ b/Doc/whatsnew/3.5.rst Fri Jun 19 12:23:55 2015 -0400 @@ -734,6 +734,9 @@ * New :func:`~types.coroutine` function. (Contributed by Yury Selivanov in :issue:`24017`.) +* New :class:`~types.CoroutineType`. (Contributed by Yury Selivanov + in :issue:`24400`.) + urllib ------ @@ -1101,6 +1104,7 @@ the :attr:`__module__` attribute. Would be an AttributeError in future. (:issue:`20204`) -* As part of PEP 492 implementation, ``tp_reserved`` slot of +* As part of :pep:`492` implementation, ``tp_reserved`` slot of :c:type:`PyTypeObject` was replaced with a - :c:member:`PyTypeObject.tp_as_async` slot. + :c:member:`PyTypeObject.tp_as_async` slot. Refer to :ref:`coro-objects` for + new types, structures and functions. diff -r af793c7580f1 Include/genobject.h --- a/Include/genobject.h Wed Jun 17 10:09:24 2015 -0500 +++ b/Include/genobject.h Fri Jun 19 12:23:55 2015 -0400 @@ -10,27 +10,26 @@ struct _frame; /* Avoid including frameobject.h */ +/* _PyGenObject_HEAD defines the initial segment of generator + and coroutine objects. */ +#define _PyGenObject_HEAD(prefix) \ + PyObject_HEAD \ + /* Note: gi_frame can be NULL if the generator is "finished" */ \ + struct _frame *prefix##_frame; \ + /* True if generator is being executed. */ \ + char prefix##_running; \ + /* The code object backing the generator */ \ + PyObject *prefix##_code; \ + /* List of weak reference. */ \ + PyObject *prefix##_weakreflist; \ + /* Name of the generator. */ \ + PyObject *prefix##_name; \ + /* Qualified name of the generator. */ \ + PyObject *prefix##_qualname; + typedef struct { - PyObject_HEAD /* The gi_ prefix is intended to remind of generator-iterator. */ - - /* Note: gi_frame can be NULL if the generator is "finished" */ - struct _frame *gi_frame; - - /* True if generator is being executed. */ - char gi_running; - - /* The code object backing the generator */ - PyObject *gi_code; - - /* List of weak reference. */ - PyObject *gi_weakreflist; - - /* Name of the generator. */ - PyObject *gi_name; - - /* Qualified name of the generator. */ - PyObject *gi_qualname; + _PyGenObject_HEAD(gi) } PyGenObject; PyAPI_DATA(PyTypeObject) PyGen_Type; @@ -38,12 +37,6 @@ #define PyGen_Check(op) PyObject_TypeCheck(op, &PyGen_Type) #define PyGen_CheckExact(op) (Py_TYPE(op) == &PyGen_Type) -#define PyGen_CheckCoroutineExact(op) (PyGen_CheckExact(op) && \ - (((PyCodeObject*) \ - ((PyGenObject*)op)->gi_code) \ - ->co_flags & (CO_ITERABLE_COROUTINE | \ - CO_COROUTINE))) - PyAPI_FUNC(PyObject *) PyGen_New(struct _frame *); PyAPI_FUNC(PyObject *) PyGen_NewWithQualName(struct _frame *, PyObject *name, PyObject *qualname); @@ -52,7 +45,21 @@ PyObject *_PyGen_Send(PyGenObject *, PyObject *); PyAPI_FUNC(void) _PyGen_Finalize(PyObject *self); -PyObject *_PyGen_GetAwaitableIter(PyObject *o); +#ifndef Py_LIMITED_API +typedef struct { + _PyGenObject_HEAD(cr) +} PyCoroObject; + +PyAPI_DATA(PyTypeObject) PyCoro_Type; +PyAPI_DATA(PyTypeObject) _PyCoroWrapper_Type; + +#define PyCoro_CheckExact(op) (Py_TYPE(op) == &PyCoro_Type) +PyObject *_PyCoro_GetAwaitableIter(PyObject *o); +PyAPI_FUNC(PyObject *) PyCoro_New(struct _frame *, + PyObject *name, PyObject *qualname); +#endif + +#undef _PyGenObject_HEAD #ifdef __cplusplus } diff -r af793c7580f1 Include/opcode.h --- a/Include/opcode.h Wed Jun 17 10:09:24 2015 -0500 +++ b/Include/opcode.h Fri Jun 19 12:23:55 2015 -0400 @@ -45,6 +45,7 @@ #define BINARY_OR 66 #define INPLACE_POWER 67 #define GET_ITER 68 +#define GET_YIELD_FROM_ITER 69 #define PRINT_EXPR 70 #define LOAD_BUILD_CLASS 71 #define YIELD_FROM 72 diff -r af793c7580f1 Lib/_collections_abc.py --- a/Lib/_collections_abc.py Wed Jun 17 10:09:24 2015 -0500 +++ b/Lib/_collections_abc.py Fri Jun 19 12:23:55 2015 -0400 @@ -52,6 +52,12 @@ ## misc ## mappingproxy = type(type.__dict__) generator = type((lambda: (yield))()) +## coroutine ## +async def _coro(): pass +_coro = _coro() +coroutine = type(_coro) +_coro.close() # Prevent ResourceWarning +del _coro ### ONE-TRICK PONIES ### @@ -78,17 +84,12 @@ class _AwaitableMeta(ABCMeta): def __instancecheck__(cls, instance): - # 0x80 = CO_COROUTINE # 0x100 = CO_ITERABLE_COROUTINE # We don't want to import 'inspect' module, as # a dependency for 'collections.abc'. - CO_COROUTINES = 0x80 | 0x100 - - if (isinstance(instance, generator) and - instance.gi_code.co_flags & CO_COROUTINES): - + if (instance.__class__ is generator and + instance.gi_code.co_flags & 0x100): return True - return super().__instancecheck__(instance) @@ -159,6 +160,9 @@ return NotImplemented +Coroutine.register(coroutine) + + class AsyncIterable(metaclass=ABCMeta): __slots__ = () diff -r af793c7580f1 Lib/asyncio/coroutines.py --- a/Lib/asyncio/coroutines.py Wed Jun 17 10:09:24 2015 -0500 +++ b/Lib/asyncio/coroutines.py Fri Jun 19 12:23:55 2015 -0400 @@ -89,10 +89,7 @@ # We only wrap here coroutines defined via 'async def' syntax. # Generator-based coroutines are wrapped in @coroutine # decorator. - if _is_native_coro_code(gen.gi_code): - return CoroWrapper(gen, None) - else: - return gen + return CoroWrapper(gen, None) class CoroWrapper: diff -r af793c7580f1 Lib/inspect.py --- a/Lib/inspect.py Wed Jun 17 10:09:24 2015 -0500 +++ b/Lib/inspect.py Fri Jun 19 12:23:55 2015 -0400 @@ -1598,6 +1598,53 @@ else: return {} + +# ------------------------------------------------ coroutine introspection + +CORO_CREATED = 'CORO_CREATED' +CORO_RUNNING = 'CORO_RUNNING' +CORO_SUSPENDED = 'CORO_SUSPENDED' +CORO_CLOSED = 'CORO_CLOSED' + +def getcoroutinestate(coroutine): + """Get current state of a coroutine object. + + Possible states are: + CORO_CREATED: Waiting to start execution. + CORO_RUNNING: Currently being executed by the interpreter. + CORO_SUSPENDED: Currently suspended at an await expression. + CORO_CLOSED: Execution has completed. + """ + if not isinstance(coroutine, types.CoroutineType): + raise TypeError( + '{!r} is not a Python coroutine'.format(coroutine)) + if coroutine.cr_running: + return CORO_RUNNING + if coroutine.cr_frame is None: + return CORO_CLOSED + if coroutine.cr_frame.f_lasti == -1: + return CORO_CREATED + return CORO_SUSPENDED + + +def getcoroutinelocals(coroutine): + """ + Get the mapping of coroutine local variables to their current values. + + A dict is returned, with the keys the local variable names and values the + bound values.""" + + if not isinstance(coroutine, types.CoroutineType): + raise TypeError( + '{!r} is not a Python coroutine'.format(coroutine)) + + frame = getattr(coroutine, "cr_frame", None) + if frame is not None: + return frame.f_locals + else: + return {} + + ############################################################################### ### Function Signature Object (PEP 362) ############################################################################### diff -r af793c7580f1 Lib/opcode.py --- a/Lib/opcode.py Wed Jun 17 10:09:24 2015 -0500 +++ b/Lib/opcode.py Fri Jun 19 12:23:55 2015 -0400 @@ -103,6 +103,7 @@ def_op('BINARY_OR', 66) def_op('INPLACE_POWER', 67) def_op('GET_ITER', 68) +def_op('GET_YIELD_FROM_ITER', 69) def_op('PRINT_EXPR', 70) def_op('LOAD_BUILD_CLASS', 71) diff -r af793c7580f1 Lib/test/test_coroutines.py --- a/Lib/test/test_coroutines.py Wed Jun 17 10:09:24 2015 -0500 +++ b/Lib/test/test_coroutines.py Fri Jun 19 12:23:55 2015 -0400 @@ -24,7 +24,7 @@ def run_async(coro): - assert coro.__class__ is types.GeneratorType + assert coro.__class__ in {types.GeneratorType, types.CoroutineType} buffer = [] result = None @@ -37,6 +37,25 @@ return buffer, result +def run_async__await__(coro): + assert coro.__class__ is types.CoroutineType + aw = coro.__await__() + buffer = [] + result = None + i = 0 + while True: + try: + if i % 2: + buffer.append(next(aw)) + else: + buffer.append(aw.send(None)) + i += 1 + except StopIteration as ex: + result = ex.args[0] if ex.args else None + break + return buffer, result + + @contextlib.contextmanager def silence_coro_gc(): with warnings.catch_warnings(): @@ -121,13 +140,15 @@ return 10 f = foo() - self.assertIsInstance(f, types.GeneratorType) + self.assertIsInstance(f, types.CoroutineType) self.assertTrue(bool(foo.__code__.co_flags & 0x80)) - self.assertTrue(bool(foo.__code__.co_flags & 0x20)) - self.assertTrue(bool(f.gi_code.co_flags & 0x80)) - self.assertTrue(bool(f.gi_code.co_flags & 0x20)) + self.assertFalse(bool(foo.__code__.co_flags & 0x20)) + self.assertTrue(bool(f.cr_code.co_flags & 0x80)) + self.assertFalse(bool(f.cr_code.co_flags & 0x20)) self.assertEqual(run_async(f), ([], 10)) + self.assertEqual(run_async__await__(foo()), ([], 10)) + def bar(): pass self.assertFalse(bool(bar.__code__.co_flags & 0x80)) @@ -136,7 +157,7 @@ raise StopIteration with self.assertRaisesRegex( - RuntimeError, "generator raised StopIteration"): + RuntimeError, "coroutine raised StopIteration"): run_async(foo()) @@ -152,7 +173,7 @@ raise StopIteration check = lambda: self.assertRaisesRegex( - TypeError, "coroutine-objects do not support iteration") + TypeError, "'coroutine' object is not iterable") with check(): list(foo()) @@ -166,9 +187,6 @@ with check(): iter(foo()) - with check(): - next(foo()) - with silence_coro_gc(), check(): for i in foo(): pass @@ -185,7 +203,7 @@ await bar() check = lambda: self.assertRaisesRegex( - TypeError, "coroutine-objects do not support iteration") + TypeError, "'coroutine' object is not iterable") with check(): for el in foo(): pass @@ -221,7 +239,7 @@ with silence_coro_gc(), self.assertRaisesRegex( TypeError, - "cannot 'yield from' a coroutine object from a generator"): + "cannot 'yield from' a coroutine object in a non-coroutine generator"): list(foo()) @@ -244,6 +262,98 @@ foo() support.gc_collect() + def test_func_10(self): + N = 0 + + @types.coroutine + def gen(): + nonlocal N + try: + a = yield + yield (a ** 2) + except ZeroDivisionError: + N += 100 + raise + finally: + N += 1 + + async def foo(): + await gen() + + coro = foo() + aw = coro.__await__() + self.assertIs(aw, iter(aw)) + next(aw) + self.assertEqual(aw.send(10), 100) + + self.assertEqual(N, 0) + aw.close() + self.assertEqual(N, 1) + + coro = foo() + aw = coro.__await__() + next(aw) + with self.assertRaises(ZeroDivisionError): + aw.throw(ZeroDivisionError, None, None) + self.assertEqual(N, 102) + + def test_func_11(self): + async def func(): pass + coro = func() + # Test that PyCoro_Type and _PyCoroWrapper_Type types were properly + # initialized + self.assertIn('__await__', dir(coro)) + self.assertIn('__iter__', dir(coro.__await__())) + self.assertIn('coroutine_wrapper', repr(coro.__await__())) + coro.close() # avoid RuntimeWarning + + def test_func_12(self): + async def g(): + i = me.send(None) + await foo + me = g() + with self.assertRaisesRegex(ValueError, + "coroutine already executing"): + me.send(None) + + def test_func_13(self): + async def g(): + pass + with self.assertRaisesRegex( + TypeError, + "can't send non-None value to a just-started coroutine"): + + g().send('spam') + + def test_func_14(self): + @types.coroutine + def gen(): + yield + async def coro(): + try: + await gen() + except GeneratorExit: + await gen() + c = coro() + c.send(None) + with self.assertRaisesRegex(RuntimeError, + "coroutine ignored GeneratorExit"): + c.close() + + def test_corotype_1(self): + ct = types.CoroutineType + self.assertIn('into coroutine', ct.send.__doc__) + self.assertIn('inside coroutine', ct.close.__doc__) + self.assertIn('in coroutine', ct.throw.__doc__) + self.assertIn('of the coroutine', ct.__dict__['__name__'].__doc__) + self.assertIn('of the coroutine', ct.__dict__['__qualname__'].__doc__) + self.assertEqual(ct.__name__, 'coroutine') + + async def f(): pass + c = f() + self.assertIn('coroutine object', repr(c)) + c.close() + def test_await_1(self): async def foo(): @@ -262,6 +372,7 @@ await AsyncYieldFrom([1, 2, 3]) self.assertEqual(run_async(foo()), ([1, 2, 3], None)) + self.assertEqual(run_async__await__(foo()), ([1, 2, 3], None)) def test_await_4(self): async def bar(): @@ -1015,6 +1126,24 @@ finally: sys.set_coroutine_wrapper(None) + def test_set_wrapper_4(self): + @types.coroutine + def foo(): + return 'spam' + + wrapped = None + def wrap(gen): + nonlocal wrapped + wrapped = gen + return gen + + sys.set_coroutine_wrapper(wrap) + try: + foo() + self.assertIs(wrapped, None) + finally: + sys.set_coroutine_wrapper(None) + class CAPITest(unittest.TestCase): diff -r af793c7580f1 Lib/test/test_inspect.py --- a/Lib/test/test_inspect.py Wed Jun 17 10:09:24 2015 -0500 +++ b/Lib/test/test_inspect.py Fri Jun 19 12:23:55 2015 -0400 @@ -1737,6 +1737,76 @@ self.assertRaises(TypeError, inspect.getgeneratorlocals, (2,3)) +class TestGetCoroutineState(unittest.TestCase): + + def setUp(self): + @types.coroutine + def number_coroutine(): + for number in range(5): + yield number + async def coroutine(): + await number_coroutine() + self.coroutine = coroutine() + + def tearDown(self): + self.coroutine.close() + + def _coroutinestate(self): + return inspect.getcoroutinestate(self.coroutine) + + def test_created(self): + self.assertEqual(self._coroutinestate(), inspect.CORO_CREATED) + + def test_suspended(self): + self.coroutine.send(None) + self.assertEqual(self._coroutinestate(), inspect.CORO_SUSPENDED) + + def test_closed_after_exhaustion(self): + while True: + try: + self.coroutine.send(None) + except StopIteration: + break + + self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) + + def test_closed_after_immediate_exception(self): + with self.assertRaises(RuntimeError): + self.coroutine.throw(RuntimeError) + self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) + + def test_easy_debugging(self): + # repr() and str() of a coroutine state should contain the state name + names = 'CORO_CREATED CORO_RUNNING CORO_SUSPENDED CORO_CLOSED'.split() + for name in names: + state = getattr(inspect, name) + self.assertIn(name, repr(state)) + self.assertIn(name, str(state)) + + def test_getcoroutinelocals(self): + @types.coroutine + def gencoro(): + yield + + gencoro = gencoro() + async def func(a=None): + b = 'spam' + await gencoro + + coro = func() + self.assertEqual(inspect.getcoroutinelocals(coro), + {'a': None, 'gencoro': gencoro}) + coro.send(None) + self.assertEqual(inspect.getcoroutinelocals(coro), + {'a': None, 'gencoro': gencoro, 'b': 'spam'}) + + def test_getcoroutinelocals_error(self): + self.assertRaises(TypeError, inspect.getcoroutinelocals, 1) + self.assertRaises(TypeError, inspect.getcoroutinelocals, lambda x: (yield)) + self.assertRaises(TypeError, inspect.getcoroutinelocals, set) + self.assertRaises(TypeError, inspect.getcoroutinelocals, (2,3)) + + class MySignature(inspect.Signature): # Top-level to make it picklable; # used in test_signature_object_pickle @@ -3494,7 +3564,8 @@ TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject, TestBoundArguments, TestSignaturePrivateHelpers, TestSignatureDefinitions, - TestGetClosureVars, TestUnwrap, TestMain, TestReload + TestGetClosureVars, TestUnwrap, TestMain, TestReload, + TestGetCoroutineState ) if __name__ == "__main__": diff -r af793c7580f1 Lib/test/test_types.py --- a/Lib/test/test_types.py Wed Jun 17 10:09:24 2015 -0500 +++ b/Lib/test/test_types.py Fri Jun 19 12:23:55 2015 -0400 @@ -1205,10 +1205,28 @@ def test_wrong_func(self): @types.coroutine def foo(): - pass - with self.assertRaisesRegex(TypeError, - 'callable wrapped .* non-coroutine'): - foo() + return 'spam' + self.assertEqual(foo(), 'spam') + + def test_async_def(self): + # Test that types.coroutine passes 'async def' coroutines + # without modification + + async def foo(): pass + foo_code = foo.__code__ + foo_flags = foo.__code__.co_flags + decorated_foo = types.coroutine(foo) + self.assertIs(foo, decorated_foo) + self.assertEqual(foo.__code__.co_flags, foo_flags) + self.assertIs(decorated_foo.__code__, foo_code) + + foo_coro = foo() + @types.coroutine + def bar(): return foo_coro + coro = bar() + self.assertIs(foo_coro, coro) + self.assertEqual(coro.cr_code.co_flags, foo_flags) + coro.close() def test_duck_coro(self): class CoroLike: @@ -1221,6 +1239,23 @@ @types.coroutine def foo(): return coro + self.assertIs(foo(), coro) + self.assertIs(foo().__await__(), coro) + + def test_duck_corogen(self): + class CoroGenLike: + def send(self): pass + def throw(self): pass + def close(self): pass + def __await__(self): return self + def __iter__(self): return self + def __next__(self): pass + + coro = CoroGenLike() + @types.coroutine + def foo(): + return coro + self.assertIs(foo(), coro) self.assertIs(foo().__await__(), coro) def test_duck_gen(self): @@ -1236,7 +1271,7 @@ def foo(): return gen self.assertIs(foo().__await__(), gen) - + self.assertTrue(isinstance(foo(), collections.abc.Coroutine)) with self.assertRaises(AttributeError): foo().gi_code @@ -1259,7 +1294,13 @@ self.assertFalse(isinstance(gen(), collections.abc.Coroutine)) self.assertFalse(isinstance(gen(), collections.abc.Awaitable)) - self.assertIs(types.coroutine(gen), gen) + gen_code = gen.__code__ + decorated_gen = types.coroutine(gen) + self.assertIs(decorated_gen, gen) + self.assertIsNot(decorated_gen.__code__, gen_code) + + decorated_gen2 = types.coroutine(decorated_gen) + self.assertIs(decorated_gen2.__code__, decorated_gen.__code__) self.assertTrue(gen.__code__.co_flags & inspect.CO_ITERABLE_COROUTINE) self.assertFalse(gen.__code__.co_flags & inspect.CO_COROUTINE) diff -r af793c7580f1 Lib/types.py --- a/Lib/types.py Wed Jun 17 10:09:24 2015 -0500 +++ b/Lib/types.py Fri Jun 19 12:23:55 2015 -0400 @@ -19,6 +19,11 @@ yield 1 GeneratorType = type(_g()) +async def _c(): pass +_c = _c() +CoroutineType = type(_c) +_c.close() # Prevent ResourceWarning + class _C: def _m(self): pass MethodType = type(_C()._m) @@ -40,7 +45,7 @@ GetSetDescriptorType = type(FunctionType.__code__) MemberDescriptorType = type(FunctionType.__globals__) -del sys, _f, _g, _C, # Not for export +del sys, _f, _g, _C, _c, # Not for export # Provide a PEP 3115 compliant mechanism for class creation @@ -164,29 +169,33 @@ def coroutine(func): """Convert regular generator function to a coroutine.""" - # We don't want to import 'dis' or 'inspect' just for - # these constants. - CO_GENERATOR = 0x20 - CO_ITERABLE_COROUTINE = 0x100 - if not callable(func): raise TypeError('types.coroutine() expects a callable') - if (isinstance(func, FunctionType) and - isinstance(getattr(func, '__code__', None), CodeType) and - (func.__code__.co_flags & CO_GENERATOR)): + if (func.__class__ is FunctionType and + getattr(func, '__code__', None).__class__ is CodeType): - # TODO: Implement this in C. - co = func.__code__ - func.__code__ = CodeType( - co.co_argcount, co.co_kwonlyargcount, co.co_nlocals, - co.co_stacksize, - co.co_flags | CO_ITERABLE_COROUTINE, - co.co_code, - co.co_consts, co.co_names, co.co_varnames, co.co_filename, - co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars, - co.co_cellvars) - return func + co_flags = func.__code__.co_flags + + # Check if 'func' is a coroutine function. + # (0x180 == CO_COROUTINE | CO_ITERABLE_COROUTINE) + if co_flags & 0x180: + return func + + # Check if 'func' is a generator function. + # (0x20 == CO_GENERATOR) + if co_flags & 0x20: + # TODO: Implement this in C. + co = func.__code__ + func.__code__ = CodeType( + co.co_argcount, co.co_kwonlyargcount, co.co_nlocals, + co.co_stacksize, + co.co_flags | 0x100, # 0x100 == CO_ITERABLE_COROUTINE + co.co_code, + co.co_consts, co.co_names, co.co_varnames, co.co_filename, + co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars, + co.co_cellvars) + return func # The following code is primarily to support functions that # return generator-like objects (for instance generators @@ -195,11 +204,14 @@ class GeneratorWrapper: def __init__(self, gen): self.__wrapped__ = gen - self.send = gen.send - self.throw = gen.throw - self.close = gen.close self.__name__ = getattr(gen, '__name__', None) self.__qualname__ = getattr(gen, '__qualname__', None) + def send(self, val): + return self.__wrapped__.send(val) + def throw(self, *args): + return self.__wrapped__.throw(*args) + def close(self): + return self.__wrapped__.close() @property def gi_code(self): return self.__wrapped__.gi_code @@ -213,20 +225,24 @@ return next(self.__wrapped__) def __iter__(self): return self.__wrapped__ - __await__ = __iter__ + def __await__(self): + return self.__wrapped__ @_functools.wraps(func) def wrapped(*args, **kwargs): coro = func(*args, **kwargs) - if coro.__class__ is GeneratorType: + if coro.__class__ is CoroutineType: + # 'coro' is a native coroutine object. + return coro + if (coro.__class__ is GeneratorType or + (isinstance(coro, _collections_abc.Generator) and + not isinstance(coro, _collections_abc.Coroutine))): + # 'coro' is either a pure Python generator, or it implements + # collections.abc.Generator (and does not implement + # collections.abc.Coroutine). return GeneratorWrapper(coro) - # slow checks - if not isinstance(coro, _collections_abc.Coroutine): - if isinstance(coro, _collections_abc.Generator): - return GeneratorWrapper(coro) - raise TypeError( - 'callable wrapped with types.coroutine() returned ' - 'non-coroutine: {!r}'.format(coro)) + # 'coro' is either an instance of collections.abc.Coroutine or + # some other object -- pass it through. return coro return wrapped diff -r af793c7580f1 Objects/genobject.c --- a/Objects/genobject.c Wed Jun 17 10:09:24 2015 -0500 +++ b/Objects/genobject.c Fri Jun 19 12:23:55 2015 -0400 @@ -27,8 +27,7 @@ /* If `gen` is a coroutine, and if it was never awaited on, issue a RuntimeWarning. */ if (gen->gi_code != NULL - && ((PyCodeObject *)gen->gi_code)->co_flags & (CO_COROUTINE - | CO_ITERABLE_COROUTINE) + && ((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE && gen->gi_frame != NULL && gen->gi_frame->f_lasti == -1 && !PyErr_Occurred() @@ -86,8 +85,10 @@ PyObject *result; if (gen->gi_running) { - PyErr_SetString(PyExc_ValueError, - "generator already executing"); + char *msg = "generator already executing"; + if PyCoro_CheckExact(gen) + msg = "coroutine already executing"; + PyErr_SetString(PyExc_ValueError, msg); return NULL; } if (f == NULL || f->f_stacktop == NULL) { @@ -99,9 +100,12 @@ if (f->f_lasti == -1) { if (arg && arg != Py_None) { - PyErr_SetString(PyExc_TypeError, - "can't send non-None value to a " - "just-started generator"); + char *msg = "can't send non-None value to a " + "just-started generator"; + if PyCoro_CheckExact(gen) + msg = "can't send non-None value to a " + "just-started coroutine"; + PyErr_SetString(PyExc_TypeError, msg); return NULL; } } else { @@ -150,6 +154,9 @@ if (((PyCodeObject *)gen->gi_code)->co_flags & (CO_FUTURE_GENERATOR_STOP | CO_COROUTINE | CO_ITERABLE_COROUTINE)) { + char *msg = "generator raised StopIteration"; + if PyCoro_CheckExact(gen) + msg = "coroutine raised StopIteration"; PyObject *exc, *val, *val2, *tb; PyErr_Fetch(&exc, &val, &tb); PyErr_NormalizeException(&exc, &val, &tb); @@ -157,8 +164,7 @@ PyException_SetTraceback(val, tb); Py_DECREF(exc); Py_XDECREF(tb); - PyErr_SetString(PyExc_RuntimeError, - "generator raised StopIteration"); + PyErr_SetString(PyExc_RuntimeError, msg); PyErr_Fetch(&exc, &val2, &tb); PyErr_NormalizeException(&exc, &val2, &tb); Py_INCREF(val); @@ -288,9 +294,11 @@ PyErr_SetNone(PyExc_GeneratorExit); retval = gen_send_ex(gen, Py_None, 1); if (retval) { + char *msg = "generator ignored GeneratorExit"; + if (PyCoro_CheckExact(gen)) + msg = "coroutine ignored GeneratorExit"; Py_DECREF(retval); - PyErr_SetString(PyExc_RuntimeError, - "generator ignored GeneratorExit"); + PyErr_SetString(PyExc_RuntimeError, msg); return NULL; } if (PyErr_ExceptionMatches(PyExc_StopIteration) @@ -432,12 +440,6 @@ static PyObject * gen_iternext(PyGenObject *gen) { - if (((PyCodeObject*)gen->gi_code)->co_flags & CO_COROUTINE) { - PyErr_SetString(PyExc_TypeError, - "coroutine-objects do not support iteration"); - return NULL; - } - return gen_send_ex(gen, NULL, 0); } @@ -494,14 +496,8 @@ static PyObject * gen_repr(PyGenObject *gen) { - if (PyGen_CheckCoroutineExact(gen)) { - return PyUnicode_FromFormat("", - gen->gi_qualname, gen); - } - else { - return PyUnicode_FromFormat("", - gen->gi_qualname, gen); - } + return PyUnicode_FromFormat("", + gen->gi_qualname, gen); } static PyObject * @@ -537,19 +533,6 @@ return op->gi_qualname; } -static PyObject * -gen_get_iter(PyGenObject *gen) -{ - if (((PyCodeObject*)gen->gi_code)->co_flags & CO_COROUTINE) { - PyErr_SetString(PyExc_TypeError, - "coroutine-objects do not support iteration"); - return NULL; - } - - Py_INCREF(gen); - return (PyObject *)gen; -} - static int gen_set_qualname(PyGenObject *op, PyObject *value) { @@ -619,7 +602,7 @@ 0, /* tp_clear */ 0, /* tp_richcompare */ offsetof(PyGenObject, gi_weakreflist), /* tp_weaklistoffset */ - (getiterfunc)gen_get_iter, /* tp_iter */ + PyObject_SelfIter, /* tp_iter */ (iternextfunc)gen_iternext, /* tp_iternext */ gen_methods, /* tp_methods */ gen_memberlist, /* tp_members */ @@ -645,10 +628,11 @@ _PyGen_Finalize, /* tp_finalize */ }; -PyObject * -PyGen_NewWithQualName(PyFrameObject *f, PyObject *name, PyObject *qualname) +static PyObject * +gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f, + PyObject *name, PyObject *qualname) { - PyGenObject *gen = PyObject_GC_New(PyGenObject, &PyGen_Type); + PyGenObject *gen = PyObject_GC_New(PyGenObject, type); if (gen == NULL) { Py_DECREF(f); return NULL; @@ -674,9 +658,15 @@ } PyObject * +PyGen_NewWithQualName(PyFrameObject *f, PyObject *name, PyObject *qualname) +{ + return gen_new_with_qualname(&PyGen_Type, f, name, qualname); +} + +PyObject * PyGen_New(PyFrameObject *f) { - return PyGen_NewWithQualName(f, NULL, NULL); + return gen_new_with_qualname(&PyGen_Type, f, NULL, NULL); } int @@ -697,6 +687,25 @@ return 0; } +/* Coroutine Object */ + +typedef struct { + PyObject_HEAD + PyCoroObject *cw_coroutine; +} PyCoroWrapper; + +static int +gen_is_coroutine(PyObject *o) +{ + if (PyGen_CheckExact(o)) { + PyCodeObject *code = (PyCodeObject *)((PyGenObject*)o)->gi_code; + if (code->co_flags & CO_ITERABLE_COROUTINE) { + return 1; + } + } + return 0; +} + /* * This helper function returns an awaitable for `o`: * - `o` if `o` is a coroutine-object; @@ -706,13 +715,13 @@ * an awaitable and returns NULL. */ PyObject * -_PyGen_GetAwaitableIter(PyObject *o) +_PyCoro_GetAwaitableIter(PyObject *o) { unaryfunc getter = NULL; PyTypeObject *ot; - if (PyGen_CheckCoroutineExact(o)) { - /* Fast path. It's a central function for 'await'. */ + if (PyCoro_CheckExact(o) || gen_is_coroutine(o)) { + /* 'o' is a coroutine. */ Py_INCREF(o); return o; } @@ -724,22 +733,19 @@ if (getter != NULL) { PyObject *res = (*getter)(o); if (res != NULL) { - if (!PyIter_Check(res)) { + if (PyCoro_CheckExact(res) || gen_is_coroutine(res)) { + /* __await__ must return an *iterator*, not + a coroutine or another awaitable (see PEP 492) */ + PyErr_SetString(PyExc_TypeError, + "__await__() returned a coroutine"); + Py_CLEAR(res); + } else if (!PyIter_Check(res)) { PyErr_Format(PyExc_TypeError, "__await__() returned non-iterator " "of type '%.100s'", Py_TYPE(res)->tp_name); Py_CLEAR(res); } - else { - if (PyGen_CheckCoroutineExact(res)) { - /* __await__ must return an *iterator*, not - a coroutine or another awaitable (see PEP 492) */ - PyErr_SetString(PyExc_TypeError, - "__await__() returned a coroutine"); - Py_CLEAR(res); - } - } } return res; } @@ -747,6 +753,211 @@ PyErr_Format(PyExc_TypeError, "object %.100s can't be used in 'await' expression", ot->tp_name); - return NULL; } + +static PyObject * +coro_repr(PyCoroObject *coro) +{ + return PyUnicode_FromFormat("", + coro->cr_qualname, coro); +} + +static PyObject * +coro_await(PyCoroObject *coro) +{ + PyCoroWrapper *cw = PyObject_GC_New(PyCoroWrapper, &_PyCoroWrapper_Type); + if (cw == NULL) { + return NULL; + } + Py_INCREF(coro); + cw->cw_coroutine = coro; + _PyObject_GC_TRACK(cw); + return (PyObject *)cw; +} + +static PyGetSetDef coro_getsetlist[] = { + {"__name__", (getter)gen_get_name, (setter)gen_set_name, + PyDoc_STR("name of the coroutine")}, + {"__qualname__", (getter)gen_get_qualname, (setter)gen_set_qualname, + PyDoc_STR("qualified name of the coroutine")}, + {NULL} /* Sentinel */ +}; + +static PyMemberDef coro_memberlist[] = { + {"cr_frame", T_OBJECT, offsetof(PyCoroObject, cr_frame), READONLY}, + {"cr_running", T_BOOL, offsetof(PyCoroObject, cr_running), READONLY}, + {"cr_code", T_OBJECT, offsetof(PyCoroObject, cr_code), READONLY}, + {NULL} /* Sentinel */ +}; + +PyDoc_STRVAR(coro_send_doc, +"send(arg) -> send 'arg' into coroutine,\n\ +return next yielded value or raise StopIteration."); + +PyDoc_STRVAR(coro_throw_doc, +"throw(typ[,val[,tb]]) -> raise exception in coroutine,\n\ +return next yielded value or raise StopIteration."); + +PyDoc_STRVAR(coro_close_doc, +"close() -> raise GeneratorExit inside coroutine."); + +static PyMethodDef coro_methods[] = { + {"send",(PyCFunction)_PyGen_Send, METH_O, coro_send_doc}, + {"throw",(PyCFunction)gen_throw, METH_VARARGS, coro_throw_doc}, + {"close",(PyCFunction)gen_close, METH_NOARGS, coro_close_doc}, + {NULL, NULL} /* Sentinel */ +}; + +static PyAsyncMethods coro_as_async = { + (unaryfunc)coro_await, /* am_await */ + 0, /* am_aiter */ + 0 /* am_anext */ +}; + +PyTypeObject PyCoro_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "coroutine", /* tp_name */ + sizeof(PyCoroObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)gen_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + &coro_as_async, /* tp_as_async */ + (reprfunc)coro_repr, /* 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 | + Py_TPFLAGS_HAVE_FINALIZE, /* tp_flags */ + 0, /* tp_doc */ + (traverseproc)gen_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + offsetof(PyCoroObject, cr_weakreflist), /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + coro_methods, /* tp_methods */ + coro_memberlist, /* tp_members */ + coro_getsetlist, /* 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 */ + 0, /* tp_free */ + 0, /* tp_is_gc */ + 0, /* tp_bases */ + 0, /* tp_mro */ + 0, /* tp_cache */ + 0, /* tp_subclasses */ + 0, /* tp_weaklist */ + 0, /* tp_del */ + 0, /* tp_version_tag */ + _PyGen_Finalize, /* tp_finalize */ +}; + +static void +coro_wrapper_dealloc(PyCoroWrapper *cw) +{ + _PyObject_GC_UNTRACK((PyObject *)cw); + Py_CLEAR(cw->cw_coroutine); + PyObject_GC_Del(cw); +} + +static PyObject * +coro_wrapper_iternext(PyCoroWrapper *cw) +{ + return gen_send_ex((PyGenObject *)cw->cw_coroutine, NULL, 0); +} + +static PyObject * +coro_wrapper_send(PyCoroWrapper *cw, PyObject *arg) +{ + return gen_send_ex((PyGenObject *)cw->cw_coroutine, arg, 0); +} + +static PyObject * +coro_wrapper_throw(PyCoroWrapper *cw, PyObject *args) +{ + return gen_throw((PyGenObject *)cw->cw_coroutine, args); +} + +static PyObject * +coro_wrapper_close(PyCoroWrapper *cw, PyObject *args) +{ + return gen_close((PyGenObject *)cw->cw_coroutine, args); +} + +static int +coro_wrapper_traverse(PyCoroWrapper *cw, visitproc visit, void *arg) +{ + Py_VISIT((PyObject *)cw->cw_coroutine); + return 0; +} + +static PyMethodDef coro_wrapper_methods[] = { + {"send",(PyCFunction)coro_wrapper_send, METH_O, send_doc}, + {"throw",(PyCFunction)coro_wrapper_throw, METH_VARARGS, throw_doc}, + {"close",(PyCFunction)coro_wrapper_close, METH_NOARGS, close_doc}, + {NULL, NULL} /* Sentinel */ +}; + +PyTypeObject _PyCoroWrapper_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "coroutine_wrapper", + sizeof(PyCoroWrapper), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)coro_wrapper_dealloc, /* destructor tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* 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 */ + "A wrapper object implementing __await__ for coroutines.", + (traverseproc)coro_wrapper_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + (iternextfunc)coro_wrapper_iternext, /* tp_iternext */ + coro_wrapper_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_Del, /* tp_free */ +}; + +PyObject * +PyCoro_New(PyFrameObject *f, PyObject *name, PyObject *qualname) +{ + return gen_new_with_qualname(&PyCoro_Type, f, name, qualname); +} diff -r af793c7580f1 Objects/object.c --- a/Objects/object.c Wed Jun 17 10:09:24 2015 -0500 +++ b/Objects/object.c Fri Jun 19 12:23:55 2015 -0400 @@ -1726,6 +1726,12 @@ if (PyType_Ready(&PySeqIter_Type) < 0) Py_FatalError("Can't initialize sequence iterator type"); + + if (PyType_Ready(&PyCoro_Type) < 0) + Py_FatalError("Can't initialize coroutine type"); + + if (PyType_Ready(&_PyCoroWrapper_Type) < 0) + Py_FatalError("Can't initialize coroutine wrapper type"); } diff -r af793c7580f1 Python/ceval.c --- a/Python/ceval.c Wed Jun 17 10:09:24 2015 -0500 +++ b/Python/ceval.c Fri Jun 19 12:23:55 2015 -0400 @@ -1191,7 +1191,7 @@ f->f_stacktop = NULL; /* remains NULL unless yield suspends frame */ f->f_executing = 1; - if (co->co_flags & CO_GENERATOR) { + if (co->co_flags & (CO_GENERATOR | CO_COROUTINE)) { if (!throwflag && f->f_exc_type != NULL && f->f_exc_type != Py_None) { /* We were in an except handler when we left, restore the exception state which was put aside @@ -1955,7 +1955,7 @@ goto error; } - awaitable = _PyGen_GetAwaitableIter(iter); + awaitable = _PyCoro_GetAwaitableIter(iter); if (awaitable == NULL) { SET_TOP(NULL); PyErr_Format( @@ -1998,7 +1998,7 @@ goto error; } - awaitable = _PyGen_GetAwaitableIter(next_iter); + awaitable = _PyCoro_GetAwaitableIter(next_iter); if (awaitable == NULL) { PyErr_Format( PyExc_TypeError, @@ -2017,7 +2017,7 @@ TARGET(GET_AWAITABLE) { PyObject *iterable = TOP(); - PyObject *iter = _PyGen_GetAwaitableIter(iterable); + PyObject *iter = _PyCoro_GetAwaitableIter(iterable); Py_DECREF(iterable); @@ -2034,25 +2034,7 @@ PyObject *v = POP(); PyObject *reciever = TOP(); int err; - if (PyGen_CheckExact(reciever)) { - if ( - (((PyCodeObject*) \ - ((PyGenObject*)reciever)->gi_code)->co_flags & - CO_COROUTINE) - && !(co->co_flags & (CO_COROUTINE | CO_ITERABLE_COROUTINE))) - { - /* If we're yielding-from a coroutine object from a regular - generator object - raise an error. */ - - Py_CLEAR(v); - Py_CLEAR(reciever); - SET_TOP(NULL); - - PyErr_SetString(PyExc_TypeError, - "cannot 'yield from' a coroutine object " - "from a generator"); - goto error; - } + if (PyGen_CheckExact(reciever) || PyCoro_CheckExact(reciever)) { retval = _PyGen_Send((PyGenObject *)reciever, v); } else { _Py_IDENTIFIER(send); @@ -2929,19 +2911,33 @@ TARGET(GET_ITER) { /* before: [obj]; after [getiter(obj)] */ PyObject *iterable = TOP(); + PyObject *iter = PyObject_GetIter(iterable); + Py_DECREF(iterable); + SET_TOP(iter); + if (iter == NULL) + goto error; + PREDICT(FOR_ITER); + DISPATCH(); + } + + TARGET(GET_YIELD_FROM_ITER) { + /* before: [obj]; after [getiter(obj)] */ + PyObject *iterable = TOP(); PyObject *iter; - /* If we have a generator object on top -- keep it there, - it's already an iterator. - - This is needed to allow use of 'async def' coroutines - in 'yield from' expression from generator-based coroutines - (decorated with types.coroutine()). - - 'yield from' is compiled to GET_ITER..YIELD_FROM combination, - but since coroutines raise TypeError in their 'tp_iter' we - need a way for them to "pass through" the GET_ITER. - */ - if (!PyGen_CheckExact(iterable)) { + if (PyCoro_CheckExact(iterable)) { + /* `iterable` is a coroutine */ + if (!(co->co_flags & (CO_COROUTINE | CO_ITERABLE_COROUTINE))) { + /* and it is used in a 'yield from' expression of a + regular generator. */ + Py_DECREF(iterable); + SET_TOP(NULL); + PyErr_SetString(PyExc_TypeError, + "cannot 'yield from' a coroutine object " + "in a non-coroutine generator"); + goto error; + } + } + else if (!PyGen_CheckExact(iterable)) { /* `iterable` is not a generator. */ iter = PyObject_GetIter(iterable); Py_DECREF(iterable); @@ -2949,7 +2945,6 @@ if (iter == NULL) goto error; } - PREDICT(FOR_ITER); DISPATCH(); } @@ -3517,7 +3512,7 @@ assert((retval != NULL) ^ (PyErr_Occurred() != NULL)); fast_yield: - if (co->co_flags & CO_GENERATOR) { + if (co->co_flags & (CO_GENERATOR | CO_COROUTINE)) { /* The purpose of this block is to put aside the generator's exception state and restore that of the calling frame. If the current @@ -3919,10 +3914,10 @@ freevars[PyTuple_GET_SIZE(co->co_cellvars) + i] = o; } - if (co->co_flags & CO_GENERATOR) { + if (co->co_flags & (CO_GENERATOR | CO_COROUTINE)) { PyObject *gen; PyObject *coro_wrapper = tstate->coroutine_wrapper; - int is_coro = co->co_flags & (CO_COROUTINE | CO_ITERABLE_COROUTINE); + int is_coro = co->co_flags & CO_COROUTINE; if (is_coro && tstate->in_coroutine_wrapper) { assert(coro_wrapper != NULL); @@ -3942,7 +3937,11 @@ /* Create a new generator that owns the ready to run frame * and return that as the value. */ - gen = PyGen_NewWithQualName(f, name, qualname); + if (is_coro) { + gen = PyCoro_New(f, name, qualname); + } else { + gen = PyGen_NewWithQualName(f, name, qualname); + } if (gen == NULL) return NULL; diff -r af793c7580f1 Python/compile.c --- a/Python/compile.c Wed Jun 17 10:09:24 2015 -0500 +++ b/Python/compile.c Fri Jun 19 12:23:55 2015 -0400 @@ -1064,6 +1064,8 @@ return 0; case GET_ANEXT: return 1; + case GET_YIELD_FROM_ITER: + return 0; default: return PY_INVALID_STACK_EFFECT; } @@ -1751,12 +1753,8 @@ Py_DECREF(qualname); Py_DECREF(co); - if (is_async) { + if (is_async) co->co_flags |= CO_COROUTINE; - /* An async function is always a generator, even - if there is no 'yield' expressions in it. */ - co->co_flags |= CO_GENERATOR; - } /* decorators */ for (i = 0; i < asdl_seq_LEN(decos); i++) { @@ -3850,7 +3848,7 @@ return compiler_error(c, "'yield from' inside async function"); VISIT(c, expr, e->v.YieldFrom.value); - ADDOP(c, GET_ITER); + ADDOP(c, GET_YIELD_FROM_ITER); ADDOP_O(c, LOAD_CONST, Py_None, consts); ADDOP(c, YIELD_FROM); break; diff -r af793c7580f1 Python/opcode_targets.h --- a/Python/opcode_targets.h Wed Jun 17 10:09:24 2015 -0500 +++ b/Python/opcode_targets.h Fri Jun 19 12:23:55 2015 -0400 @@ -68,7 +68,7 @@ &&TARGET_BINARY_OR, &&TARGET_INPLACE_POWER, &&TARGET_GET_ITER, - &&_unknown_opcode, + &&TARGET_GET_YIELD_FROM_ITER, &&TARGET_PRINT_EXPR, &&TARGET_LOAD_BUILD_CLASS, &&TARGET_YIELD_FROM,