Repository: cython/cython

Issue:
[BUG] no member named ‘__pyx_outer_scope’
<!--
**PLEASE READ THIS FIRST:**
- Do not use the bug and feature tracker for support requests. Use the `cython-users` mailing list instead.
- Did you search for similar issues already? Please do, it helps to save us precious time that we otherwise could not invest into development.
- Did you try the latest master branch or pre-release? It might already have what you want to report. Also see the [Changelog](https://github.com/cython/cython/blob/master/CHANGES.rst) regarding recent changes.
-->

**Describe the bug**
Cython generates c code which fails to compile

**To Reproduce**
Code to reproduce the behaviour:

```cython
# test.py
def func(arg1, arg2):
    @arg1([x for x in arg2])
    async def method():
        pass
```

```console
$ cython -3 test.py
$ cc -I/usr/include/python3.9 -c -o test.o test.c
```

```
test.c: In function ‘__pyx_pf_4test_func’:
test.c:2209:34: error: ‘struct __pyx_obj_4test___pyx_scope_struct__func’ has no member named ‘__pyx_outer_scope’
 2209 |     if (unlikely(!__pyx_cur_scope->__pyx_outer_scope->__pyx_v_arg2)) { __Pyx_RaiseClosureNameError("arg2"); __PYX_ERR(0, 2, __pyx_L1_error) }
      |                                  ^~
test.c:994:43: note: in definition of macro ‘unlikely’
  994 |   #define unlikely(x) __builtin_expect(!!(x), 0)
      |                                           ^
test.c:2210:49: error: ‘struct __pyx_obj_4test___pyx_scope_struct__func’ has no member named ‘__pyx_outer_scope’
 2210 |     if (likely(PyList_CheckExact(__pyx_cur_scope->__pyx_outer_scope->__pyx_v_arg2)) || PyTuple_CheckExact(__pyx_cur_scope->__pyx_outer_scope->__pyx_v_arg2)) {
      |                                                 ^~
test.c:993:43: note: in definition of macro ‘likely’
  993 |   #define likely(x)   __builtin_expect(!!(x), 1)
      |                                           ^
/usr/include/python3.9/object.h:130:42: note: in expansion of macro ‘_PyObject_CAST_CONST’
  130 | #define Py_IS_TYPE(ob, type) _Py_IS_TYPE(_PyObject_CAST_CONST(ob), type)
      |                                          ^~~~~~~~~~~~~~~~~~~~
/usr/include/python3.9/listobject.h:26:31: note: in expansion of macro ‘Py_IS_TYPE’
   26 | #define PyList_CheckExact(op) Py_IS_TYPE(op, &PyList_Type)
      |                               ^~~~~~~~~~
test.c:2210:16: note: in expansion of macro ‘PyList_CheckExact’
 2210 |     if (likely(PyList_CheckExact(__pyx_cur_scope->__pyx_outer_scope->__pyx_v_arg2)) || PyTuple_CheckExact(__pyx_cur_scope->__pyx_outer_scope->__pyx_v_arg2)) {
      |                ^~~~~~~~~~~~~~~~~
In file included from /usr/include/python3.9/pytime.h:6,
                 from /usr/include/python3.9/Python.h:94,
                 from test.c:16:
test.c:2210:122: error: ‘struct __pyx_obj_4test___pyx_scope_struct__func’ has no member named ‘__pyx_outer_scope’
 2210 |     if (likely(PyList_CheckExact(__pyx_cur_scope->__pyx_outer_scope->__pyx_v_arg2)) || PyTuple_CheckExact(__pyx_cur_scope->__pyx_outer_scope->__pyx_v_arg2)) {
      |                                                                                                                          ^~
/usr/include/python3.9/object.h:113:53: note: in definition of macro ‘_PyObject_CAST_CONST’
  113 | #define _PyObject_CAST_CONST(op) ((const PyObject*)(op))
      |                                                     ^~
/usr/include/python3.9/tupleobject.h:28:32: note: in expansion of macro ‘Py_IS_TYPE’
   28 | #define PyTuple_CheckExact(op) Py_IS_TYPE(op, &PyTuple_Type)
      |                                ^~~~~~~~~~
test.c:2210:88: note: in expansion of macro ‘PyTuple_CheckExact’
 2210 |     if (likely(PyList_CheckExact(__pyx_cur_scope->__pyx_outer_scope->__pyx_v_arg2)) || PyTuple_CheckExact(__pyx_cur_scope->__pyx_outer_scope->__pyx_v_arg2)) {
      |                                                                                        ^~~~~~~~~~~~~~~~~~
test.c:2211:34: error: ‘struct __pyx_obj_4test___pyx_scope_struct__func’ has no member named ‘__pyx_outer_scope’
 2211 |       __pyx_t_4 = __pyx_cur_scope->__pyx_outer_scope->__pyx_v_arg2; __Pyx_INCREF(__pyx_t_4); __pyx_t_5 = 0;
      |                                  ^~
test.c:2214:67: error: ‘struct __pyx_obj_4test___pyx_scope_struct__func’ has no member named ‘__pyx_outer_scope’
 2214 |       __pyx_t_5 = -1; __pyx_t_4 = PyObject_GetIter(__pyx_cur_scope->__pyx_outer_scope->__pyx_v_arg2); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 2, __pyx_L1_error)
      |                                                                   ^~
test.c:2250:40: error: ‘struct __pyx_obj_4test___pyx_scope_struct__func’ has no member named ‘__pyx_7genexpr__pyx_v_x’
 2250 |       __Pyx_XDECREF_SET(__pyx_cur_scope->__pyx_7genexpr__pyx_v_x, __pyx_t_7);
      |                                        ^~
test.c:1119:38: note: in definition of macro ‘__Pyx_XDECREF_SET’
 1119 |         PyObject *tmp = (PyObject *) r;\
      |                                      ^
test.c:2250:40: error: ‘struct __pyx_obj_4test___pyx_scope_struct__func’ has no member named ‘__pyx_7genexpr__pyx_v_x’
 2250 |       __Pyx_XDECREF_SET(__pyx_cur_scope->__pyx_7genexpr__pyx_v_x, __pyx_t_7);
      |                                        ^~
test.c:1120:9: note: in definition of macro ‘__Pyx_XDECREF_SET’
 1120 |         r = v; __Pyx_XDECREF(tmp);\
      |         ^
test.c:2253:79: error: ‘struct __pyx_obj_4test___pyx_scope_struct__func’ has no member named ‘__pyx_7genexpr__pyx_v_x’
 2253 |       if (unlikely(__Pyx_ListComp_Append(__pyx_t_3, (PyObject*)__pyx_cur_scope->__pyx_7genexpr__pyx_v_x))) __PYX_ERR(0, 2, __pyx_L1_error)
      |                                                                               ^~
test.c:994:43: note: in definition of macro ‘unlikely’
  994 |   #define unlikely(x) __builtin_expect(!!(x), 0)
      |                                           ^
```

**Expected behavior**
The code compiles without errors

**Environment (please complete the following information):**
 - OS: Linux
 - Python 3.9.6
 - Cython version 0.29.24 and Cython version 3.0.0a9
 - cc (GCC) 11.1.0

**Additional context**

This works:

```cython
# test.py
def func(arg1, arg2):
    list_ = [x for x in arg2]
    @arg1(list_)
    async def method():
        pass
```



Discussion:
If doesn't specifically look to be related to `async` - you get the same issue with a regular def function.

I suspect the decorator is just analysed in the scope of `method` at some point rather than in the scope of `func`.

Resolving patch:
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -1750,6 +1750,8 @@ class FuncDefNode(StatNode, BlockNode):
     code_object = None
     return_type_annotation = None
 
+    outer_attrs = None  # overridden by some derived classes - to be visited outside the node's scope
+
     def analyse_default_values(self, env):
         default_seen = 0
         for arg in self.args:
diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py
--- a/Cython/Compiler/ParseTreeTransforms.py
+++ b/Cython/Compiler/ParseTreeTransforms.py
@@ -2054,8 +2054,6 @@ def visit_FuncDefNode(self, node):
     def visit_DefNode(self, node):
         node = self.visit_FuncDefNode(node)
         env = self.current_env()
-        if isinstance(node, Nodes.DefNode) and node.is_wrapper:
-            env = env.parent_scope
         if (not isinstance(node, Nodes.DefNode) or
                 node.fused_py_func or node.is_generator_body or
                 not node.needs_assignment_synthesis(env)):
diff --git a/Cython/Compiler/Visitor.py b/Cython/Compiler/Visitor.py
--- a/Cython/Compiler/Visitor.py
+++ b/Cython/Compiler/Visitor.py
@@ -370,8 +370,10 @@ def exit_scope(self):
         self.env_stack.pop()
 
     def visit_FuncDefNode(self, node):
+        outer_attrs = node.outer_attrs
+        self.visitchildren(node, attrs=outer_attrs)
         self.enter_scope(node, node.local_scope)
-        self._process_children(node)
+        self.visitchildren(node, attrs=None, exclude=outer_attrs)
         self.exit_scope()
         return node
 


Test patch:
diff --git a/tests/run/decorators.pyx b/tests/run/decorators.pyx
--- a/tests/run/decorators.pyx
+++ b/tests/run/decorators.pyx
@@ -61,3 +61,23 @@ a = A()
 @a.decorate
 def i(x):
     return x - 1
+
+def append_to_list_decorator(lst):
+    def do_append_to_list_dec(func):
+        def new_func():
+            return lst + func()
+        return new_func
+    return do_append_to_list_dec
+
+def outer(arg1, arg2):
+    """
+    ensure decorators are analysed in the correct scope
+    https://github.com/cython/cython/issues/4367
+    mainly intended as a compile-time test (but it does run...)
+    >>> outer(append_to_list_decorator, [1,2,3])
+    [1, 2, 3, 4]
+    """
+    @arg1([x for x in arg2])
+    def method():
+        return [4]
+    return method()