Repository: cython/cython

Issue:
Spurious "redeclared" warning with fused types and typedef
The following code:

```cython
ctypedef unsigned char npy_uint8
ctypedef unsigned short npy_uint16


ctypedef fused Foo:
    npy_uint8

ctypedef fused Bar:
    npy_uint8
    npy_uint16


def baz(Foo[:] a, Bar[:, :] b):
    pass
```

Seems like it should compile without warnings or errors.  However, in practice, it produces a spurious "redeclared" warning:

```
$ venv/bin/cython -3 example.pyx 
warning: __pyxutil:16:4: '___pyx_npy_uint8' redeclared !!!
```

From hacking on the code a little to make it spit out the full context for errors, it looks like the declaration it's choking on is from some code that was auto-generated internally as part of the compilation process and was pseudo-named `__pyxutil`:

```cython
cdef extern from *:
    void __pyx_PyErr_Clear "PyErr_Clear" ()
    type __Pyx_ImportNumPyArrayTypeIfAvailable()
    int __Pyx_Is_Little_Endian()
    __Pyx_memviewslice __Pyx_PyObject_to_MemoryviewSlice_ds_nn___pyx_t_15fused_redeclare_npy_uint8(object, int)
    __Pyx_memviewslice __Pyx_PyObject_to_MemoryviewSlice_dsds_nn___pyx_t_15fused_redeclare_npy_uint8(object, int)
    __Pyx_memviewslice __Pyx_PyObject_to_MemoryviewSlice_dsds_nn___pyx_t_15fused_redeclare_npy_uint16(object, int)
    
    ctypedef struct __Pyx_memviewslice:
        void *memview
    
    void __PYX_XDEC_MEMVIEW(__Pyx_memviewslice *, int have_gil)
    bint __pyx_memoryview_check(object)
    ctypedef unsigned char ___pyx_npy_uint8 "__pyx_t_15fused_redeclare_npy_uint8"
    ctypedef unsigned char ___pyx_npy_uint8 "__pyx_t_15fused_redeclare_npy_uint8"
    ctypedef unsigned short ___pyx_npy_uint16 "__pyx_t_15fused_redeclare_npy_uint16"
```

I think this is due to there being code for deduplicating integer types, but no code for deduplicating typedefs:

https://github.com/cython/cython/blob/00c1dc96af0e44741dc777f96a2eb5769f572bb3/Cython/Compiler/FusedNode.py#L510-L529


Discussion:

Resolving patch:
diff --git a/Cython/Compiler/FusedNode.py b/Cython/Compiler/FusedNode.py
--- a/Cython/Compiler/FusedNode.py
+++ b/Cython/Compiler/FusedNode.py
@@ -507,20 +507,22 @@ def _buffer_declarations(self, pyx_code, decl_code, all_buffer_types, pythran_ty
                 ndarray = __Pyx_ImportNumPyArrayTypeIfAvailable()
             """)
 
+        seen_typedefs = set()
         seen_int_dtypes = set()
         for buffer_type in all_buffer_types:
             dtype = buffer_type.dtype
+            dtype_name = self._dtype_name(dtype)
             if dtype.is_typedef:
-                 #decl_code.putln("ctypedef %s %s" % (dtype.resolve(),
-                 #                                    self._dtype_name(dtype)))
-                decl_code.putln('ctypedef %s %s "%s"' % (dtype.resolve(),
-                                                         self._dtype_name(dtype),
-                                                         dtype.empty_declaration_code()))
+                if dtype_name not in seen_typedefs:
+                    seen_typedefs.add(dtype_name)
+                    decl_code.putln(
+                        'ctypedef %s %s "%s"' % (dtype.resolve(), dtype_name,
+                                                 dtype.empty_declaration_code()))
 
             if buffer_type.dtype.is_int:
                 if str(dtype) not in seen_int_dtypes:
                     seen_int_dtypes.add(str(dtype))
-                    pyx_code.context.update(dtype_name=self._dtype_name(dtype),
+                    pyx_code.context.update(dtype_name=dtype_name,
                                             dtype_type=self._dtype_type(dtype))
                     pyx_code.local_variable_declarations.put_chunk(
                         u"""


Test patch:
diff --git a/tests/compile/fused_redeclare_T3111.pyx b/tests/compile/fused_redeclare_T3111.pyx
new file mode 100644
--- /dev/null
+++ b/tests/compile/fused_redeclare_T3111.pyx
@@ -0,0 +1,39 @@
+# ticket: 3111
+# mode: compile
+# tag: warnings
+
+ctypedef unsigned char npy_uint8
+ctypedef unsigned short npy_uint16
+
+
+ctypedef fused dtype_t:
+    npy_uint8
+
+ctypedef fused dtype_t_out:
+    npy_uint8
+    npy_uint16
+
+
+def foo(dtype_t[:] a, dtype_t_out[:, :] b):
+    pass
+
+
+# The primary thing we're trying to test here is the _absence_ of the warning
+# "__pyxutil:16:4: '___pyx_npy_uint8' redeclared".  The remaining warnings are
+# unrelated to this test.
+_WARNINGS = """
+22:10: 'cpdef_method' redeclared
+33:10: 'cpdef_cname_method' redeclared
+446:72: Argument evaluation order in C function call is undefined and may not be as expected
+446:72: Argument evaluation order in C function call is undefined and may not be as expected
+749:34: Argument evaluation order in C function call is undefined and may not be as expected
+749:34: Argument evaluation order in C function call is undefined and may not be as expected
+943:27: Ambiguous exception value, same as default return value: 0
+943:27: Ambiguous exception value, same as default return value: 0
+974:29: Ambiguous exception value, same as default return value: 0
+974:29: Ambiguous exception value, same as default return value: 0
+1002:46: Ambiguous exception value, same as default return value: 0
+1002:46: Ambiguous exception value, same as default return value: 0
+1092:29: Ambiguous exception value, same as default return value: 0
+1092:29: Ambiguous exception value, same as default return value: 0
+"""