Repository: cython/cython

Issue:
[BUG] function with fused type default argument causes gcc error
### Describe the bug

In a function taking a fused type pointer, setting the default argument of that parameter to `NULL` causes a gcc compiler error: `error: invalid conversion from ‘void*’ to ‘double*’ [-fpermissive]`. The example works fine using cython 0.29.36.

```
$ cythonize -i foo.pyx 
running build_ext
building 'foo' extension
creating /home/berneluk/cython3test/tmpy52hsl6q/home
creating /home/berneluk/cython3test/tmpy52hsl6q/home/berneluk
creating /home/berneluk/cython3test/tmpy52hsl6q/home/berneluk/cython3test
x86_64-linux-gnu-gcc -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I/home/berneluk/cython3test/.venv/include -I/usr/include/python3.10 -c /home/berneluk/cython3test/foo.cpp -o /home/berneluk/cython3test/tmpy52hsl6q/home/berneluk/cython3test/foo.o
/home/berneluk/cython3test/foo.cpp: In function ‘int __pyx_pymod_exec_foo(PyObject*)’:
/home/berneluk/cython3test/foo.cpp:2200:14: error: invalid conversion from ‘void*’ to ‘double*’ [-fpermissive]
 2200 |   __pyx_k_ = __pyx_t_2;
      |              ^~~~~~~~~
      |              |
      |              void*
error: command '/usr/bin/x86_64-linux-gnu-gcc' failed with exit code 1
```

### Code to reproduce the behaviour:

```cython
# distutils: language=c++

ctypedef fused bar:
    double

cdef foo(bar* b=NULL):
    pass
```


### Expected behaviour

The code should compile.

### OS

Linux

### Python version

3.10.6

### Cython version

3.0.0

### Additional context

In cython 0.29.36 this code compiles as expected. I looked at the cpp files and noticed that in 3.0.0, an additional variable is generated:

```cpp
// 3.0.0
void *__pyx_t_2;
double *__pyx_k_;
// ...
/* "foo.pyx":6
 * 	double
 * 
 * cdef foo(bar* b=NULL):             # <<<<<<<<<<<<<<
 * 	pass
 */
__pyx_t_2 = NULL;
__pyx_k_ = __pyx_t_2;
```
```cpp
// 0.29.36
static double *__pyx_k_;
// ...
/* "foo.pyx":6
 * 	double
 * 
 * cdef foo(bar* b=NULL):             # <<<<<<<<<<<<<<
 * 	pass
 */
__pyx_k_ = NULL;
```
Seems like the 3.0.0 code might just be missing a `static_cast<double*>`?


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
@@ -889,8 +889,11 @@ def analyse_expressions(self, env):
         for arg in self.node.args:
             if arg.default:
                 arg.default = arg.default.analyse_expressions(env)
-                # coerce the argument to temp since CloneNode really requires a temp
-                defaults.append(ProxyNode(arg.default.coerce_to_temp(env)))
+                if arg.default.is_literal:
+                    defaults.append(copy.copy(arg.default))
+                else:
+                    # coerce the argument to temp since CloneNode really requires a temp
+                    defaults.append(ProxyNode(arg.default.coerce_to_temp(env)))
             else:
                 defaults.append(None)
 
@@ -900,7 +903,10 @@ def analyse_expressions(self, env):
                 # the dispatcher specifically doesn't want its defaults overriding
                 for arg, default in zip(stat.args, defaults):
                     if default is not None:
-                        arg.default = CloneNode(default).analyse_expressions(env).coerce_to(arg.type, env)
+                        if default.is_literal:
+                            arg.default = default
+                        else:
+                            arg.default = CloneNode(default).analyse_expressions(env).coerce_to(arg.type, env)
 
         if self.py_func:
             args = [CloneNode(default) for default in defaults if default]


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
@@ -596,3 +596,32 @@ def test_fused_func_pointer_multilevel():
     """
     print(call_function_that_calls_fused_pointer(call_func_pointer_with_1[double, int]))
     print(call_function_that_calls_fused_pointer(call_func_pointer_with_1[float, int]))
+
+cdef null_default(cython.floating x, cython.floating *x_minus_1_out=NULL):
+    # On C++ a void* can't be assigned to a regular pointer, therefore setting up
+    # needs to avoid going through a void* temp
+    if x_minus_1_out:
+        x_minus_1_out[0] = x-1
+    return x
+
+def test_null_default():
+    """
+    >>> test_null_default()
+    2.0 1.0
+    2.0
+    2.0 1.0
+    2.0
+    """
+    cdef double xd = 2.
+    cdef double xd_minus_1
+    result = null_default(xd, &xd_minus_1)
+    print result, xd_minus_1
+    result = null_default(xd)
+    print result
+
+    cdef float xf = 2.
+    cdef float xf_minus_1
+    result = null_default(xf, &xf_minus_1)
+    print result, xf_minus_1
+    result = null_default(xf)
+    print result