Repository: cupy/cupy

Issue:
Fused function returns the last argument when the target function returns None
Code:
```
import cupy

@cupy.fuse()
def func(a, b):
    pass

x = cupy.ones((2, 2))
y = cupy.ones((2, 2)) *3
z = func(x, y)
print(z)
```

Results:
```
[[ 3.  3.]
 [ 3.  3.]]
```


Discussion:

Resolving patch:
diff --git a/cupy/core/fusion.py b/cupy/core/fusion.py
--- a/cupy/core/fusion.py
+++ b/cupy/core/fusion.py
@@ -647,11 +647,24 @@ def get_fusion(self, func, in_dtypes, name):
         """
         in_params = [self._fresh_premap_param(t) for t in in_dtypes]
         in_pvars = [FusionVarPython(_, False) for _ in in_params]
-        out_pvars = func(*in_pvars)
-        if isinstance(out_pvars, tuple):
-            out_pvars = list(out_pvars)
+        return_value = func(*in_pvars)
+
+        if isinstance(return_value, tuple):
+            return_tuple = True
+            no_return = False
+            out_pvars = return_value
+        elif isinstance(return_value, FusionVarPython):
+            return_tuple = False
+            no_return = False
+            out_pvars = [return_value]
+        elif return_value is None:
+            return_tuple = False
+            no_return = True
+            out_pvars = []
         else:
-            out_pvars = [out_pvars]
+            raise TypeError(
+                'Fusion function can\'t return {}'.format(type(return_value)))
+
         out_pvars = [_ for _ in out_pvars if _ is not None]
         out_cvars = [self._get_cuda_var(_) for _ in out_pvars]
 
@@ -672,6 +685,8 @@ def get_fusion(self, func, in_dtypes, name):
             kernel = core.ElementwiseKernel(
                 in_params_code, out_params_code, operation,
                 preamble=submodule_code,
+                return_tuple=return_tuple,
+                no_return=no_return,
                 name=name)
             return kernel, {}
         else:


Test patch:
diff --git a/tests/cupy_tests/core_tests/test_fusion.py b/tests/cupy_tests/core_tests/test_fusion.py
--- a/tests/cupy_tests/core_tests/test_fusion.py
+++ b/tests/cupy_tests/core_tests/test_fusion.py
@@ -1413,3 +1413,76 @@ def test_numpy_scalar(self, xp, dtype):
         def f(x):
             return x * dtype(1)
         return f(testing.shaped_arange((1,), xp, dtype))
+
+
+@testing.gpu
+class TestFusionReturnsConstantValue(unittest.TestCase):
+
+    @testing.for_all_dtypes()
+    @testing.numpy_cupy_array_equal()
+    def test_pass(self, xp, dtype):
+
+        @cupy.fuse()
+        def f(x):
+            pass
+
+        x = testing.shaped_arange((3, 3), xp, dtype)
+        y = f(x)
+        self.assertEqual(y, None)
+        return x
+
+    @testing.for_all_dtypes(no_bool=True)
+    @testing.numpy_cupy_array_equal()
+    def test_no_retval(self, xp, dtype):
+
+        @cupy.fuse()
+        def f(x):
+            x += 1
+
+        x = testing.shaped_arange((3, 3), xp, dtype)
+        y = f(x)
+        self.assertEqual(y, None)
+        return x
+
+
+@testing.gpu
+class TestFusionReturnsTuple(unittest.TestCase):
+
+    @testing.for_all_dtypes()
+    @testing.numpy_cupy_array_list_equal()
+    def test_empty_tuple(self, xp, dtype):
+
+        @cupy.fuse()
+        def f(x):
+            return ()
+
+        x = testing.shaped_arange((3, 4), xp, dtype)
+        y = f(x)
+        self.assertEqual(type(y), tuple)
+        return y
+
+    @testing.for_all_dtypes()
+    @testing.numpy_cupy_array_list_equal()
+    def test_singleton_tuple(self, xp, dtype):
+
+        @cupy.fuse()
+        def f(x):
+            return x * 2,
+
+        x = testing.shaped_arange((3, 4), xp, dtype)
+        y = f(x)
+        self.assertEqual(type(y), tuple)
+        return y
+
+    @testing.for_all_dtypes()
+    @testing.numpy_cupy_array_list_equal()
+    def test_pair_tuple(self, xp, dtype):
+
+        @cupy.fuse()
+        def f(x):
+            return x * 2, x * 3
+
+        x = testing.shaped_arange((3, 4), xp, dtype)
+        y = f(x)
+        self.assertEqual(type(y), tuple)
+        return y