Repository: cython/cython

Issue:
[BUG] migration to cython3.0.5: redefinition of '__Pyx_Enum_<enum_name>_to_py
### Describe the bug

I'm having an issue with migrating PyVerbs package, part of the upstream rdma-core, to cython3.
We've been working for a few years with Cython 0.2X.YY versions without the below issues.
And I didn't find a solved issue for this or any relevant notes in the [migration document](https://docs.cython.org/en/latest/src/userguide/migrating_to_cy30.htm).

```
<path-to-rdma-core>/rdma-core/build-fc39/pyverbs/srq.c:5528:18: error: redefinition of '__Pyx_Enum_ibv_ops_wr_opcode_to_py'
5528 | static PyObject *__Pyx_Enum_ibv_ops_wr_opcode_to_py(enum ibv_ops_wr_opcode __pyx_v_c_val) {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<path-to-rdma-core>/rdma-core/build-fc39/pyverbs/srq.c:4511:18: note: previous definition of '__Pyx_Enum_ibv_ops_wr_opcode_to_py' with type 'PyObject *(enum ibv_ops_wr_opcode)' {aka 'struct _object *(enum ibv_ops_wr_opcode)'}
4511 | static PyObject *__Pyx_Enum_ibv_ops_wr_opcode_to_py(enum ibv_ops_wr_opcode __pyx_v_c_val) {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[420/575] Building C object pyverbs/CMakeFiles/cq.cpython-312-x86_64-linux-gnu.dir/cq.c.o
```






### Code to reproduce the behaviour:

Go to [rdma-core](https://github.com/linux-rdma/rdma-core) project and checkout this PR branch:[Update to fc39]( https://github.com/linux-rdma/rdma-core/pull/1405)
Then, to build, and reproduce, you can do:
```
buildlib/cbuild build-images fc39
buildlib/cbuild make fc39
```

### Expected behaviour

I expect the compilation to pass as it's been passing on any Cython 0.29.XY versions.
The cythonization process creates a duplicate enum definition in the C files due to the "inlucde <pxd_file_name>.pxd" lines.

### OS

Linux

### Python version

3.10.8

### Cython version

3.0.5 (actually from any cython3 version)

### Additional context

_No response_


Discussion:
That's almost certainly a bug rather than something that should be in the migration notes.
My suspicion (based on a quick look through the code) is that this is to do with duplicating enums via both `include` and `cimport`. That isn't based on any actual debugging so could well be wrong.

If that's the case it may be difficult for Cython to deal with - it'd be better to raise an error earlier rather than generating invalid C code though.
> My suspicion (based on a quick look through the code) is that this is to do with duplicating enums via both `include` and `cimport`. That isn't based on any actual debugging so could well be wrong.

@da-woods Firstly, thanks for your quick response! 
The "include" you see in some of the pxd files (such as "include 'libibverbs_enums.pxd'" in pyverbs/libibverbs.pxd file) is made to allow the required enums to be accessible by relevant structs or function definitions.
What happens, from what I understand, is when other pyx files cimport from both libibverbs.pxd and libibverbs_enums.pxd, the enum defs get duplicated by the cythonization process.

But this behavior was protected and worked for years, up until cython3.
So I consider it as a bug.

BTW, I tried to prevent that by removing the direct enums cimports and cimport from teh files that already includes them.
In the example above, instead of doing:
``` cython
cimport pyverbs.libibverbs_enums as e
cimport pyverbs.libibverbs as v
#then: v.<some_struct>; e.<some_enum>
```
I did:
```
cimport pyverbs.libibverbs as v
#then: "indirectly" accessing by: v.<some_struct>; v.<some_enum>

```

The compilation succeeds, but I get a lot of UserWarnings in runtime, such as:
```
$ python3 tests/run_tests.py -v --help
<frozen importlib._bootstrap>:241: UserWarning: enum class rdma_port_space not importable from pyverbs.librdmacm. You are probably using a cpdef enum declared in a .pxd file that does not have a .py  or .pyx file.
<frozen importlib._bootstrap>:241: UserWarning: enum class ibv_send_flags not importable from pyverbs.libibverbs. You are probably using a cpdef enum declared in a .pxd file that does not have a .py  or .pyx file.
<frozen importlib._bootstrap>:241: UserWarning: enum class ibv_wr_opcode not importable from pyverbs.libibverbs. You are probably using a cpdef enum declared in a .pxd file that does not have a .py  or .pyx file.
<frozen importlib._bootstrap>:241: UserWarning: enum class ibv_ops_wr_opcode not importable from pyverbs.libibverbs. You are probably using a cpdef enum declared in a .pxd file that does not have a .py  or .pyx file.
<...........>
```
While every enum.pxd file has a corresponding enum.pyx file (usually as a symlink)

@da-woods  Could you please take a look at this issue?
It's blocking Linux rdma-core subsystem from being built on any OS with cython3+

Resolving patch:
diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py
--- a/Cython/Compiler/PyrexTypes.py
+++ b/Cython/Compiler/PyrexTypes.py
@@ -4302,7 +4302,7 @@ class EnumMixin:
 
     def create_enum_to_py_utility_code(self, env):
         from .UtilityCode import CythonUtilityCode
-        self.to_py_function = "__Pyx_Enum_%s_to_py" % self.name
+        self.to_py_function = "__Pyx_Enum_%s_to_py" % type_identifier(self)
         if self.entry.scope != env.global_scope():
             module_name = self.entry.scope.qualified_name
         else:


Test patch:
diff --git a/tests/run/cpdef_enums_import.srctree b/tests/run/cpdef_enums_import.srctree
--- a/tests/run/cpdef_enums_import.srctree
+++ b/tests/run/cpdef_enums_import.srctree
@@ -8,7 +8,7 @@ from Cython.Build.Dependencies import cythonize
 from distutils.core import setup
 
 setup(
-  ext_modules = cythonize(["enums.pyx", "no_enums.pyx"]),
+  ext_modules = cythonize(["enums.pyx", "enums_same_name.pyx", "no_enums.pyx"]),
 )
 
 ######## enums.pyx ########
@@ -28,6 +28,14 @@ cpdef enum NamedEnumType:
 
 cpdef foo()
 
+######## enums_same_name.pyx ############
+
+######## enums_same_name.pxd ############
+
+# Note - same name as enums.pxd but shouldn't conflict
+cpdef enum NamedEnumType:
+    Value = 1
+
 ######## enums_without_pyx.pxd #####
 
 cpdef enum EnumTypeNotInPyx:
@@ -37,10 +45,16 @@ cpdef enum EnumTypeNotInPyx:
 
 from enums cimport *
 from enums_without_pyx cimport *
+cimport enums_same_name
 
 def get_named_enum_value():
     return NamedEnumType.NamedEnumValue
 
+def get_from_enums_same_name():
+    # This should not generate conflicting "to py" functions with the other
+    # identically named enum from a different pxd file.
+    return enums_same_name.NamedEnumType.Value
+
 def get_named_without_pyx():
     # This'll generate a warning but return a c int
     return EnumTypeNotInPyx.AnotherEnumValue
@@ -49,6 +63,7 @@ def get_named_without_pyx():
 
 # We can import enums with a star import.
 from enums import *
+import enums_same_name
 
 print(dir())
 assert 'BAR' in dir() and 'FOO' in dir()
@@ -64,3 +79,5 @@ assert no_enums.get_named_enum_value() == NamedEnumType.NamedEnumValue
 # In this case the enum isn't accessible from Python (by design)
 # but the conversion to Python goes through a reasonable fallback
 assert no_enums.get_named_without_pyx() == 500
+
+assert no_enums.get_from_enums_same_name() == enums_same_name.NamedEnumType.Value