summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cython/Compiler/Builtin.py7
-rw-r--r--Cython/Compiler/Code.py1
-rw-r--r--Cython/Compiler/ExprNodes.py151
-rw-r--r--Cython/Compiler/FlowControl.py6
-rw-r--r--Cython/Compiler/ModuleNode.py2
-rw-r--r--Cython/Compiler/Nodes.py64
-rw-r--r--Cython/Compiler/Parsing.pxd7
-rw-r--r--Cython/Compiler/Parsing.py30
-rw-r--r--Cython/Utility/Coroutine.c149
-rw-r--r--tests/run/test_coroutines_pep492.pyx6
10 files changed, 356 insertions, 67 deletions
diff --git a/Cython/Compiler/Builtin.py b/Cython/Compiler/Builtin.py
index b518abbd7..843f83edc 100644
--- a/Cython/Compiler/Builtin.py
+++ b/Cython/Compiler/Builtin.py
@@ -398,9 +398,16 @@ def init_builtins():
init_builtin_structs()
init_builtin_types()
init_builtin_funcs()
+
builtin_scope.declare_var(
'__debug__', PyrexTypes.c_const_type(PyrexTypes.c_bint_type),
pos=None, cname='(!Py_OptimizeFlag)', is_cdef=True)
+
+ entry = builtin_scope.declare_var(
+ 'StopAsyncIteration', PyrexTypes.py_object_type,
+ pos=None, cname='__Pyx_PyExc_StopAsyncIteration')
+ entry.utility_code = UtilityCode.load_cached("StopAsyncIteration", "Coroutine.c")
+
global list_type, tuple_type, dict_type, set_type, frozenset_type
global bytes_type, str_type, unicode_type, basestring_type, slice_type
global float_type, bool_type, type_type, complex_type, bytearray_type
diff --git a/Cython/Compiler/Code.py b/Cython/Compiler/Code.py
index 8aa64b3e2..99587e4aa 100644
--- a/Cython/Compiler/Code.py
+++ b/Cython/Compiler/Code.py
@@ -49,7 +49,6 @@ non_portable_builtins_map = {
'basestring' : ('PY_MAJOR_VERSION >= 3', 'str'),
'xrange' : ('PY_MAJOR_VERSION >= 3', 'range'),
'raw_input' : ('PY_MAJOR_VERSION >= 3', 'input'),
- 'StopAsyncIteration': ('PY_VERSION_HEX < 0x030500B1', 'StopIteration'),
}
basicsize_builtins_map = {
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
index c2f67beed..12a76b832 100644
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -2298,6 +2298,7 @@ class IteratorNode(ExprNode):
counter_cname = None
cpp_iterator_cname = None
reversed = False # currently only used for list/tuple types (see Optimize.py)
+ is_async = False
subexprs = ['sequence']
@@ -2311,8 +2312,7 @@ class IteratorNode(ExprNode):
self.analyse_cpp_types(env)
else:
self.sequence = self.sequence.coerce_to_pyobject(env)
- if self.sequence.type is list_type or \
- self.sequence.type is tuple_type:
+ if self.sequence.type in (list_type, tuple_type):
self.sequence = self.sequence.as_none_safe_node("'NoneType' object is not iterable")
self.is_temp = 1
return self
@@ -2400,8 +2400,8 @@ class IteratorNode(ExprNode):
return
if sequence_type.is_array or sequence_type.is_ptr:
raise InternalError("for in carray slice not transformed")
- is_builtin_sequence = sequence_type is list_type or \
- sequence_type is tuple_type
+
+ is_builtin_sequence = sequence_type in (list_type, tuple_type)
if not is_builtin_sequence:
# reversed() not currently optimised (see Optimize.py)
assert not self.reversed, "internal error: reversed() only implemented for list/tuple objects"
@@ -2411,6 +2411,7 @@ class IteratorNode(ExprNode):
"if (likely(PyList_CheckExact(%s)) || PyTuple_CheckExact(%s)) {" % (
self.sequence.py_result(),
self.sequence.py_result()))
+
if is_builtin_sequence or self.may_be_a_sequence:
self.counter_cname = code.funcstate.allocate_temp(
PyrexTypes.c_py_ssize_t_type, manage_ref=False)
@@ -2421,25 +2422,25 @@ class IteratorNode(ExprNode):
init_value = 'PyTuple_GET_SIZE(%s) - 1' % self.result()
else:
init_value = '0'
- code.putln(
- "%s = %s; __Pyx_INCREF(%s); %s = %s;" % (
- self.result(),
- self.sequence.py_result(),
- self.result(),
- self.counter_cname,
- init_value
- ))
+ code.putln("%s = %s; __Pyx_INCREF(%s); %s = %s;" % (
+ self.result(),
+ self.sequence.py_result(),
+ self.result(),
+ self.counter_cname,
+ init_value))
if not is_builtin_sequence:
self.iter_func_ptr = code.funcstate.allocate_temp(self._func_iternext_type, manage_ref=False)
if self.may_be_a_sequence:
code.putln("%s = NULL;" % self.iter_func_ptr)
code.putln("} else {")
code.put("%s = -1; " % self.counter_cname)
+
code.putln("%s = PyObject_GetIter(%s); %s" % (
- self.result(),
- self.sequence.py_result(),
- code.error_goto_if_null(self.result(), self.pos)))
+ self.result(),
+ self.sequence.py_result(),
+ code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
+
# PyObject_GetIter() fails if "tp_iternext" is not set, but the check below
# makes it visible to the C compiler that the pointer really isn't NULL, so that
# it can distinguish between the special cases and the generic case
@@ -2553,7 +2554,7 @@ class IteratorNode(ExprNode):
class NextNode(AtomicExprNode):
# Used as part of for statement implementation.
- # Implements result = iterator.next()
+ # Implements result = next(iterator)
# Created during analyse_types phase.
# The iterator is not owned by this node.
#
@@ -2566,7 +2567,7 @@ class NextNode(AtomicExprNode):
def type_dependencies(self, env):
return self.iterator.type_dependencies(env)
- def infer_type(self, env, iterator_type = None):
+ def infer_type(self, env, iterator_type=None):
if iterator_type is None:
iterator_type = self.iterator.infer_type(env)
if iterator_type.is_ptr or iterator_type.is_array:
@@ -2596,13 +2597,75 @@ class NextNode(AtomicExprNode):
self.iterator.generate_iter_next_result_code(self.result(), code)
+class AsyncIteratorNode(ExprNode):
+ # Used as part of 'async for' statement implementation.
+ #
+ # Implements result = sequence.__aiter__()
+ #
+ # sequence ExprNode
+
+ subexprs = ['sequence']
+
+ is_async = True
+ type = py_object_type
+ is_temp = 1
+
+ def infer_type(self, env):
+ return py_object_type
+
+ def analyse_types(self, env):
+ self.sequence = self.sequence.analyse_types(env)
+ if not self.sequence.type.is_pyobject:
+ error(self.pos, "async for loops not allowed on C/C++ types")
+ self.sequence = self.sequence.coerce_to_pyobject(env)
+ return self
+
+ def generate_result_code(self, code):
+ code.globalstate.use_utility_code(UtilityCode.load_cached("AsyncIter", "Coroutine.c"))
+ code.putln("%s = __Pyx_Coroutine_GetAsyncIter(%s); %s" % (
+ self.result(),
+ self.sequence.py_result(),
+ code.error_goto_if_null(self.result(), self.pos)))
+ code.put_gotref(self.result())
+
+
+class AsyncNextNode(AtomicExprNode):
+ # Used as part of 'async for' statement implementation.
+ # Implements result = iterator.__anext__()
+ # Created during analyse_types phase.
+ # The iterator is not owned by this node.
+ #
+ # iterator IteratorNode
+
+ type = py_object_type
+ is_temp = 1
+
+ def __init__(self, iterator):
+ AtomicExprNode.__init__(self, iterator.pos)
+ self.iterator = iterator
+
+ def infer_type(self, env):
+ return py_object_type
+
+ def analyse_types(self, env):
+ return self
+
+ def generate_result_code(self, code):
+ code.globalstate.use_utility_code(UtilityCode.load_cached("AsyncIter", "Coroutine.c"))
+ code.putln("%s = __Pyx_Coroutine_AsyncIterNext(%s); %s" % (
+ self.result(),
+ self.iterator.py_result(),
+ code.error_goto_if_null(self.result(), self.pos)))
+ code.put_gotref(self.result())
+
+
class WithExitCallNode(ExprNode):
# The __exit__() call of a 'with' statement. Used in both the
# except and finally clauses.
# with_stat WithStatNode the surrounding 'with' statement
# args TupleNode or ResultStatNode the exception info tuple
- # await AwaitExprNode the await
+ # await AwaitExprNode the await expression of an 'async with' statement
subexprs = ['args', 'await']
test_if_run = True
@@ -2639,7 +2702,8 @@ class WithExitCallNode(ExprNode):
code.put_gotref(result_var)
if self.await:
- self.await.generate_evaluation_code(code, source_cname=result_var)
+ # FIXME: result_var temp currently leaks into the closure
+ self.await.generate_evaluation_code(code, source_cname=result_var, decref_source=True)
code.putln("%s = %s;" % (result_var, self.await.py_result()))
self.await.generate_post_assignment_code(code)
self.await.free_temps(code)
@@ -8686,7 +8750,7 @@ class YieldFromExprNode(YieldExprNode):
code.globalstate.use_utility_code(UtilityCode.load_cached("GeneratorYieldFrom", "Coroutine.c"))
return "__Pyx_Generator_Yield_From"
- def generate_evaluation_code(self, code, source_cname=None):
+ def generate_evaluation_code(self, code, source_cname=None, decref_source=False):
if source_cname is None:
self.arg.generate_evaluation_code(code)
code.putln("%s = %s(%s, %s);" % (
@@ -8697,7 +8761,7 @@ class YieldFromExprNode(YieldExprNode):
if source_cname is None:
self.arg.generate_disposal_code(code)
self.arg.free_temps(code)
- else:
+ elif decref_source:
code.put_decref_clear(source_cname, py_object_type)
code.put_xgotref(Naming.retval_cname)
@@ -8706,17 +8770,23 @@ class YieldFromExprNode(YieldExprNode):
code.putln("} else {")
# either error or sub-generator has normally terminated: return value => node result
if self.result_is_used:
- # YieldExprNode has allocated the result temp for us
- code.putln("%s = NULL;" % self.result())
- code.put_error_if_neg(self.pos, "__Pyx_PyGen_FetchStopIterationValue(&%s)" % self.result())
- code.put_gotref(self.result())
+ self.fetch_iteration_result(code)
else:
- code.putln("PyObject* exc_type = PyErr_Occurred();")
- code.putln("if (exc_type) {")
- code.putln("if (likely(exc_type == PyExc_StopIteration ||"
- " PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();")
- code.putln("else %s" % code.error_goto(self.pos))
- code.putln("}")
+ self.handle_iteration_exception(code)
+ code.putln("}")
+
+ def fetch_iteration_result(self, code):
+ # YieldExprNode has allocated the result temp for us
+ code.putln("%s = NULL;" % self.result())
+ code.put_error_if_neg(self.pos, "__Pyx_PyGen_FetchStopIterationValue(&%s)" % self.result())
+ code.put_gotref(self.result())
+
+ def handle_iteration_exception(self, code):
+ code.putln("PyObject* exc_type = PyErr_Occurred();")
+ code.putln("if (exc_type) {")
+ code.putln("if (likely(exc_type == PyExc_StopIteration ||"
+ " PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();")
+ code.putln("else %s" % code.error_goto(self.pos))
code.putln("}")
@@ -8725,7 +8795,7 @@ class AwaitExprNode(YieldFromExprNode):
#
# arg ExprNode the Awaitable value to await
# label_num integer yield label number
- # is_yield_from boolean is a YieldFromExprNode to delegate to another generator
+
is_await = True
expr_keyword = 'await'
@@ -8739,6 +8809,23 @@ class AwaitExprNode(YieldFromExprNode):
return "__Pyx_Coroutine_Yield_From"
+class AwaitIterNextExprNode(AwaitExprNode):
+ # 'await' expression node as part of 'async for' iteration
+ #
+ # Breaks out of loop on StopAsyncIteration exception.
+
+ def fetch_iteration_result(self, code):
+ assert code.break_label, "AwaitIterNextExprNode outside of 'async for' loop"
+ code.globalstate.use_utility_code(UtilityCode.load_cached("StopAsyncIteration", "Coroutine.c"))
+ code.putln("PyObject* exc_type = PyErr_Occurred();")
+ code.putln("if (exc_type && likely(exc_type == __Pyx_PyExc_StopAsyncIteration ||"
+ " PyErr_GivenExceptionMatches(exc_type, __Pyx_PyExc_StopAsyncIteration))) {")
+ code.putln("PyErr_Clear();")
+ code.putln("break;")
+ code.putln("}")
+ super(AwaitIterNextExprNode, self).fetch_iteration_result(code)
+
+
class GlobalsExprNode(AtomicExprNode):
type = dict_type
is_temp = 1
diff --git a/Cython/Compiler/FlowControl.py b/Cython/Compiler/FlowControl.py
index e0569a0fa..3d6b17a07 100644
--- a/Cython/Compiler/FlowControl.py
+++ b/Cython/Compiler/FlowControl.py
@@ -991,6 +991,9 @@ class ControlFlowAnalysis(CythonTransform):
self.mark_assignment(target, node.item)
+ def visit_AsyncForStatNode(self, node):
+ return self.visit_ForInStatNode(node)
+
def visit_ForInStatNode(self, node):
condition_block = self.flow.nextblock()
next_block = self.flow.newblock()
@@ -1002,6 +1005,9 @@ class ControlFlowAnalysis(CythonTransform):
if isinstance(node, Nodes.ForInStatNode):
self.mark_forloop_target(node)
+ elif isinstance(node, Nodes.AsyncForStatNode):
+ # not entirely correct, but good enough for now
+ self.mark_assignment(node.target, node.item)
else: # Parallel
self.mark_assignment(node.target)
diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py
index 5aaae67e7..42a63b40e 100644
--- a/Cython/Compiler/ModuleNode.py
+++ b/Cython/Compiler/ModuleNode.py
@@ -2071,7 +2071,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
code.putln("%s = PyBytes_FromStringAndSize(\"\", 0); %s" % (
Naming.empty_bytes, code.error_goto_if_null(Naming.empty_bytes, self.pos)))
- for ext_type in ('CyFunction', 'FusedFunction', 'Coroutine', 'Generator'):
+ for ext_type in ('CyFunction', 'FusedFunction', 'Coroutine', 'Generator', 'StopAsyncIteration'):
code.putln("#ifdef __Pyx_%s_USED" % ext_type)
code.put_error_if_neg(self.pos, "__pyx_%s_init()" % ext_type)
code.putln("#endif")
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
index ddd43f5ba..2694ca8ba 100644
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -6069,40 +6069,49 @@ class DictIterationNextNode(Node):
target.generate_assignment_code(result, code)
var.release(code)
+
def ForStatNode(pos, **kw):
if 'iterator' in kw:
- return ForInStatNode(pos, **kw)
+ if kw['iterator'].is_async:
+ return AsyncForStatNode(pos, **kw)
+ else:
+ return ForInStatNode(pos, **kw)
else:
return ForFromStatNode(pos, **kw)
-class ForInStatNode(LoopNode, StatNode):
- # for statement
+
+class _ForInStatNode(LoopNode, StatNode):
+ # Base class of 'for-in' statements.
#
# target ExprNode
- # iterator IteratorNode
+ # iterator IteratorNode | AwaitExprNode(AsyncIteratorNode)
# body StatNode
# else_clause StatNode
- # item NextNode used internally
+ # item NextNode | AwaitExprNode(AsyncNextNode)
+ # is_async boolean true for 'async for' statements
- child_attrs = ["target", "iterator", "body", "else_clause"]
+ child_attrs = ["target", "item", "iterator", "body", "else_clause"]
item = None
+ is_async = False
+
+ def _create_item_node(self):
+ raise NotImplementedError("must be implemented by subclasses")
def analyse_declarations(self, env):
- from . import ExprNodes
self.target.analyse_target_declaration(env)
self.body.analyse_declarations(env)
if self.else_clause:
self.else_clause.analyse_declarations(env)
- self.item = ExprNodes.NextNode(self.iterator)
+ self._create_item_node()
def analyse_expressions(self, env):
self.target = self.target.analyse_target_types(env)
self.iterator = self.iterator.analyse_expressions(env)
- from . import ExprNodes
- self.item = ExprNodes.NextNode(self.iterator) # must rewrap after analysis
+ self._create_item_node() # must rewrap self.item after analysis
self.item = self.item.analyse_expressions(env)
- if (self.iterator.type.is_ptr or self.iterator.type.is_array) and \
- self.target.type.assignable_from(self.iterator.type):
+ if (not self.is_async and
+ (self.iterator.type.is_ptr or self.iterator.type.is_array) and
+ self.target.type.assignable_from(self.iterator.type)):
# C array slice optimization.
pass
else:
@@ -6168,6 +6177,37 @@ class ForInStatNode(LoopNode, StatNode):
self.item.annotate(code)
+class ForInStatNode(_ForInStatNode):
+ # 'for' statement
+
+ is_async = False
+
+ def _create_item_node(self):
+ from .ExprNodes import NextNode
+ self.item = NextNode(self.iterator)
+
+
+class AsyncForStatNode(_ForInStatNode):
+ # 'async for' statement
+ #
+ # iterator AwaitExprNode(AsyncIteratorNode)
+ # item AwaitIterNextExprNode(AsyncIteratorNode)
+
+ is_async = True
+
+ def __init__(self, pos, iterator, **kw):
+ assert 'item' not in kw
+ from . import ExprNodes
+ # AwaitExprNodes must appear before running MarkClosureVisitor
+ kw['iterator'] = ExprNodes.AwaitExprNode(iterator.pos, arg=iterator)
+ kw['item'] = ExprNodes.AwaitIterNextExprNode(iterator.pos, arg=None)
+ _ForInStatNode.__init__(self, pos, **kw)
+
+ def _create_item_node(self):
+ from . import ExprNodes
+ self.item.arg = ExprNodes.AsyncNextNode(self.iterator)
+
+
class ForFromStatNode(LoopNode, StatNode):
# for name from expr rel name rel expr
#
diff --git a/Cython/Compiler/Parsing.pxd b/Cython/Compiler/Parsing.pxd
index 1b766a7fe..5c431ad97 100644
--- a/Cython/Compiler/Parsing.pxd
+++ b/Cython/Compiler/Parsing.pxd
@@ -44,7 +44,6 @@ cdef p_typecast(PyrexScanner s)
cdef p_sizeof(PyrexScanner s)
cdef p_yield_expression(PyrexScanner s)
cdef p_yield_statement(PyrexScanner s)
-cdef p_await_expression(PyrexScanner s)
cdef p_async_statement(PyrexScanner s, ctx)
cdef p_power(PyrexScanner s)
cdef p_new_expr(PyrexScanner s)
@@ -109,13 +108,13 @@ cdef p_if_statement(PyrexScanner s)
cdef p_if_clause(PyrexScanner s)
cdef p_else_clause(PyrexScanner s)
cdef p_while_statement(PyrexScanner s)
-cdef p_for_statement(PyrexScanner s)
-cdef dict p_for_bounds(PyrexScanner s, bint allow_testlist = *)
+cdef p_for_statement(PyrexScanner s, bint is_async=*)
+cdef dict p_for_bounds(PyrexScanner s, bint allow_testlist=*, bint is_async=*)
cdef p_for_from_relation(PyrexScanner s)
cdef p_for_from_step(PyrexScanner s)
cdef p_target(PyrexScanner s, terminator)
cdef p_for_target(PyrexScanner s)
-cdef p_for_iterator(PyrexScanner s, bint allow_testlist = *)
+cdef p_for_iterator(PyrexScanner s, bint allow_testlist=*, bint is_async=*)
cdef p_try_statement(PyrexScanner s)
cdef p_except_clause(PyrexScanner s)
cdef p_include_statement(PyrexScanner s, ctx)
diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py
index 3e008bee3..1ce20c78d 100644
--- a/Cython/Compiler/Parsing.py
+++ b/Cython/Compiler/Parsing.py
@@ -390,8 +390,7 @@ def p_async_statement(s, ctx, decorators):
elif decorators:
s.error("Decorators can only be followed by functions or classes")
elif s.sy == 'for':
- #s.error("'async for' is not currently supported", fatal=False)
- return p_statement(s, ctx) # TODO: implement
+ return p_for_statement(s, is_async=True)
elif s.sy == 'with':
s.next()
return p_with_items(s, is_async=True)
@@ -399,10 +398,6 @@ def p_async_statement(s, ctx, decorators):
s.error("expected one of 'def', 'for', 'with' after 'async'")
-def p_await_expression(s):
- n1 = p_atom(s)
-
-
#power: atom_expr ('**' factor)*
#atom_expr: ['await'] atom trailer*
@@ -1604,23 +1599,25 @@ def p_while_statement(s):
condition = test, body = body,
else_clause = else_clause)
-def p_for_statement(s):
+
+def p_for_statement(s, is_async=False):
# s.sy == 'for'
pos = s.position()
s.next()
- kw = p_for_bounds(s, allow_testlist=True)
+ kw = p_for_bounds(s, allow_testlist=True, is_async=is_async)
body = p_suite(s)
else_clause = p_else_clause(s)
- kw.update(body = body, else_clause = else_clause)
+ kw.update(body=body, else_clause=else_clause, is_async=is_async)
return Nodes.ForStatNode(pos, **kw)
-def p_for_bounds(s, allow_testlist=True):
+
+def p_for_bounds(s, allow_testlist=True, is_async=False):
target = p_for_target(s)
if s.sy == 'in':
s.next()
- iterator = p_for_iterator(s, allow_testlist)
- return dict( target = target, iterator = iterator )
- elif not s.in_python_file:
+ iterator = p_for_iterator(s, allow_testlist, is_async=is_async)
+ return dict(target=target, iterator=iterator)
+ elif not s.in_python_file and not is_async:
if s.sy == 'from':
s.next()
bound1 = p_bit_expr(s)
@@ -1690,16 +1687,19 @@ def p_target(s, terminator):
else:
return expr
+
def p_for_target(s):
return p_target(s, 'in')
-def p_for_iterator(s, allow_testlist=True):
+
+def p_for_iterator(s, allow_testlist=True, is_async=False):
pos = s.position()
if allow_testlist:
expr = p_testlist(s)
else:
expr = p_or_test(s)
- return ExprNodes.IteratorNode(pos, sequence = expr)
+ return (ExprNodes.AsyncIteratorNode if is_async else ExprNodes.IteratorNode)(pos, sequence=expr)
+
def p_try_statement(s):
# s.sy == 'try'
diff --git a/Cython/Utility/Coroutine.c b/Cython/Utility/Coroutine.c
index 4d2e51987..a2fef160e 100644
--- a/Cython/Utility/Coroutine.c
+++ b/Cython/Utility/Coroutine.c
@@ -155,6 +155,56 @@ bad:
}
+//////////////////// AsyncIter.proto ////////////////////
+
+static CYTHON_INLINE PyObject *__Pyx_Coroutine_GetAsyncIter(PyObject *o); /*proto*/
+static CYTHON_INLINE PyObject *__Pyx_Coroutine_AsyncIterNext(PyObject *o); /*proto*/
+
+//////////////////// AsyncIter ////////////////////
+//@requires: GetAwaitIter
+//@requires: ObjectHandling.c::PyObjectCallMethod0
+
+static CYTHON_INLINE PyObject *__Pyx_Coroutine_GetAsyncIter(PyObject *obj) {
+#if PY_VERSION_HEX >= 0x030500B1
+ PyAsyncMethods* am = Py_TYPE(obj)->tp_as_async;
+ if (likely(am && am->am_aiter)) {
+ return (*am->am_aiter)(obj);
+ }
+#else
+ PyObject *iter = __Pyx_PyObject_CallMethod0(obj, PYIDENT("__aiter__"));
+ if (likely(iter))
+ return iter;
+ // FIXME: for the sake of a nicely conforming exception message, assume any AttributeError meant '__aiter__'
+ if (!PyErr_ExceptionMatches(PyExc_AttributeError))
+ return NULL;
+#endif
+
+ PyErr_Format(PyExc_TypeError, "'async for' requires an object with __aiter__ method, got %.100s",
+ Py_TYPE(obj)->tp_name);
+ return NULL;
+}
+
+static CYTHON_INLINE PyObject *__Pyx_Coroutine_AsyncIterNext(PyObject *obj) {
+#if PY_VERSION_HEX >= 0x030500B1
+ PyAsyncMethods* am = Py_TYPE(obj)->tp_as_async;
+ if (likely(am && am->am_anext)) {
+ return (*am->am_anext)(obj);
+ } else {
+#else
+ PyObject *value = __Pyx_PyObject_CallMethod0(obj, PYIDENT("__anext__"));
+ if (likely(value))
+ return value;
+ // FIXME: for the sake of a nicely conforming exception message, assume any AttributeError meant '__anext__'
+ if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
+#endif
+
+ PyErr_Format(PyExc_TypeError, "'async for' requires an object with __anext__ method, got %.100s",
+ Py_TYPE(obj)->tp_name);
+ }
+ return NULL;
+}
+
+
//////////////////// pep479.proto ////////////////////
static void __Pyx_Generator_Replace_StopIteration(void); /*proto*/
@@ -1531,3 +1581,102 @@ except AttributeError:
#endif
return module;
}
+
+
+//////////////////// StopAsyncIteration.proto ////////////////////
+
+#define __Pyx_StopAsyncIteration_USED
+static PyObject *__Pyx_PyExc_StopAsyncIteration;
+static int __pyx_StopAsyncIteration_init(void); /*proto*/
+
+//////////////////// StopAsyncIteration ////////////////////
+
+#if PY_VERSION_HEX < 0x030500B1
+static PyTypeObject __Pyx__PyExc_StopAsyncIteration_type = {
+ PyVarObject_HEAD_INIT(0, 0)
+ "StopAsyncIteration", /*tp_name*/
+ sizeof(PyBaseExceptionObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ 0, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+#if PY_MAJOR_VERSION < 3
+ 0, /*tp_compare*/
+#else
+ 0, /*reserved*/
+#endif
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+ PyDoc_STR("Signal the end from iterator.__anext__()."), /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*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*/
+ 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*/
+#if PY_VERSION_HEX >= 0x030400a1
+ 0, /*tp_finalize*/
+#endif
+};
+#endif
+
+static int __pyx_StopAsyncIteration_init(void) {
+#if PY_VERSION_HEX >= 0x030500B1
+ __Pyx_PyExc_StopAsyncIteration = PyExc_StopAsyncIteration;
+#else
+ PyObject *builtins = PyEval_GetBuiltins();
+ if (likely(builtins)) {
+ PyObject *exc = PyMapping_GetItemString(builtins, "StopAsyncIteration");
+ if (exc) {
+ __Pyx_PyExc_StopAsyncIteration = exc;
+ return 0;
+ }
+ }
+ PyErr_Clear();
+
+ __Pyx__PyExc_StopAsyncIteration_type.tp_traverse = ((PyTypeObject*)PyExc_BaseException)->tp_traverse;
+ __Pyx__PyExc_StopAsyncIteration_type.tp_clear = ((PyTypeObject*)PyExc_BaseException)->tp_clear;
+ __Pyx__PyExc_StopAsyncIteration_type.tp_dealloc = ((PyTypeObject*)PyExc_BaseException)->tp_dealloc;
+ __Pyx__PyExc_StopAsyncIteration_type.tp_dictoffset = ((PyTypeObject*)PyExc_BaseException)->tp_dictoffset;
+ __Pyx__PyExc_StopAsyncIteration_type.tp_base = (PyTypeObject*)PyExc_Exception;
+ __Pyx__PyExc_StopAsyncIteration_type.tp_init = ((PyTypeObject*)PyExc_BaseException)->tp_init;
+ __Pyx__PyExc_StopAsyncIteration_type.tp_new = ((PyTypeObject*)PyExc_BaseException)->tp_new;
+
+ __Pyx_PyExc_StopAsyncIteration = (PyObject*) __Pyx_FetchCommonType(&__Pyx__PyExc_StopAsyncIteration_type);
+ if (unlikely(!__Pyx_PyExc_StopAsyncIteration))
+ return -1;
+ if (builtins && unlikely(PyMapping_SetItemString(builtins, "StopAsyncIteration", __Pyx_PyExc_StopAsyncIteration) < 0))
+ return -1;
+#endif
+ return 0;
+}
diff --git a/tests/run/test_coroutines_pep492.pyx b/tests/run/test_coroutines_pep492.pyx
index 793831883..7d216835d 100644
--- a/tests/run/test_coroutines_pep492.pyx
+++ b/tests/run/test_coroutines_pep492.pyx
@@ -104,6 +104,7 @@ class TokenizerRegrTest(unittest.TestCase):
class CoroutineTest(unittest.TestCase):
+ @classmethod
def setUpClass(cls):
# never mark warnings as "already seen" to prevent them from being suppressed
from warnings import simplefilter
@@ -209,8 +210,9 @@ class CoroutineTest(unittest.TestCase):
with check():
iter(foo())
- with check():
- next(foo())
+ # in Cython: not iterable, but an iterator ...
+ #with check():
+ # next(foo())
with silence_coro_gc(), check():
for i in foo():