Repository: cython/cython

Issue:
[BUG] CompileError with `const fused_t[:, ::1]` argument 
**Describe the bug**
Addition of a `const` identifier to the fused type declaration crashes the compiler. This is already reported in #3222 but marked as solved. Hence I am not sure whether this is related. Without the `const` it compiles fine.

**To Reproduce**
Code to reproduce the behaviour:

```cython
%%cython -3 -a
import cython
cimport numpy as cnp
from libc.math cimport isfinite

ctypedef fused lapack_t:
    float
    double
    float complex
    double complex
ctypedef float complex float_complex
ctypedef fused lapack_cz_t:
    float complex
    double complex
ctypedef fused lapack_sd_t:
    float
    double

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef inline bint array_chkfinite(const lapack_t[:, ::1]A):
    """

    """
    cdef size_t n = A.shape[0]
    cdef int r, c
    cdef lapack_t entry
    if lapack_t in lapack_cz_t:
        for r in xrange(n):
            for c in xrange(n):
                entry = A[r, c]
                if not(isfinite(entry.real) and isfinite(entry.imag)):
                    return False
    else:
        for r in xrange(n):
            for c in xrange(n):
                if isfinite(A[r, c]):
                    return False
    return True
```

This throws the following error 

<details> 

```bash
Error compiling Cython file:
------------------------------------------------------------
...
    float
    double

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef inline bint array_chkfinite(const lapack_t[:, ::1]A):
                                                ^
------------------------------------------------------------

C:\Users\Ilhan Polat\.ipython\cython\_cython_magic_d06ba7f2fb4592a1e2bcd75d12e69286.pyx:21:49: Compiler crash in AnalyseDeclarationsTransform

File 'ModuleNode.py', line 124, in analyse_declarations: ModuleNode(_cython_magic_d06ba7f2fb4592a1e2bcd75d12e69286.pyx:1:0,
    full_module_name = '_cython_magic_d06ba7f2fb4592a1e2bcd75d12e69286')
File 'Nodes.py', line 431, in analyse_declarations: StatListNode(_cython_magic_d06ba7f2fb4592a1e2bcd75d12e69286.pyx:1:0)
File 'Nodes.py', line 431, in analyse_declarations: StatListNode(_cython_magic_d06ba7f2fb4592a1e2bcd75d12e69286.pyx:21:6)
File 'Nodes.py', line 375, in analyse_declarations: CompilerDirectivesNode(_cython_magic_d06ba7f2fb4592a1e2bcd75d12e69286.pyx:21:6)
File 'Nodes.py', line 431, in analyse_declarations: StatListNode(_cython_magic_d06ba7f2fb4592a1e2bcd75d12e69286.pyx:21:6)
File 'Nodes.py', line 2354, in analyse_declarations: CFuncDefNode(_cython_magic_d06ba7f2fb4592a1e2bcd75d12e69286.pyx:21:6,
    decorators = [...]/0,
    doc = '\n\n    ',
    modifiers = [...]/1,
    overridable = 1,
    visibility = 'private')
File 'Nodes.py', line 681, in analyse: CFuncDeclaratorNode(_cython_magic_d06ba7f2fb4592a1e2bcd75d12e69286.pyx:21:33,
    calling_convention = '',
    overridable = 1)
File 'Nodes.py', line 891, in analyse: CArgDeclNode(_cython_magic_d06ba7f2fb4592a1e2bcd75d12e69286.pyx:21:34,
    is_generic = 1,
    outer_attrs = [...]/2)
File 'Nodes.py', line 1091, in analyse: MemoryViewSliceTypeNode(_cython_magic_d06ba7f2fb4592a1e2bcd75d12e69286.pyx:21:49,
    is_arg = True,
    name = 'memoryview')

Compiler crash traceback from this point on:
  File "c:\users\ilhan polat\appdata\local\programs\python\python38\lib\site-packages\Cython\Compiler\Nodes.py", line 1091, in analyse
    self.type = PyrexTypes.MemoryViewSliceType(base_type, axes_specs)
  File "c:\users\ilhan polat\appdata\local\programs\python\python38\lib\site-packages\Cython\Compiler\PyrexTypes.py", line 625, in __init__
    self.dtype_name = Buffer.mangle_dtype_name(self.dtype)
  File "c:\users\ilhan polat\appdata\local\programs\python\python38\lib\site-packages\Cython\Compiler\Buffer.py", line 631, in mangle_dtype_name
    return prefix + dtype.specialization_name()
  File "c:\users\ilhan polat\appdata\local\programs\python\python38\lib\site-packages\Cython\Compiler\PyrexTypes.py", line 57, in specialization_name
    common_subs = (self.empty_declaration_code()
  File "c:\users\ilhan polat\appdata\local\programs\python\python38\lib\site-packages\Cython\Compiler\PyrexTypes.py", line 51, in empty_declaration_code
    self._empty_declaration = self.declaration_code('')
  File "c:\users\ilhan polat\appdata\local\programs\python\python38\lib\site-packages\Cython\Compiler\PyrexTypes.py", line 1582, in declaration_code
    return self.const_base_type.declaration_code("const %s" % entity_code, for_display, dll_linkage, pyrex)
  File "c:\users\ilhan polat\appdata\local\programs\python\python38\lib\site-packages\Cython\Compiler\PyrexTypes.py", line 1649, in declaration_code
    raise Exception("This may never happen, please report a bug")
Exception: This may never happen, please report a bug
```
</details>

**Environment (please complete the following information):**
 - OS:  Windows 10.1904
 - Python version 3.8.0
 - Cython version 0.29.21



Discussion:

Resolving patch:
diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py
--- a/Cython/Compiler/PyrexTypes.py
+++ b/Cython/Compiler/PyrexTypes.py
@@ -1561,6 +1561,7 @@ def __hash__(self):
 class CConstType(BaseType):
 
     is_const = 1
+    subtypes = ['const_base_type']
 
     def __init__(self, const_base_type):
         self.const_base_type = const_base_type


Test patch:
diff --git a/tests/errors/fused_types.pyx b/tests/errors/fused_types.pyx
--- a/tests/errors/fused_types.pyx
+++ b/tests/errors/fused_types.pyx
@@ -1,4 +1,5 @@
 # mode: error
+# ticket: 1772
 
 cimport cython
 from cython import fused_type
@@ -64,17 +65,30 @@ ctypedef fused fused2:
 func(x, y)
 
 
+cdef fused mix_const_t:
+    int
+    const int
+
+cdef cdef_func_with_mix_const_type(mix_const_t val):
+    print(val)
+
+# Mixing const and non-const type makes fused type ambiguous
+cdef_func_with_mix_const_type(1)
+
+
 _ERRORS = u"""
-10:15: fused_type does not take keyword arguments
-15:33: Type specified multiple times
-26:0: Invalid use of fused types, type cannot be specialized
-26:4: Not enough types specified to specialize the function, int2_t is still fused
+11:15: fused_type does not take keyword arguments
+16:33: Type specified multiple times
 27:0: Invalid use of fused types, type cannot be specialized
 27:4: Not enough types specified to specialize the function, int2_t is still fused
-28:16: Call with wrong number of arguments (expected 2, got 1)
-29:16: Call with wrong number of arguments (expected 2, got 3)
-36:6: Invalid base type for memoryview slice: int *
-39:0: Fused lambdas not allowed
-42:5: Fused types not allowed here
-45:9: Fused types not allowed here
+28:0: Invalid use of fused types, type cannot be specialized
+28:4: Not enough types specified to specialize the function, int2_t is still fused
+29:16: Call with wrong number of arguments (expected 2, got 1)
+30:16: Call with wrong number of arguments (expected 2, got 3)
+37:6: Invalid base type for memoryview slice: int *
+40:0: Fused lambdas not allowed
+43:5: Fused types not allowed here
+46:9: Fused types not allowed here
+76:0: Invalid use of fused types, type cannot be specialized
+76:29: ambiguous overloaded method
 """
diff --git a/tests/memoryview/numpy_memoryview_readonly.pyx b/tests/memoryview/numpy_memoryview_readonly.pyx
--- a/tests/memoryview/numpy_memoryview_readonly.pyx
+++ b/tests/memoryview/numpy_memoryview_readonly.pyx
@@ -1,10 +1,14 @@
 # mode: run
 # tag: readonly, const, numpy
+# ticket: 1772
 
 import numpy as np
+cimport cython
 
-def new_array():
-    return np.arange(10).astype('float')
+def new_array(dtype='float', writeable=True):
+    array = np.arange(10, dtype=dtype)
+    array.setflags(write=writeable)
+    return array
 
 ARRAY = new_array()
 
@@ -124,3 +128,45 @@ def test_copy():
     rw[1] = 2
     rw2[2] = 2
     return rw[0], rw[1], rw[2], rw2[0], rw2[1], rw2[2]
+
+
+cdef getmax_floating(const cython.floating[:] x):
+    """Function with fused type, should work with both ro and rw memoryviews"""
+    cdef cython.floating max_val = - float('inf')
+    for val in x:
+        if val > max_val:
+            max_val = val
+    return max_val
+
+
+def test_mmview_const_fused_cdef():
+    """Test cdef function with const fused type memory view as argument.
+
+    >>> test_mmview_const_fused_cdef()
+    """
+    cdef float[:] data_rw = new_array(dtype='float32')
+    assert getmax_floating(data_rw) == 9
+
+    cdef const float[:] data_ro = new_array(dtype='float32', writeable=False)
+    assert getmax_floating(data_ro) == 9
+
+
+def test_mmview_const_fused_def(const cython.floating[:] x):
+    """Test def function with const fused type memory view as argument.
+
+    With read-write numpy array:
+
+    >>> test_mmview_const_fused_def(new_array('float32', writeable=True))
+    0.0
+    >>> test_mmview_const_fused_def(new_array('float64', writeable=True))
+    0.0
+
+    With read-only numpy array:
+
+    >>> test_mmview_const_fused_def(new_array('float32', writeable=False))
+    0.0
+    >>> test_mmview_const_fused_def(new_array('float64', writeable=False))
+    0.0
+    """
+    cdef cython.floating result = x[0]
+    return result
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
@@ -1,4 +1,5 @@
 # mode: run
+# ticket: 1772
 
 cimport cython
 from cython.view cimport array
@@ -363,6 +364,22 @@ def test_fused_memslice_dtype_repeated_2(cython.floating[:] array1, cython.float
     """
     print cython.typeof(array1), cython.typeof(array2), cython.typeof(array3)
 
+def test_fused_const_memslice_dtype_repeated(const cython.floating[:] array1, cython.floating[:] array2):
+    """Test fused types memory view with one being const
+
+    >>> sorted(test_fused_const_memslice_dtype_repeated.__signatures__)
+    ['double', 'float']
+
+    >>> test_fused_const_memslice_dtype_repeated(get_array(8, 'd'), get_array(8, 'd'))
+    const double[:] double[:]
+    >>> test_fused_const_memslice_dtype_repeated(get_array(4, 'f'), get_array(4, 'f'))
+    const float[:] float[:]
+    >>> test_fused_const_memslice_dtype_repeated(get_array(8, 'd'), get_array(4, 'f'))
+    Traceback (most recent call last):
+    ValueError: Buffer dtype mismatch, expected 'double' but got 'float'
+    """
+    print cython.typeof(array1), cython.typeof(array2)
+
 def test_cython_numeric(cython.numeric arg):
     """
     Test to see whether complex numbers have their utility code declared
@@ -388,6 +405,18 @@ def test_index_fused_args(cython.floating f, ints_t i):
     """
     _test_index_fused_args[cython.floating, ints_t](f, i)
 
+cdef _test_index_const_fused_args(const cython.floating f, const ints_t i):
+    print(cython.typeof(f), cython.typeof(i))
+
+def test_index_const_fused_args(const cython.floating f, const ints_t i):
+    """Test indexing function implementation with const fused type args
+
+    >>> import cython
+    >>> test_index_const_fused_args[cython.double, cython.int](2.0, 3)
+    ('const double', 'const int')
+    """
+    _test_index_const_fused_args[cython.floating, ints_t](f, i)
+
 
 def test_composite(fused_composite x):
     """
@@ -404,6 +433,30 @@ def test_composite(fused_composite x):
         return 2 * x
 
 
+cdef cdef_func_const_fused_arg(const cython.floating val,
+                               const fused_type1 * ptr_to_const,
+                               const (cython.floating *) const_ptr):
+    print(val, cython.typeof(val))
+    print(ptr_to_const[0], cython.typeof(ptr_to_const[0]))
+    print(const_ptr[0], cython.typeof(const_ptr[0]))
+
+    ptr_to_const = NULL  # pointer is not const, value is const
+    const_ptr[0] = 0.0  # pointer is const, value is not const
+
+def test_cdef_func_with_const_fused_arg():
+    """Test cdef function with const fused type argument
+
+    >>> test_cdef_func_with_const_fused_arg()
+    (0.0, 'const float')
+    (1, 'const int')
+    (2.0, 'float')
+    """
+    cdef float arg0 = 0.0
+    cdef int arg1 = 1
+    cdef float arg2 = 2.0
+    cdef_func_const_fused_arg(arg0, &arg1, &arg2)
+
+
 ### see GH3642 - presence of cdef inside "unrelated" caused a type to be incorrectly inferred
 cdef unrelated(cython.floating x):
     cdef cython.floating t = 1