Repository: cython/cython

Issue:
[BUG] cython does not translate `await` in set comprehension properly 
### Describe the bug

Cython warns `warning: ‘__Pyx_PyAsyncGen_Fini’ defined but not used [-Wunused-function]` in compilation of test.py and raise `TypeError: 'async_generator' object is not iterable`  when running the test_run.py script. The script can be runned without any exception before I compile test.py by _Cython_

steps of the failed test:
```bash
cythonize test.py --inplace
python test_run.py
```
steps of the successful test without compiling by Cython:
```bash
rm test.cpython-311-x86_64-linux-gnu.so
python test_run.py
```

### Code to reproduce the behaviour:

test.py
```cython
import asyncio


async def f():
    return set(x*2 for x in await h(2))


async def h(arg):
    await asyncio.sleep(1)
    return [arg, arg+1]


def test():
    asyncio.run(f())                        
```
test_run.py
```python
from test import test
if __name__=="__main__":
    test()
```

### Expected behaviour

Moving the await clause out of the set comprehension can be a workaround:
```python
import asyncio


async def f():
    s= await h(2)
    return set(x*2 for x in s)


async def h(arg):
    await asyncio.sleep(1)
    return [arg, arg+1]


def test():
    asyncio.run(f())             
```
But I think there's something wrong in Cython's compilation.


### OS

Linux

### Python version

3.11.2

### Cython version

3.0.5

### Additional context

_No response_


Discussion:

Resolving patch:
diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py
--- a/Cython/Compiler/ParseTreeTransforms.py
+++ b/Cython/Compiler/ParseTreeTransforms.py
@@ -211,7 +211,7 @@ def visit_LambdaNode(self, node):
     def visit_GeneratorExpressionNode(self, node):
         # unpack a generator expression into the corresponding DefNode
         collector = YieldNodeCollector()
-        collector.visitchildren(node.loop)
+        collector.visitchildren(node.loop, attrs=None, exclude=["iterator"])
         node.def_node = Nodes.DefNode(
             node.pos, name=node.name, doc=None,
             args=[], star_arg=None, starstar_arg=None,
@@ -3145,7 +3145,7 @@ def visit_TryFinallyStatNode(self, node):
 
 class YieldNodeCollector(TreeVisitor):
 
-    def __init__(self):
+    def __init__(self, excludes=[]):
         super().__init__()
         self.yields = []
         self.returns = []
@@ -3154,9 +3154,11 @@ def __init__(self):
         self.has_return_value = False
         self.has_yield = False
         self.has_await = False
+        self.excludes = excludes
 
     def visit_Node(self, node):
-        self.visitchildren(node)
+        if node not in self.excludes:
+            self.visitchildren(node)
 
     def visit_YieldExprNode(self, node):
         self.yields.append(node)
@@ -3192,7 +3194,11 @@ def visit_LambdaNode(self, node):
         pass
 
     def visit_GeneratorExpressionNode(self, node):
-        pass
+        # node.loop iterator is evaluated outside the generator expression
+        if isinstance(node.loop, Nodes._ForInStatNode):
+            # Possibly should handle ForFromStatNode
+            # but for now do nothing
+            self.visit(node.loop.iterator)
 
     def visit_CArgDeclNode(self, node):
         # do not look into annotations
@@ -3206,6 +3212,7 @@ class MarkClosureVisitor(CythonTransform):
 
     def visit_ModuleNode(self, node):
         self.needs_closure = False
+        self.excludes = []
         self.visitchildren(node)
         return node
 
@@ -3215,7 +3222,7 @@ def visit_FuncDefNode(self, node):
         node.needs_closure = self.needs_closure
         self.needs_closure = True
 
-        collector = YieldNodeCollector()
+        collector = YieldNodeCollector(self.excludes)
         collector.visitchildren(node)
 
         if node.is_async_def:
@@ -3274,7 +3281,11 @@ def visit_ClassDefNode(self, node):
         return node
 
     def visit_GeneratorExpressionNode(self, node):
+        excludes = self.excludes
+        if isinstance(node.loop, Nodes._ForInStatNode):
+            self.excludes = [node.loop.iterator]
         node = self.visit_LambdaNode(node)
+        self.excludes = excludes
         if not isinstance(node.loop, Nodes._ForInStatNode):
             # Possibly should handle ForFromStatNode
             # but for now do nothing


Test patch:
diff --git a/tests/run/async_def.pyx b/tests/run/async_def.pyx
--- a/tests/run/async_def.pyx
+++ b/tests/run/async_def.pyx
@@ -8,15 +8,16 @@ Cython specific tests in addition to "test_coroutines_pep492.pyx"
 """
 
 
-def run_async(coro):
-    #assert coro.__class__ is types.GeneratorType
-    assert coro.__class__.__name__ in ('coroutine', '_GeneratorWrapper'), coro.__class__.__name__
+def run_async(coro, assert_type=True, send_value=None):
+    if assert_type:
+        #assert coro.__class__ is types.GeneratorType
+        assert coro.__class__.__name__ in ('coroutine', '_GeneratorWrapper'), coro.__class__.__name__
 
     buffer = []
     result = None
     while True:
         try:
-            buffer.append(coro.send(None))
+            buffer.append(coro.send(send_value))
         except StopIteration as ex:
             result = ex.value
             break
@@ -58,3 +59,46 @@ async def outer_with_nested(called):
 
     called.append('return inner')
     return inner
+
+# used in "await_in_genexpr_iterator"
+async def h(arg):
+    return [arg, arg+1]
+
+async def await_in_genexpr_iterator():
+    """
+    >>> _, x = run_async(await_in_genexpr_iterator())
+    >>> x
+    [4, 6]
+    """
+    lst = list  # obfuscate from any optimizations cython might try
+    return lst(x*2 for x in await h(2))
+
+def yield_in_genexpr_iterator():
+    """
+    Same test as await_in_genexpr_iterator but with yield.
+    (Possibly in the wrong place, but grouped with related tests)
+
+    >>> g = yield_in_genexpr_iterator()
+    >>> g.send(None)
+    >>> _, x = run_async(g, assert_type=False, send_value=[2, 3])
+    >>> x
+    [4, 6]
+    """
+    lst = list  # obfuscate from any optimizations cython might try
+    return lst(x*2 for x in (yield))
+
+def h_yield_from(arg):
+    yield
+    return [arg, arg+1]
+
+def yield_from_in_genexpr_iterator():
+    """
+    Same test as await_in_genexpr_iterator but with "yield from".
+    (Possibly in the wrong place, but grouped with related tests)
+
+    >>> _, x = run_async(yield_from_in_genexpr_iterator(), assert_type=False)
+    >>> x
+    [4, 6]
+    """
+    lst = list  # obfuscate from any optimizations cython might try
+    return lst(x*2 for x in (yield from h_yield_from(2)))