Repository: cython/cython

Issue:
[BUG] Compilation crashes when using `const`-qualified customly fused-typed memoryview parameters for `def` function
### Describe the bug

As of Cython 0.29.33, compilation crashes:
 - when defining a `def` function with a memoryview as a parameter
 - when a custom fused type is defined using NumPy types 
 - when using `const`-qualification on a memoryview typed with this custom fused type

### Code to reproduce the behaviour:

This crashes:
```cython
cimport numpy as cnp

ctypedef fused fused_t:
    cnp.float32_t
    cnp.float64_t
    cnp.int32_t
    cnp.int64_t

def _func(
    const fused_t[:] data,
):    
    return
```

with this trace:

```
Error compiling Cython file:
------------------------------------------------------------
...
    cdef __Pyx_memviewslice memslice
    cdef Py_ssize_t itemsize
    cdef bint dtype_signed
    cdef char kind
    itemsize = -1
    cdef bint ___pyx_const int32_t_is_signed
                          ^
------------------------------------------------------------

(tree fragment):16:27: Syntax error in C variable declaration
```

<details>
<summary>Alternatives that compiles</summary>

The following snippets compiles:

```cython
ctypedef fused fused_t:
    float
    double
    int
    long

def _func(
    const fused_t[:] data,
):    
    return
```

```cython
from cython cimport numeric

def _func(
    const numeric[:] data,
):    
    return
```


```cython
cimport numpy as cnp

ctypedef fused fused_t:
    cnp.float32_t
    cnp.float64_t
    cnp.int32_t
    cnp.int64_t

cdef void _func(
    const fused_t[:] data,
):    
    return
```


</details>




### Expected behaviour

This must compile and work.

### Environment

OS: Linux
Python version 3.11.0
Cython version 0.29.33


### Additional context

First observed in https://github.com/scikit-learn/scikit-learn/pull/25041/files#r1087947278 as of https://github.com/scikit-learn/scikit-learn/pull/25041/commits/563fd00a8a729519b3d9be9a433b8df60a0f5ef0.


Discussion:
It's broken on master too. It looks fairly obvious to fix though (it's just injecting `const` into utility code somewhere it shouldn't) so I'll do it at the weekend if no-one gets there first.

Resolving patch:
diff --git a/Cython/Compiler/FusedNode.py b/Cython/Compiler/FusedNode.py
--- a/Cython/Compiler/FusedNode.py
+++ b/Cython/Compiler/FusedNode.py
@@ -1,7 +1,8 @@
 import copy
 
 from . import (ExprNodes, PyrexTypes, MemoryView,
-               ParseTreeTransforms, StringEncoding, Errors)
+               ParseTreeTransforms, StringEncoding, Errors,
+               Naming)
 from .ExprNodes import CloneNode, ProxyNode, TupleNode
 from .Nodes import FuncDefNode, CFuncDefNode, StatListNode, DefNode
 from ..Utils import OrderedSet
@@ -303,9 +304,10 @@ def _fused_instance_checks(self, normal_types, pyx_code, env):
                 """)
 
     def _dtype_name(self, dtype):
+        name = str(dtype).replace('_', '__').replace(' ', '_')
         if dtype.is_typedef:
-            return '___pyx_%s' % dtype
-        return str(dtype).replace(' ', '_')
+            name = Naming.fused_dtype_prefix + name
+        return name
 
     def _dtype_type(self, dtype):
         if dtype.is_typedef:
diff --git a/Cython/Compiler/Naming.py b/Cython/Compiler/Naming.py
--- a/Cython/Compiler/Naming.py
+++ b/Cython/Compiler/Naming.py
@@ -132,6 +132,7 @@
 error_without_exception_cname = pyrex_prefix + "error_without_exception"
 binding_cfunc    = pyrex_prefix + "binding_PyCFunctionType"
 fused_func_prefix = pyrex_prefix + 'fuse_'
+fused_dtype_prefix = pyrex_prefix + 'fused_dtype_'
 quick_temp_cname = pyrex_prefix + "temp"  # temp variable for quick'n'dirty temping
 tp_dict_version_temp = pyrex_prefix + "tp_dict_version"
 obj_dict_version_temp = pyrex_prefix + "obj_dict_version"


Test patch:
diff --git a/tests/run/fused_types.pyx b/tests/run/fused_types.pyx
--- a/tests/run/fused_types.pyx
+++ b/tests/run/fused_types.pyx
@@ -24,6 +24,13 @@ fused_type3 = cython.fused_type(int, double)
 fused_composite = cython.fused_type(fused_type2, fused_type3)
 just_float = cython.fused_type(float)
 
+ctypedef int inttypedef
+ctypedef double doubletypedef
+fused_with_typedef = cython.fused_type(inttypedef, doubletypedef)
+
+ctypedef float const_inttypedef  # misleading name
+fused_misleading_name = cython.fused_type(const_inttypedef, char)
+
 
 def test_pure():
     """
@@ -554,6 +561,26 @@ def convert_to_ptr(cython.floating x):
     elif cython.floating is double:
         return handle_double(&x)
 
+def constfused_with_typedef(const fused_with_typedef[:] x):
+    """
+    >>> constfused_with_typedef(get_array(8, 'd'))
+    5.0
+    >>> constfused_with_typedef(get_intc_array())
+    5
+    """
+    return x[5]
+
+def constfused_typedef_name_clashes(const fused_with_typedef[:] x, fused_misleading_name[:] y):
+    """
+    This'll deliberately end up with two typedefs that generate the same name in dispatch code
+    (and thus one needs to end up numbered to make it work).
+    It's mainly a compile test and the runtime part is fairly token.
+
+    >>> constfused_typedef_name_clashes(get_intc_array(), get_array(4, 'f'))
+    (5, 5.0)
+    """
+    return x[5], y[5]
+
 cdef double get_double():
     return 1.0
 cdef float get_float():