Repository: cupy/cupy

Issue:
cupy.nan_to_num does not accept cupy.nan
### Description

After upgrading to `cupy` v10, some calls like `cupy.nan_to_num(cupy.nan)` are failing at [this line](https://github.com/cupy/cupy/pull/5870/files#diff-c8d13a3653ab59bc655bb3f6bf2e749e8a475a2fb06133bf39fcd7f1319585fcR381).

`cupy.nan` is a `float` and so does not have a `.dtype` attribute.

I guess that this is desired as it removes an implicit memory transfer, however, this is valid in `numpy`.

If this is intentional I can work around it, but it seems counter-intuitive to me.

Thanks!

### To Reproduce

```py
>>> import cupy
>>> cupy.nan_to_num(cupy.nan)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/colm.talbot/.conda/envs/dev-39/lib/python3.9/site-packages/cupy/_math/misc.py", line 383, in nan_to_num
    dtype = x.dtype
AttributeError: 'float' object has no attribute 'dtype'
>>> cupy.nan_to_num(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/colm.talbot/.conda/envs/dev-39/lib/python3.9/site-packages/cupy/_math/misc.py", line 383, in nan_to_num
    dtype = x.dtype
AttributeError: 'int' object has no attribute 'dtype'
```

by contrast, this is the `numpy` behaviour

```py
>>> import numpy
>>> numpy.nan_to_num(numpy.nan)
0.0
>>> numpy.nan_to_num(0)
0
```

the old behaviour

```py
>>> import cupy
>>> cupy.nan_to_num(cupy.asanyarray(cupy.nan))
array(0.)
```


### Installation

Conda-Forge (`conda install ...`)

### Environment

```
OS                           : Linux-3.10.0-1160.49.1.el7.x86_64-x86_64-with-glibc2.17
Python Version               : 3.9.9
CuPy Version                 : 10.1.0
CuPy Platform                : NVIDIA CUDA
NumPy Version                : 1.22.1
SciPy Version                : None
Cython Build Version         : 0.29.26
Cython Runtime Version       : None
CUDA Root                    : /usr/local/cuda
nvcc PATH                    : /usr/local/cuda/bin/nvcc
CUDA Build Version           : 11020
CUDA Driver Version          : 11050
CUDA Runtime Version         : 11060
cuBLAS Version               : (available)
cuFFT Version                : 10600
cuRAND Version               : 10209
cuSOLVER Version             : (11, 3, 2)
cuSPARSE Version             : (available)
NVRTC Version                : (11, 6)
Thrust Version               : 101000
CUB Build Version            : 101000
Jitify Build Version         : 7ed41e6
cuDNN Build Version          : 8201
cuDNN Version                : 8301
NCCL Build Version           : None
NCCL Runtime Version         : None
cuTENSOR Version             : 10303
cuSPARSELt Build Version     : None
Device 0 Name                : NVIDIA A100-PCIE-40GB
Device 0 Compute Capability  : 80
Device 0 PCI Bus ID          : 0000:C1:00.0
Device 1 Name                : NVIDIA GeForce RTX 3080
Device 1 Compute Capability  : 86
Device 1 PCI Bus ID          : 0000:01:00.0```


### Additional Information

_No response_


Discussion:
Thanks for reporting! I'm writing a fix for it.

Resolving patch:
diff --git a/cupy/_math/misc.py b/cupy/_math/misc.py
--- a/cupy/_math/misc.py
+++ b/cupy/_math/misc.py
@@ -388,11 +388,14 @@ def nan_to_num(x, copy=True, nan=0.0, posinf=None, neginf=None):
     .. seealso:: :func:`numpy.nan_to_num`
 
     """
-    dtype = x.dtype
+    if not isinstance(x, cupy.ndarray):
+        out = cupy.full((), x)
+    else:
+        out = cupy.empty_like(x) if copy else x
+    dtype = out.dtype
     nan = _check_nan_inf(nan, dtype)
     posinf = _check_nan_inf(posinf, dtype, False)
     neginf = _check_nan_inf(neginf, dtype, True)
-    out = None if copy else x
     return _nan_to_num(x, nan, posinf, neginf, out=out)
 
 


Test patch:
diff --git a/tests/cupy_tests/math_tests/test_misc.py b/tests/cupy_tests/math_tests/test_misc.py
--- a/tests/cupy_tests/math_tests/test_misc.py
+++ b/tests/cupy_tests/math_tests/test_misc.py
@@ -1,5 +1,4 @@
 import sys
-import unittest
 
 import numpy
 import pytest
@@ -8,8 +7,7 @@
 from cupy import testing
 
 
-@testing.gpu
-class TestMisc(unittest.TestCase):
+class TestMisc:
 
     @testing.for_all_dtypes()
     @testing.numpy_cupy_allclose(atol=1e-5)
@@ -87,8 +85,8 @@ def check_binary_nan(self, name, xp, dtype):
                      dtype=dtype)
         return getattr(xp, name)(a, b)
 
-    @unittest.skipIf(
-        sys.platform == 'win32', 'dtype problem on Windows')
+    @pytest.mark.skipIf(
+        sys.platform == 'win32', reason='dtype problem on Windows')
     @testing.for_all_dtypes(no_complex=True)
     @testing.numpy_cupy_array_equal()
     def test_clip1(self, xp, dtype):
@@ -120,8 +118,8 @@ def test_clip_min_max_none(self, dtype):
             with pytest.raises(ValueError):
                 a.clip(None, None)
 
-    @unittest.skipIf(
-        sys.platform == 'win32', 'dtype problem on Windows')
+    @pytest.mark.skipIf(
+        sys.platform == 'win32', reason='dtype problem on Windows')
     @testing.for_all_dtypes(no_complex=True)
     @testing.numpy_cupy_array_equal()
     def test_external_clip1(self, xp, dtype):
@@ -220,6 +218,10 @@ def test_nan_to_num_inf(self):
     def test_nan_to_num_nan(self):
         self.check_unary_nan('nan_to_num')
 
+    @testing.numpy_cupy_allclose(atol=1e-5)
+    def test_nan_to_num_scalar_nan(self, xp):
+        return xp.nan_to_num(xp.nan)
+
     def test_nan_to_num_inf_nan(self):
         self.check_unary_inf_nan('nan_to_num')
 
@@ -243,6 +245,16 @@ def test_nan_to_num_inplace(self, xp):
         assert x is y
         return y
 
+    @pytest.mark.parametrize('kwarg', ['nan', 'posinf', 'neginf'])
+    def test_nan_to_num_broadcast(self, kwarg):
+        for xp in (numpy, cupy):
+            x = xp.asarray([0, 1, xp.nan, 4], dtype=xp.float64)
+            y = xp.zeros((2, 4), dtype=xp.float64)
+            with pytest.raises(ValueError):
+                xp.nan_to_num(x, **{kwarg: y})
+            with pytest.raises(ValueError):
+                xp.nan_to_num(0.0, **{kwarg: y})
+
     @testing.for_all_dtypes(name='dtype_x', no_bool=True, no_complex=True)
     @testing.for_all_dtypes(name='dtype_y', no_bool=True)
     @testing.numpy_cupy_allclose(atol=1e-5)
@@ -371,13 +383,12 @@ def test_interp_inf_to_nan(self, xp, dtype_y, dtype_x):
         return xp.interp(x, fx, fy)
 
 
-@testing.gpu
 @testing.parameterize(*testing.product({
     'mode': ['valid', 'same', 'full'],
     'shape1': [(), (5,), (6,), (20,), (21,)],
     'shape2': [(), (5,), (6,), (20,), (21,)],
 }))
-class TestConvolveShapeCombination(unittest.TestCase):
+class TestConvolveShapeCombination:
 
     @testing.for_all_dtypes(no_float16=True)
     @testing.numpy_cupy_allclose(rtol=1e-3)
@@ -412,11 +423,10 @@ def test_convolve_diff_types(self, xp, dtype1, dtype2, mode):
         return xp.convolve(a, b, mode=mode)
 
 
-@testing.gpu
 @testing.parameterize(*testing.product({
     'mode': ['valid', 'same', 'full']
 }))
-class TestConvolveInvalid(unittest.TestCase):
+class TestConvolveInvalid:
 
     @testing.for_all_dtypes()
     def test_convolve_empty(self, dtype):