Commit eb636455 authored by Yury Selivanov's avatar Yury Selivanov

Issue #28003: Implement PEP 525 -- Asynchronous Generators.

parent b96ef55d
...@@ -25,6 +25,10 @@ PyAPI_FUNC(void) PyEval_SetProfile(Py_tracefunc, PyObject *); ...@@ -25,6 +25,10 @@ PyAPI_FUNC(void) PyEval_SetProfile(Py_tracefunc, PyObject *);
PyAPI_FUNC(void) PyEval_SetTrace(Py_tracefunc, PyObject *); PyAPI_FUNC(void) PyEval_SetTrace(Py_tracefunc, PyObject *);
PyAPI_FUNC(void) _PyEval_SetCoroutineWrapper(PyObject *); PyAPI_FUNC(void) _PyEval_SetCoroutineWrapper(PyObject *);
PyAPI_FUNC(PyObject *) _PyEval_GetCoroutineWrapper(void); PyAPI_FUNC(PyObject *) _PyEval_GetCoroutineWrapper(void);
PyAPI_FUNC(void) _PyEval_SetAsyncGenFirstiter(PyObject *);
PyAPI_FUNC(PyObject *) _PyEval_GetAsyncGenFirstiter(void);
PyAPI_FUNC(void) _PyEval_SetAsyncGenFinalizer(PyObject *);
PyAPI_FUNC(PyObject *) _PyEval_GetAsyncGenFinalizer(void);
#endif #endif
struct _frame; /* Avoid including frameobject.h */ struct _frame; /* Avoid including frameobject.h */
......
...@@ -59,6 +59,7 @@ typedef struct { ...@@ -59,6 +59,7 @@ typedef struct {
``async def`` keywords) */ ``async def`` keywords) */
#define CO_COROUTINE 0x0080 #define CO_COROUTINE 0x0080
#define CO_ITERABLE_COROUTINE 0x0100 #define CO_ITERABLE_COROUTINE 0x0100
#define CO_ASYNC_GENERATOR 0x0200
/* These are no longer used. */ /* These are no longer used. */
#if 0 #if 0
......
...@@ -61,6 +61,37 @@ PyObject *_PyAIterWrapper_New(PyObject *aiter); ...@@ -61,6 +61,37 @@ PyObject *_PyAIterWrapper_New(PyObject *aiter);
PyObject *_PyCoro_GetAwaitableIter(PyObject *o); PyObject *_PyCoro_GetAwaitableIter(PyObject *o);
PyAPI_FUNC(PyObject *) PyCoro_New(struct _frame *, PyAPI_FUNC(PyObject *) PyCoro_New(struct _frame *,
PyObject *name, PyObject *qualname); PyObject *name, PyObject *qualname);
/* Asynchronous Generators */
typedef struct {
_PyGenObject_HEAD(ag)
PyObject *ag_finalizer;
/* Flag is set to 1 when hooks set up by sys.set_asyncgen_hooks
were called on the generator, to avoid calling them more
than once. */
int ag_hooks_inited;
/* Flag is set to 1 when aclose() is called for the first time, or
when a StopAsyncIteration exception is raised. */
int ag_closed;
} PyAsyncGenObject;
PyAPI_DATA(PyTypeObject) PyAsyncGen_Type;
PyAPI_DATA(PyTypeObject) _PyAsyncGenASend_Type;
PyAPI_DATA(PyTypeObject) _PyAsyncGenWrappedValue_Type;
PyAPI_DATA(PyTypeObject) _PyAsyncGenAThrow_Type;
PyAPI_FUNC(PyObject *) PyAsyncGen_New(struct _frame *,
PyObject *name, PyObject *qualname);
#define PyAsyncGen_CheckExact(op) (Py_TYPE(op) == &PyAsyncGen_Type)
PyObject *_PyAsyncGenValueWrapperNew(PyObject *);
int PyAsyncGen_ClearFreeLists(void);
#endif #endif
#undef _PyGenObject_HEAD #undef _PyGenObject_HEAD
......
...@@ -107,6 +107,7 @@ PyAPI_FUNC(void) _PyGC_Fini(void); ...@@ -107,6 +107,7 @@ PyAPI_FUNC(void) _PyGC_Fini(void);
PyAPI_FUNC(void) PySlice_Fini(void); PyAPI_FUNC(void) PySlice_Fini(void);
PyAPI_FUNC(void) _PyType_Fini(void); PyAPI_FUNC(void) _PyType_Fini(void);
PyAPI_FUNC(void) _PyRandom_Fini(void); PyAPI_FUNC(void) _PyRandom_Fini(void);
PyAPI_FUNC(void) PyAsyncGen_Fini(void);
PyAPI_DATA(PyThreadState *) _Py_Finalizing; PyAPI_DATA(PyThreadState *) _Py_Finalizing;
#endif #endif
......
...@@ -148,6 +148,9 @@ typedef struct _ts { ...@@ -148,6 +148,9 @@ typedef struct _ts {
Py_ssize_t co_extra_user_count; Py_ssize_t co_extra_user_count;
freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS]; freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];
PyObject *async_gen_firstiter;
PyObject *async_gen_finalizer;
/* XXX signal handlers should also be here */ /* XXX signal handlers should also be here */
} PyThreadState; } PyThreadState;
......
...@@ -48,6 +48,7 @@ typedef struct _symtable_entry { ...@@ -48,6 +48,7 @@ typedef struct _symtable_entry {
unsigned ste_child_free : 1; /* true if a child block has free vars, unsigned ste_child_free : 1; /* true if a child block has free vars,
including free refs to globals */ including free refs to globals */
unsigned ste_generator : 1; /* true if namespace is a generator */ unsigned ste_generator : 1; /* true if namespace is a generator */
unsigned ste_coroutine : 1; /* true if namespace is a coroutine */
unsigned ste_varargs : 1; /* true if block has varargs */ unsigned ste_varargs : 1; /* true if block has varargs */
unsigned ste_varkeywords : 1; /* true if block has varkeywords */ unsigned ste_varkeywords : 1; /* true if block has varkeywords */
unsigned ste_returns_value : 1; /* true if namespace uses return with unsigned ste_returns_value : 1; /* true if namespace uses return with
......
...@@ -13,7 +13,6 @@ conscious design decision, leaving the door open for keyword arguments ...@@ -13,7 +13,6 @@ conscious design decision, leaving the door open for keyword arguments
to modify the meaning of the API call itself. to modify the meaning of the API call itself.
""" """
import collections import collections
import concurrent.futures import concurrent.futures
import heapq import heapq
...@@ -28,6 +27,7 @@ import time ...@@ -28,6 +27,7 @@ import time
import traceback import traceback
import sys import sys
import warnings import warnings
import weakref
from . import compat from . import compat
from . import coroutines from . import coroutines
...@@ -242,6 +242,13 @@ class BaseEventLoop(events.AbstractEventLoop): ...@@ -242,6 +242,13 @@ class BaseEventLoop(events.AbstractEventLoop):
self._task_factory = None self._task_factory = None
self._coroutine_wrapper_set = False self._coroutine_wrapper_set = False
# A weak set of all asynchronous generators that are being iterated
# by the loop.
self._asyncgens = weakref.WeakSet()
# Set to True when `loop.shutdown_asyncgens` is called.
self._asyncgens_shutdown_called = False
def __repr__(self): def __repr__(self):
return ('<%s running=%s closed=%s debug=%s>' return ('<%s running=%s closed=%s debug=%s>'
% (self.__class__.__name__, self.is_running(), % (self.__class__.__name__, self.is_running(),
...@@ -333,6 +340,46 @@ class BaseEventLoop(events.AbstractEventLoop): ...@@ -333,6 +340,46 @@ class BaseEventLoop(events.AbstractEventLoop):
if self._closed: if self._closed:
raise RuntimeError('Event loop is closed') raise RuntimeError('Event loop is closed')
def _asyncgen_finalizer_hook(self, agen):
self._asyncgens.discard(agen)
if not self.is_closed():
self.create_task(agen.aclose())
def _asyncgen_firstiter_hook(self, agen):
if self._asyncgens_shutdown_called:
warnings.warn(
"asynchronous generator {!r} was scheduled after "
"loop.shutdown_asyncgens() call".format(agen),
ResourceWarning, source=self)
self._asyncgens.add(agen)
@coroutine
def shutdown_asyncgens(self):
"""Shutdown all active asynchronous generators."""
self._asyncgens_shutdown_called = True
if not len(self._asyncgens):
return
closing_agens = list(self._asyncgens)
self._asyncgens.clear()
shutdown_coro = tasks.gather(
*[ag.aclose() for ag in closing_agens],
return_exceptions=True,
loop=self)
results = yield from shutdown_coro
for result, agen in zip(results, closing_agens):
if isinstance(result, Exception):
self.call_exception_handler({
'message': 'an error occurred during closing of '
'asynchronous generator {!r}'.format(agen),
'exception': result,
'asyncgen': agen
})
def run_forever(self): def run_forever(self):
"""Run until stop() is called.""" """Run until stop() is called."""
self._check_closed() self._check_closed()
...@@ -340,6 +387,9 @@ class BaseEventLoop(events.AbstractEventLoop): ...@@ -340,6 +387,9 @@ class BaseEventLoop(events.AbstractEventLoop):
raise RuntimeError('Event loop is running.') raise RuntimeError('Event loop is running.')
self._set_coroutine_wrapper(self._debug) self._set_coroutine_wrapper(self._debug)
self._thread_id = threading.get_ident() self._thread_id = threading.get_ident()
old_agen_hooks = sys.get_asyncgen_hooks()
sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook,
finalizer=self._asyncgen_finalizer_hook)
try: try:
while True: while True:
self._run_once() self._run_once()
...@@ -349,6 +399,7 @@ class BaseEventLoop(events.AbstractEventLoop): ...@@ -349,6 +399,7 @@ class BaseEventLoop(events.AbstractEventLoop):
self._stopping = False self._stopping = False
self._thread_id = None self._thread_id = None
self._set_coroutine_wrapper(False) self._set_coroutine_wrapper(False)
sys.set_asyncgen_hooks(*old_agen_hooks)
def run_until_complete(self, future): def run_until_complete(self, future):
"""Run until the Future is done. """Run until the Future is done.
...@@ -1179,7 +1230,9 @@ class BaseEventLoop(events.AbstractEventLoop): ...@@ -1179,7 +1230,9 @@ class BaseEventLoop(events.AbstractEventLoop):
- 'handle' (optional): Handle instance; - 'handle' (optional): Handle instance;
- 'protocol' (optional): Protocol instance; - 'protocol' (optional): Protocol instance;
- 'transport' (optional): Transport instance; - 'transport' (optional): Transport instance;
- 'socket' (optional): Socket instance. - 'socket' (optional): Socket instance;
- 'asyncgen' (optional): Asynchronous generator that caused
the exception.
New keys maybe introduced in the future. New keys maybe introduced in the future.
......
...@@ -276,7 +276,10 @@ def _format_coroutine(coro): ...@@ -276,7 +276,10 @@ def _format_coroutine(coro):
try: try:
coro_code = coro.gi_code coro_code = coro.gi_code
except AttributeError: except AttributeError:
coro_code = coro.cr_code try:
coro_code = coro.cr_code
except AttributeError:
return repr(coro)
try: try:
coro_frame = coro.gi_frame coro_frame = coro.gi_frame
......
...@@ -248,6 +248,10 @@ class AbstractEventLoop: ...@@ -248,6 +248,10 @@ class AbstractEventLoop:
""" """
raise NotImplementedError raise NotImplementedError
def shutdown_asyncgens(self):
"""Shutdown all active asynchronous generators."""
raise NotImplementedError
# Methods scheduling callbacks. All these return Handles. # Methods scheduling callbacks. All these return Handles.
def _timer_handle_cancelled(self, handle): def _timer_handle_cancelled(self, handle):
......
...@@ -87,6 +87,7 @@ COMPILER_FLAG_NAMES = { ...@@ -87,6 +87,7 @@ COMPILER_FLAG_NAMES = {
64: "NOFREE", 64: "NOFREE",
128: "COROUTINE", 128: "COROUTINE",
256: "ITERABLE_COROUTINE", 256: "ITERABLE_COROUTINE",
512: "ASYNC_GENERATOR",
} }
def pretty_flags(flags): def pretty_flags(flags):
......
...@@ -185,6 +185,13 @@ def iscoroutinefunction(object): ...@@ -185,6 +185,13 @@ def iscoroutinefunction(object):
return bool((isfunction(object) or ismethod(object)) and return bool((isfunction(object) or ismethod(object)) and
object.__code__.co_flags & CO_COROUTINE) object.__code__.co_flags & CO_COROUTINE)
def isasyncgenfunction(object):
return bool((isfunction(object) or ismethod(object)) and
object.__code__.co_flags & CO_ASYNC_GENERATOR)
def isasyncgen(object):
return isinstance(object, types.AsyncGeneratorType)
def isgenerator(object): def isgenerator(object):
"""Return true if the object is a generator. """Return true if the object is a generator.
......
This diff is collapsed.
...@@ -88,12 +88,6 @@ class AsyncBadSyntaxTest(unittest.TestCase): ...@@ -88,12 +88,6 @@ class AsyncBadSyntaxTest(unittest.TestCase):
with self.assertRaisesRegex(SyntaxError, 'invalid syntax'): with self.assertRaisesRegex(SyntaxError, 'invalid syntax'):
import test.badsyntax_async5 import test.badsyntax_async5
def test_badsyntax_6(self):
with self.assertRaisesRegex(
SyntaxError, "'yield' inside async function"):
import test.badsyntax_async6
def test_badsyntax_7(self): def test_badsyntax_7(self):
with self.assertRaisesRegex( with self.assertRaisesRegex(
SyntaxError, "'yield from' inside async function"): SyntaxError, "'yield from' inside async function"):
......
...@@ -574,7 +574,7 @@ Argument count: 0 ...@@ -574,7 +574,7 @@ Argument count: 0
Kw-only arguments: 0 Kw-only arguments: 0
Number of locals: 2 Number of locals: 2
Stack size: 17 Stack size: 17
Flags: OPTIMIZED, NEWLOCALS, GENERATOR, NOFREE, COROUTINE Flags: OPTIMIZED, NEWLOCALS, NOFREE, COROUTINE
Constants: Constants:
0: None 0: None
1: 1""" 1: 1"""
......
...@@ -65,7 +65,8 @@ class IsTestBase(unittest.TestCase): ...@@ -65,7 +65,8 @@ class IsTestBase(unittest.TestCase):
inspect.isframe, inspect.isfunction, inspect.ismethod, inspect.isframe, inspect.isfunction, inspect.ismethod,
inspect.ismodule, inspect.istraceback, inspect.ismodule, inspect.istraceback,
inspect.isgenerator, inspect.isgeneratorfunction, inspect.isgenerator, inspect.isgeneratorfunction,
inspect.iscoroutine, inspect.iscoroutinefunction]) inspect.iscoroutine, inspect.iscoroutinefunction,
inspect.isasyncgen, inspect.isasyncgenfunction])
def istest(self, predicate, exp): def istest(self, predicate, exp):
obj = eval(exp) obj = eval(exp)
...@@ -73,6 +74,7 @@ class IsTestBase(unittest.TestCase): ...@@ -73,6 +74,7 @@ class IsTestBase(unittest.TestCase):
for other in self.predicates - set([predicate]): for other in self.predicates - set([predicate]):
if (predicate == inspect.isgeneratorfunction or \ if (predicate == inspect.isgeneratorfunction or \
predicate == inspect.isasyncgenfunction or \
predicate == inspect.iscoroutinefunction) and \ predicate == inspect.iscoroutinefunction) and \
other == inspect.isfunction: other == inspect.isfunction:
continue continue
...@@ -82,6 +84,10 @@ def generator_function_example(self): ...@@ -82,6 +84,10 @@ def generator_function_example(self):
for i in range(2): for i in range(2):
yield i yield i
async def async_generator_function_example(self):
async for i in range(2):
yield i
async def coroutine_function_example(self): async def coroutine_function_example(self):
return 'spam' return 'spam'
...@@ -122,6 +128,10 @@ class TestPredicates(IsTestBase): ...@@ -122,6 +128,10 @@ class TestPredicates(IsTestBase):
self.istest(inspect.isdatadescriptor, 'collections.defaultdict.default_factory') self.istest(inspect.isdatadescriptor, 'collections.defaultdict.default_factory')
self.istest(inspect.isgenerator, '(x for x in range(2))') self.istest(inspect.isgenerator, '(x for x in range(2))')
self.istest(inspect.isgeneratorfunction, 'generator_function_example') self.istest(inspect.isgeneratorfunction, 'generator_function_example')
self.istest(inspect.isasyncgen,
'async_generator_function_example(1)')
self.istest(inspect.isasyncgenfunction,
'async_generator_function_example')
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.simplefilter("ignore") warnings.simplefilter("ignore")
......
...@@ -1192,6 +1192,32 @@ class SizeofTest(unittest.TestCase): ...@@ -1192,6 +1192,32 @@ class SizeofTest(unittest.TestCase):
# sys.flags # sys.flags
check(sys.flags, vsize('') + self.P * len(sys.flags)) check(sys.flags, vsize('') + self.P * len(sys.flags))
def test_asyncgen_hooks(self):
old = sys.get_asyncgen_hooks()
self.assertIsNone(old.firstiter)
self.assertIsNone(old.finalizer)
firstiter = lambda *a: None
sys.set_asyncgen_hooks(firstiter=firstiter)
hooks = sys.get_asyncgen_hooks()
self.assertIs(hooks.firstiter, firstiter)
self.assertIs(hooks[0], firstiter)
self.assertIs(hooks.finalizer, None)
self.assertIs(hooks[1], None)
finalizer = lambda *a: None
sys.set_asyncgen_hooks(finalizer=finalizer)
hooks = sys.get_asyncgen_hooks()
self.assertIs(hooks.firstiter, firstiter)
self.assertIs(hooks[0], firstiter)
self.assertIs(hooks.finalizer, finalizer)
self.assertIs(hooks[1], finalizer)
sys.set_asyncgen_hooks(*old)
cur = sys.get_asyncgen_hooks()
self.assertIsNone(cur.firstiter)
self.assertIsNone(cur.finalizer)
def test_main(): def test_main():
test.support.run_unittest(SysModuleTest, SizeofTest) test.support.run_unittest(SysModuleTest, SizeofTest)
......
...@@ -24,6 +24,11 @@ _c = _c() ...@@ -24,6 +24,11 @@ _c = _c()
CoroutineType = type(_c) CoroutineType = type(_c)
_c.close() # Prevent ResourceWarning _c.close() # Prevent ResourceWarning
async def _ag():
yield
_ag = _ag()
AsyncGeneratorType = type(_ag)
class _C: class _C:
def _m(self): pass def _m(self): pass
MethodType = type(_C()._m) MethodType = type(_C()._m)
......
...@@ -103,6 +103,9 @@ Core and Builtins ...@@ -103,6 +103,9 @@ Core and Builtins
- Issue #27985: Implement PEP 526 -- Syntax for Variable Annotations. - Issue #27985: Implement PEP 526 -- Syntax for Variable Annotations.
Patch by Ivan Levkivskyi. Patch by Ivan Levkivskyi.
- Issue #28003: Implement PEP 525 -- Asynchronous Generators.
Library Library
------- -------
......
...@@ -892,6 +892,7 @@ clear_freelists(void) ...@@ -892,6 +892,7 @@ clear_freelists(void)
(void)PyList_ClearFreeList(); (void)PyList_ClearFreeList();
(void)PyDict_ClearFreeList(); (void)PyDict_ClearFreeList();
(void)PySet_ClearFreeList(); (void)PySet_ClearFreeList();
(void)PyAsyncGen_ClearFreeLists();
} }
/* This is the main function. Read this to understand how the /* This is the main function. Read this to understand how the
......
This diff is collapsed.
...@@ -1204,7 +1204,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) ...@@ -1204,7 +1204,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
f->f_stacktop = NULL; /* remains NULL unless yield suspends frame */ f->f_stacktop = NULL; /* remains NULL unless yield suspends frame */
f->f_executing = 1; f->f_executing = 1;
if (co->co_flags & (CO_GENERATOR | CO_COROUTINE)) { if (co->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) {
if (!throwflag && f->f_exc_type != NULL && f->f_exc_type != Py_None) { if (!throwflag && f->f_exc_type != NULL && f->f_exc_type != Py_None) {
/* We were in an except handler when we left, /* We were in an except handler when we left,
restore the exception state which was put aside restore the exception state which was put aside
...@@ -2083,36 +2083,45 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) ...@@ -2083,36 +2083,45 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
PyObject *aiter = TOP(); PyObject *aiter = TOP();
PyTypeObject *type = Py_TYPE(aiter); PyTypeObject *type = Py_TYPE(aiter);
if (type->tp_as_async != NULL) if (PyAsyncGen_CheckExact(aiter)) {
getter = type->tp_as_async->am_anext; awaitable = type->tp_as_async->am_anext(aiter);
if (awaitable == NULL) {
goto error;
}
} else {
if (type->tp_as_async != NULL){
getter = type->tp_as_async->am_anext;
}
if (getter != NULL) { if (getter != NULL) {
next_iter = (*getter)(aiter); next_iter = (*getter)(aiter);
if (next_iter == NULL) { if (next_iter == NULL) {
goto error;
}
}
else {
PyErr_Format(
PyExc_TypeError,
"'async for' requires an iterator with "
"__anext__ method, got %.100s",
type->tp_name);
goto error; goto error;
} }
}
else {
PyErr_Format(
PyExc_TypeError,
"'async for' requires an iterator with "
"__anext__ method, got %.100s",
type->tp_name);
goto error;
}
awaitable = _PyCoro_GetAwaitableIter(next_iter); awaitable = _PyCoro_GetAwaitableIter(next_iter);
if (awaitable == NULL) { if (awaitable == NULL) {
PyErr_Format( PyErr_Format(
PyExc_TypeError, PyExc_TypeError,
"'async for' received an invalid object " "'async for' received an invalid object "
"from __anext__: %.100s", "from __anext__: %.100s",
Py_TYPE(next_iter)->tp_name); Py_TYPE(next_iter)->tp_name);
Py_DECREF(next_iter); Py_DECREF(next_iter);
goto error; goto error;
} else } else {
Py_DECREF(next_iter); Py_DECREF(next_iter);
}
}
PUSH(awaitable); PUSH(awaitable);
PREDICT(LOAD_CONST); PREDICT(LOAD_CONST);
...@@ -2187,6 +2196,17 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) ...@@ -2187,6 +2196,17 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
TARGET(YIELD_VALUE) { TARGET(YIELD_VALUE) {
retval = POP(); retval = POP();
if (co->co_flags & CO_ASYNC_GENERATOR) {
PyObject *w = _PyAsyncGenValueWrapperNew(retval);
Py_DECREF(retval);
if (w == NULL) {
retval = NULL;
goto error;
}
retval = w;
}
f->f_stacktop = stack_pointer; f->f_stacktop = stack_pointer;
why = WHY_YIELD; why = WHY_YIELD;
goto fast_yield; goto fast_yield;
...@@ -3712,7 +3732,7 @@ fast_block_end: ...@@ -3712,7 +3732,7 @@ fast_block_end:
assert((retval != NULL) ^ (PyErr_Occurred() != NULL)); assert((retval != NULL) ^ (PyErr_Occurred() != NULL));
fast_yield: fast_yield:
if (co->co_flags & (CO_GENERATOR | CO_COROUTINE)) { if (co->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) {
/* The purpose of this block is to put aside the generator's exception /* The purpose of this block is to put aside the generator's exception
state and restore that of the calling frame. If the current state and restore that of the calling frame. If the current
...@@ -4156,8 +4176,8 @@ _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals, ...@@ -4156,8 +4176,8 @@ _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
freevars[PyTuple_GET_SIZE(co->co_cellvars) + i] = o; freevars[PyTuple_GET_SIZE(co->co_cellvars) + i] = o;
} }
/* Handle generator/coroutine */ /* Handle generator/coroutine/asynchronous generator */
if (co->co_flags & (CO_GENERATOR | CO_COROUTINE)) { if (co->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) {
PyObject *gen; PyObject *gen;
PyObject *coro_wrapper = tstate->coroutine_wrapper; PyObject *coro_wrapper = tstate->coroutine_wrapper;
int is_coro = co->co_flags & CO_COROUTINE; int is_coro = co->co_flags & CO_COROUTINE;
...@@ -4182,6 +4202,8 @@ _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals, ...@@ -4182,6 +4202,8 @@ _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
* and return that as the value. */ * and return that as the value. */
if (is_coro) { if (is_coro) {
gen = PyCoro_New(f, name, qualname); gen = PyCoro_New(f, name, qualname);
} else if (co->co_flags & CO_ASYNC_GENERATOR) {
gen = PyAsyncGen_New(f, name, qualname);
} else { } else {
gen = PyGen_NewWithQualName(f, name, qualname); gen = PyGen_NewWithQualName(f, name, qualname);
} }
...@@ -4660,6 +4682,38 @@ _PyEval_GetCoroutineWrapper(void) ...@@ -4660,6 +4682,38 @@ _PyEval_GetCoroutineWrapper(void)
return tstate->coroutine_wrapper; return tstate->coroutine_wrapper;
} }
void
_PyEval_SetAsyncGenFirstiter(PyObject *firstiter)
{
PyThreadState *tstate = PyThreadState_GET();
Py_XINCREF(firstiter);
Py_XSETREF(tstate->async_gen_firstiter, firstiter);
}
PyObject *
_PyEval_GetAsyncGenFirstiter(void)
{
PyThreadState *tstate = PyThreadState_GET();
return tstate->async_gen_firstiter;
}
void
_PyEval_SetAsyncGenFinalizer(PyObject *finalizer)
{
PyThreadState *tstate = PyThreadState_GET();
Py_XINCREF(finalizer);
Py_XSETREF(tstate->async_gen_finalizer, finalizer);
}
PyObject *
_PyEval_GetAsyncGenFinalizer(void)
{
PyThreadState *tstate = PyThreadState_GET();
return tstate->async_gen_finalizer;
}
PyObject * PyObject *
PyEval_GetBuiltins(void) PyEval_GetBuiltins(void)
{ {
......
...@@ -1886,8 +1886,6 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async) ...@@ -1886,8 +1886,6 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async)
return 0; return 0;
} }
if (is_async)
co->co_flags |= CO_COROUTINE;
compiler_make_closure(c, co, funcflags, qualname); compiler_make_closure(c, co, funcflags, qualname);
Py_DECREF(qualname); Py_DECREF(qualname);
Py_DECREF(co); Py_DECREF(co);
...@@ -2801,6 +2799,9 @@ compiler_visit_stmt(struct compiler *c, stmt_ty s) ...@@ -2801,6 +2799,9 @@ compiler_visit_stmt(struct compiler *c, stmt_ty s)
if (c->u->u_ste->ste_type != FunctionBlock) if (c->u->u_ste->ste_type != FunctionBlock)
return compiler_error(c, "'return' outside function"); return compiler_error(c, "'return' outside function");
if (s->v.Return.value) { if (s->v.Return.value) {
if (c->u->u_ste->ste_coroutine && c->u->u_ste->ste_generator)
return compiler_error(
c, "'return' with value in async generator");
VISIT(c, expr, s->v.Return.value); VISIT(c, expr, s->v.Return.value);
} }
else else
...@@ -4115,8 +4116,6 @@ compiler_visit_expr(struct compiler *c, expr_ty e) ...@@ -4115,8 +4116,6 @@ compiler_visit_expr(struct compiler *c, expr_ty e)
case Yield_kind: case Yield_kind:
if (c->u->u_ste->ste_type != FunctionBlock) if (c->u->u_ste->ste_type != FunctionBlock)
return compiler_error(c, "'yield' outside function"); return compiler_error(c, "'yield' outside function");
if (c->u->u_scope_type == COMPILER_SCOPE_ASYNC_FUNCTION)
return compiler_error(c, "'yield' inside async function");
if (e->v.Yield.value) { if (e->v.Yield.value) {
VISIT(c, expr, e->v.Yield.value); VISIT(c, expr, e->v.Yield.value);
} }
...@@ -4992,8 +4991,12 @@ compute_code_flags(struct compiler *c) ...@@ -4992,8 +4991,12 @@ compute_code_flags(struct compiler *c)
flags |= CO_NEWLOCALS | CO_OPTIMIZED; flags |= CO_NEWLOCALS | CO_OPTIMIZED;
if (ste->ste_nested) if (ste->ste_nested)
flags |= CO_NESTED; flags |= CO_NESTED;
if (ste->ste_generator) if (ste->ste_generator && !ste->ste_coroutine)
flags |= CO_GENERATOR; flags |= CO_GENERATOR;
if (!ste->ste_generator && ste->ste_coroutine)
flags |= CO_COROUTINE;
if (ste->ste_generator && ste->ste_coroutine)
flags |= CO_ASYNC_GENERATOR;
if (ste->ste_varargs) if (ste->ste_varargs)
flags |= CO_VARARGS; flags |= CO_VARARGS;
if (ste->ste_varkeywords) if (ste->ste_varkeywords)
......
...@@ -694,6 +694,7 @@ Py_FinalizeEx(void) ...@@ -694,6 +694,7 @@ Py_FinalizeEx(void)
_PyGC_Fini(); _PyGC_Fini();
_PyRandom_Fini(); _PyRandom_Fini();
_PyArg_Fini(); _PyArg_Fini();
PyAsyncGen_Fini();
/* Cleanup Unicode implementation */ /* Cleanup Unicode implementation */
_PyUnicode_Fini(); _PyUnicode_Fini();
......
...@@ -229,6 +229,9 @@ new_threadstate(PyInterpreterState *interp, int init) ...@@ -229,6 +229,9 @@ new_threadstate(PyInterpreterState *interp, int init)
tstate->in_coroutine_wrapper = 0; tstate->in_coroutine_wrapper = 0;
tstate->co_extra_user_count = 0; tstate->co_extra_user_count = 0;
tstate->async_gen_firstiter = NULL;
tstate->async_gen_finalizer = NULL;
if (init) if (init)
_PyThreadState_Init(tstate); _PyThreadState_Init(tstate);
...@@ -408,6 +411,8 @@ PyThreadState_Clear(PyThreadState *tstate) ...@@ -408,6 +411,8 @@ PyThreadState_Clear(PyThreadState *tstate)
Py_CLEAR(tstate->c_traceobj); Py_CLEAR(tstate->c_traceobj);
Py_CLEAR(tstate->coroutine_wrapper); Py_CLEAR(tstate->coroutine_wrapper);
Py_CLEAR(tstate->async_gen_firstiter);
Py_CLEAR(tstate->async_gen_finalizer);
} }
......
...@@ -63,6 +63,7 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block, ...@@ -63,6 +63,7 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block,
ste->ste_nested = 1; ste->ste_nested = 1;
ste->ste_child_free = 0; ste->ste_child_free = 0;
ste->ste_generator = 0; ste->ste_generator = 0;
ste->ste_coroutine = 0;
ste->ste_returns_value = 0; ste->ste_returns_value = 0;
ste->ste_needs_class_closure = 0; ste->ste_needs_class_closure = 0;
...@@ -378,7 +379,7 @@ error_at_directive(PySTEntryObject *ste, PyObject *name) ...@@ -378,7 +379,7 @@ error_at_directive(PySTEntryObject *ste, PyObject *name)
PyLong_AsLong(PyTuple_GET_ITEM(data, 2))); PyLong_AsLong(PyTuple_GET_ITEM(data, 2)));
return 0; return 0;
} }
} }
PyErr_SetString(PyExc_RuntimeError, PyErr_SetString(PyExc_RuntimeError,
"BUG: internal directive bookkeeping broken"); "BUG: internal directive bookkeeping broken");
...@@ -1397,6 +1398,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) ...@@ -1397,6 +1398,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
FunctionBlock, (void *)s, s->lineno, FunctionBlock, (void *)s, s->lineno,
s->col_offset)) s->col_offset))
VISIT_QUIT(st, 0); VISIT_QUIT(st, 0);
st->st_cur->ste_coroutine = 1;
VISIT(st, arguments, s->v.AsyncFunctionDef.args); VISIT(st, arguments, s->v.AsyncFunctionDef.args);
VISIT_SEQ(st, stmt, s->v.AsyncFunctionDef.body); VISIT_SEQ(st, stmt, s->v.AsyncFunctionDef.body);
if (!symtable_exit_block(st, s)) if (!symtable_exit_block(st, s))
...@@ -1492,7 +1494,7 @@ symtable_visit_expr(struct symtable *st, expr_ty e) ...@@ -1492,7 +1494,7 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
break; break;
case Await_kind: case Await_kind:
VISIT(st, expr, e->v.Await.value); VISIT(st, expr, e->v.Await.value);
st->st_cur->ste_generator = 1; st->st_cur->ste_coroutine = 1;
break; break;
case Compare_kind: case Compare_kind:
VISIT(st, expr, e->v.Compare.left); VISIT(st, expr, e->v.Compare.left);
......
...@@ -717,6 +717,113 @@ Return the wrapper for coroutine objects set by sys.set_coroutine_wrapper." ...@@ -717,6 +717,113 @@ Return the wrapper for coroutine objects set by sys.set_coroutine_wrapper."
); );
static PyTypeObject AsyncGenHooksType;
PyDoc_STRVAR(asyncgen_hooks_doc,
"asyncgen_hooks\n\
\n\
A struct sequence providing information about asynhronous\n\
generators hooks. The attributes are read only.");
static PyStructSequence_Field asyncgen_hooks_fields[] = {
{"firstiter", "Hook to intercept first iteration"},
{"finalizer", "Hook to intercept finalization"},
{0}
};
static PyStructSequence_Desc asyncgen_hooks_desc = {
"asyncgen_hooks", /* name */
asyncgen_hooks_doc, /* doc */
asyncgen_hooks_fields , /* fields */
2
};
static PyObject *
sys_set_asyncgen_hooks(PyObject *self, PyObject *args, PyObject *kw)
{
static char *keywords[] = {"firstiter", "finalizer", NULL};
PyObject *firstiter = NULL;
PyObject *finalizer = NULL;
if (!PyArg_ParseTupleAndKeywords(
args, kw, "|OO", keywords,
&firstiter, &finalizer)) {
return NULL;
}
if (finalizer && finalizer != Py_None) {
if (!PyCallable_Check(finalizer)) {
PyErr_Format(PyExc_TypeError,
"callable finalizer expected, got %.50s",
Py_TYPE(finalizer)->tp_name);
return NULL;
}
_PyEval_SetAsyncGenFinalizer(finalizer);
}
else if (finalizer == Py_None) {
_PyEval_SetAsyncGenFinalizer(NULL);
}
if (firstiter && firstiter != Py_None) {
if (!PyCallable_Check(firstiter)) {
PyErr_Format(PyExc_TypeError,
"callable firstiter expected, got %.50s",
Py_TYPE(firstiter)->tp_name);
return NULL;
}
_PyEval_SetAsyncGenFirstiter(firstiter);
}
else if (firstiter == Py_None) {
_PyEval_SetAsyncGenFirstiter(NULL);
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(set_asyncgen_hooks_doc,
"set_asyncgen_hooks(*, firstiter=None, finalizer=None)\n\
\n\
Set a finalizer for async generators objects."
);
static PyObject *
sys_get_asyncgen_hooks(PyObject *self, PyObject *args)
{
PyObject *res;
PyObject *firstiter = _PyEval_GetAsyncGenFirstiter();
PyObject *finalizer = _PyEval_GetAsyncGenFinalizer();
res = PyStructSequence_New(&AsyncGenHooksType);
if (res == NULL) {
return NULL;
}
if (firstiter == NULL) {
firstiter = Py_None;
}
if (finalizer == NULL) {
finalizer = Py_None;
}
Py_INCREF(firstiter);
PyStructSequence_SET_ITEM(res, 0, firstiter);
Py_INCREF(finalizer);
PyStructSequence_SET_ITEM(res, 1, finalizer);
return res;
}
PyDoc_STRVAR(get_asyncgen_hooks_doc,
"get_asyncgen_hooks()\n\
\n\
Return a namedtuple of installed asynchronous generators hooks \
(firstiter, finalizer)."
);
static PyTypeObject Hash_InfoType; static PyTypeObject Hash_InfoType;
PyDoc_STRVAR(hash_info_doc, PyDoc_STRVAR(hash_info_doc,
...@@ -1315,6 +1422,10 @@ static PyMethodDef sys_methods[] = { ...@@ -1315,6 +1422,10 @@ static PyMethodDef sys_methods[] = {
set_coroutine_wrapper_doc}, set_coroutine_wrapper_doc},
{"get_coroutine_wrapper", sys_get_coroutine_wrapper, METH_NOARGS, {"get_coroutine_wrapper", sys_get_coroutine_wrapper, METH_NOARGS,
get_coroutine_wrapper_doc}, get_coroutine_wrapper_doc},
{"set_asyncgen_hooks", sys_set_asyncgen_hooks,
METH_VARARGS | METH_KEYWORDS, set_asyncgen_hooks_doc},
{"get_asyncgen_hooks", sys_get_asyncgen_hooks, METH_NOARGS,
get_asyncgen_hooks_doc},
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };
...@@ -1950,6 +2061,14 @@ _PySys_Init(void) ...@@ -1950,6 +2061,14 @@ _PySys_Init(void)
SET_SYS_FROM_STRING("thread_info", PyThread_GetInfo()); SET_SYS_FROM_STRING("thread_info", PyThread_GetInfo());
#endif #endif
/* initialize asyncgen_hooks */
if (AsyncGenHooksType.tp_name == NULL) {
if (PyStructSequence_InitType2(
&AsyncGenHooksType, &asyncgen_hooks_desc) < 0) {
return NULL;
}
}
#undef SET_SYS_FROM_STRING #undef SET_SYS_FROM_STRING
#undef SET_SYS_FROM_STRING_BORROW #undef SET_SYS_FROM_STRING_BORROW
if (PyErr_Occurred()) if (PyErr_Occurred())
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment