diff -r 6216fb8afa53 Include/genobject.h --- a/Include/genobject.h Mon Jun 06 13:00:03 2016 +0300 +++ b/Include/genobject.h Mon Jun 06 15:07:58 2016 -0400 @@ -54,6 +54,9 @@ PyAPI_DATA(PyTypeObject) PyCoro_Type; PyAPI_DATA(PyTypeObject) _PyCoroWrapper_Type; +PyAPI_DATA(PyTypeObject) _PyAIterWrapper_Type; +PyObject *_PyAIterWrapper_New(PyObject *aiter); + #define PyCoro_CheckExact(op) (Py_TYPE(op) == &PyCoro_Type) PyObject *_PyCoro_GetAwaitableIter(PyObject *o); PyAPI_FUNC(PyObject *) PyCoro_New(struct _frame *, diff -r 6216fb8afa53 Lib/test/test_coroutines.py --- a/Lib/test/test_coroutines.py Mon Jun 06 13:00:03 2016 +0300 +++ b/Lib/test/test_coroutines.py Mon Jun 06 15:07:58 2016 -0400 @@ -1255,8 +1255,9 @@ buffer = [] async def test1(): - async for i1, i2 in AsyncIter(): - buffer.append(i1 + i2) + with self.assertWarnsRegex(DeprecationWarning, "legacy"): + async for i1, i2 in AsyncIter(): + buffer.append(i1 + i2) yielded, _ = run_async(test1()) # Make sure that __aiter__ was called only once @@ -1268,12 +1269,13 @@ buffer = [] async def test2(): nonlocal buffer - async for i in AsyncIter(): - buffer.append(i[0]) - if i[0] == 20: - break - else: - buffer.append('what?') + with self.assertWarnsRegex(DeprecationWarning, "legacy"): + async for i in AsyncIter(): + buffer.append(i[0]) + if i[0] == 20: + break + else: + buffer.append('what?') buffer.append('end') yielded, _ = run_async(test2()) @@ -1286,12 +1288,13 @@ buffer = [] async def test3(): nonlocal buffer - async for i in AsyncIter(): - if i[0] > 20: - continue - buffer.append(i[0]) - else: - buffer.append('what?') + with self.assertWarnsRegex(DeprecationWarning, "legacy"): + async for i in AsyncIter(): + if i[0] > 20: + continue + buffer.append(i[0]) + else: + buffer.append('what?') buffer.append('end') yielded, _ = run_async(test3()) @@ -1338,7 +1341,7 @@ def test_for_4(self): class I: - async def __aiter__(self): + def __aiter__(self): return self def __anext__(self): @@ -1368,8 +1371,9 @@ return 123 async def foo(): - async for i in I(): - print('never going to happen') + with self.assertWarnsRegex(DeprecationWarning, "legacy"): + async for i in I(): + print('never going to happen') with self.assertRaisesRegex( TypeError, @@ -1393,7 +1397,7 @@ def __init__(self): self.i = 0 - async def __aiter__(self): + def __aiter__(self): return self async def __anext__(self): @@ -1472,6 +1476,21 @@ 1/0 async def foo(): nonlocal CNT + with self.assertWarnsRegex(DeprecationWarning, "legacy"): + async for i in AI(): + CNT += 1 + CNT += 10 + with self.assertRaises(ZeroDivisionError): + run_async(foo()) + self.assertEqual(CNT, 0) + + def test_for_8(self): + CNT = 0 + class AI: + def __aiter__(self): + 1/0 + async def foo(): + nonlocal CNT async for i in AI(): CNT += 1 CNT += 10 diff -r 6216fb8afa53 Objects/genobject.c --- a/Objects/genobject.c Mon Jun 06 13:00:03 2016 +0300 +++ b/Objects/genobject.c Mon Jun 06 15:07:58 2016 -0400 @@ -984,3 +984,97 @@ { return gen_new_with_qualname(&PyCoro_Type, f, name, qualname); } + + +/* __aiter__ wrapper */ + +typedef struct { + PyObject_HEAD + PyObject *aw_aiter; +} PyAIterWrapper; + + +static PyObject * +aiter_wrapper_iternext(PyAIterWrapper *aw) +{ + PyErr_SetObject(PyExc_StopIteration, aw->aw_aiter); + return NULL; +} + +static int +aiter_wrapper_traverse(PyAIterWrapper *aw, visitproc visit, void *arg) +{ + Py_VISIT((PyObject *)aw->aw_aiter); + return 0; +} + +static void +aiter_wrapper_dealloc(PyAIterWrapper *aw) +{ + _PyObject_GC_UNTRACK((PyObject *)aw); + Py_CLEAR(aw->aw_aiter); + PyObject_GC_Del(aw); +} + +static PyAsyncMethods aiter_wrapper_as_async = { + PyObject_SelfIter, /* am_await */ + 0, /* am_aiter */ + 0 /* am_anext */ +}; + +PyTypeObject _PyAIterWrapper_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "aiter_wrapper", + sizeof(PyAIterWrapper), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)aiter_wrapper_dealloc, /* destructor tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + &aiter_wrapper_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 */ + "A wrapper object for __aiter__ bakwards compatibility.", + (traverseproc)aiter_wrapper_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + (iternextfunc)aiter_wrapper_iternext, /* tp_iternext */ + 0, /* 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 * +_PyAIterWrapper_New(PyObject *aiter) +{ + PyAIterWrapper *aw = PyObject_GC_New(PyAIterWrapper, + &_PyAIterWrapper_Type); + if (aw == NULL) { + return NULL; + } + Py_INCREF(aiter); + aw->aw_aiter = aiter; + _PyObject_GC_TRACK(aw); + return (PyObject *)aw; +} diff -r 6216fb8afa53 Python/ceval.c --- a/Python/ceval.c Mon Jun 06 13:00:03 2016 +0300 +++ b/Python/ceval.c Mon Jun 06 15:07:58 2016 -0400 @@ -1935,8 +1935,9 @@ PyObject *obj = TOP(); PyTypeObject *type = Py_TYPE(obj); - if (type->tp_as_async != NULL) + if (type->tp_as_async != NULL) { getter = type->tp_as_async->am_aiter; + } if (getter != NULL) { iter = (*getter)(obj); @@ -1957,6 +1958,15 @@ goto error; } + if (Py_TYPE(iter)->tp_as_async != NULL && + Py_TYPE(iter)->tp_as_async->am_anext != NULL) { + + PyObject *wrapper = _PyAIterWrapper_New(iter); + Py_DECREF(iter); + SET_TOP(wrapper); + DISPATCH(); + } + awaitable = _PyCoro_GetAwaitableIter(iter); if (awaitable == NULL) { SET_TOP(NULL); @@ -1968,9 +1978,23 @@ Py_DECREF(iter); goto error; - } else + } else { Py_DECREF(iter); + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "'%.100s' implements legacy __aiter__ protocol; " + "__aiter__ should return an asynchronous " + "iterator, not awaitable", + type->tp_name)) + { + /* Warning was converted to an error. */ + Py_DECREF(awaitable); + SET_TOP(NULL); + goto error; + } + } + SET_TOP(awaitable); DISPATCH(); }