Repository: cython/cython

Issue:
[BUG] NameError with cpdef enum
### Describe the bug

cpdef enums defined in a pxd file don't work if used as a default value for a class method.

### Code to reproduce the behaviour:

I create a myenum package with the following the below three files:

In enum.pyx:

```cython
cdef class A:
    def __init__(self, MyEnum e=MyEnum.bla):
        if e == MyEnum.bla:
            print("bla")

    # def cmp(self, MyEnum e=MyEnum.bla):
        # if e == MyEnum.bla:
            # print("bla")
```

In enum.pxd:

```cython
# distutils: language = c++
cdef extern from 'enum.hpp':
    cpdef enum class MyEnum:
        bla
        blabla
```

and in enum.hpp:
```cpp
enum class MyEnum {bla, blabla};
```


### Expected behaviour

Code compile and executes fine with the cmp method commented out. But with the cmp method, I get the following error when trying to import A:

```
Cell In[1], line 1
----> 1 from myenum.enum import A

File enum.pyx:6, in init myenum.enum()

File <stringsource>:137, in EnumTypeToPy.__Pyx_Enum_MyEnum_to_py()

NameError: name 'MyEnum' is not defined
```
The issue is the default value in cmp. If e doesn't have a default value then it works as well.

### Environment

OS: Linux
Python version: 3.10.2
Cython version: 3.0.0b1


### Additional context

_No response_


Discussion:
Forgot to mention, this was working fine with cython3 alpha11.

Resolving patch:
diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py
--- a/Cython/Compiler/ModuleNode.py
+++ b/Cython/Compiler/ModuleNode.py
@@ -121,25 +121,41 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
 
     child_attrs = ["body"]
     directives = None
+    # internal - used in merging
+    pxd_stats = None
+    utility_code_stats = None
 
-    def merge_in(self, tree, scope, merge_scope=False):
+
+    def merge_in(self, tree, scope, stage, merge_scope=False):
         # Merges in the contents of another tree, and possibly scope. With the
         # current implementation below, this must be done right prior
         # to code generation.
+        # Stage is one of "pxd" or "utility" to indicate pxd file or utility
+        # code. This helps define the order.
         #
         # Note: This way of doing it seems strange -- I believe the
         # right concept is to split ModuleNode into a ModuleNode and a
         # CodeGenerator, and tell that CodeGenerator to generate code
         # from multiple sources.
         assert isinstance(self.body, Nodes.StatListNode)
+        assert stage in ('pxd', 'utility')
+
+        if self.pxd_stats is None:
+            self.pxd_stats = Nodes.StatListNode(self.body.pos, stats=[])
+            self.utility_code_stats = Nodes.StatListNode(self.body.pos, stats=[])
+            self.body.stats.insert(0, self.pxd_stats)
+            self.body.stats.insert(0, self.utility_code_stats)
+
         if scope.directives != self.scope.directives:
             # merged in nodes should keep their original compiler directives
             # (for example inline cdef functions)
             tree = Nodes.CompilerDirectivesNode(tree.pos, body=tree, directives=scope.directives)
+
+        target_stats = self.pxd_stats if stage == "pxd" else self.utility_code_stats
         if isinstance(tree, Nodes.StatListNode):
-            self.body.stats.extend(tree.stats)
+            target_stats.stats.extend(tree.stats)
         else:
-            self.body.stats.append(tree)
+            target_stats.stats.append(tree)
 
         self.scope.utility_code_list.extend(scope.utility_code_list)
 
diff --git a/Cython/Compiler/Pipeline.py b/Cython/Compiler/Pipeline.py
--- a/Cython/Compiler/Pipeline.py
+++ b/Cython/Compiler/Pipeline.py
@@ -58,7 +58,7 @@ def generate_pyx_code_stage(module_node):
 def inject_pxd_code_stage_factory(context):
     def inject_pxd_code_stage(module_node):
         for name, (statlistnode, scope) in context.pxds.items():
-            module_node.merge_in(statlistnode, scope)
+            module_node.merge_in(statlistnode, scope, stage="pxd")
         return module_node
     return inject_pxd_code_stage
 
@@ -130,7 +130,8 @@ def inject_utility_code_stage(module_node):
             tree = utilcode.get_tree(cython_scope=context.cython_scope)
             if tree:
                 module_node.merge_in(tree.with_compiler_directives(),
-                                     tree.scope, merge_scope=True)
+                                     tree.scope, stage="utility",
+                                     merge_scope=True)
         return module_node
 
     return inject_utility_code_stage


Test patch:
diff --git a/tests/run/cpdef_enums.pyx b/tests/run/cpdef_enums.pyx
--- a/tests/run/cpdef_enums.pyx
+++ b/tests/run/cpdef_enums.pyx
@@ -161,3 +161,16 @@ def test_pickle():
     True
     """
     pass
+
+def test_as_default_value(PxdEnum val=PxdEnum.RANK_1):
+    """
+    In order to work, this requires the utility code to be evaluated
+    before the function definition
+    >>> test_as_default_value()
+    True
+    >>> test_as_default_value(PxdEnum.RANK_2)
+    False
+    >>> test_as_default_value.__defaults__[0] == PxdEnum.RANK_1
+    True
+    """
+    return val == PxdEnum.RANK_1