Repository: cupy/cupy

Issue:
`cupy.full` makes casting in `safe` rule
### Description

Rel #6181.

As fixed in #6481, both `numpy.fill` and `cupy.fill` make casting in `safe` rule. However, `numpy.full` make casting in `unsafe` rule unlike `numpy.fill` does, and #6481 brought a bug that `cupy.full` works in `safe` rule as it is currently implemented using `cupy.fill`.


### To Reproduce

```py
>>> import numpy as np
>>> import cupy as cp
>>> np.full((), np.array(1, dtype=np.float64), dtype=np.int64)
array(1)
>>> cp.full((), cp.array(1, dtype=cp.float64), dtype=cp.int64)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/ext-mtakagi/cupy.cuda-11.6/cupy/_creation/basic.py", line 276, in full
    a.fill(fill_value)
  File "cupy/_core/core.pyx", line 665, in cupy._core.core.ndarray.fill
    cpdef fill(self, value):
  File "cupy/_core/core.pyx", line 679, in cupy._core.core.ndarray.fill
    raise TypeError(
TypeError: Cannot cast scalar from dtype('float64') to dtype('int64') according to the rule 'safe'
```


### Installation

Built from GitHub source

### Environment

```
# Paste the output here
```


### Additional Information

`numpy.fill` and `cupy.fill` make casting in `safe` rule:
```py
>>> import numpy as np
>>> import cupy as cp
>>> a = np.array(0, dtype=np.int64)
>>> a.fill(np.array(1, dtype=np.float64))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Cannot cast scalar from dtype('float64') to dtype('int64') according to the rule 'safe'
>>> a = cp.array(0, dtype=cp.int64)
>>> a = a.fill(cp.array(1, dtype=cp.float64))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "cupy/_core/core.pyx", line 665, in cupy._core.core.ndarray.fill
    cpdef fill(self, value):
  File "cupy/_core/core.pyx", line 679, in cupy._core.core.ndarray.fill
    raise TypeError(
TypeError: Cannot cast scalar from dtype('float64') to dtype('int64') according to the rule 'safe'
```


Discussion:

Resolving patch:
diff --git a/cupy/_creation/basic.py b/cupy/_creation/basic.py
--- a/cupy/_creation/basic.py
+++ b/cupy/_creation/basic.py
@@ -273,7 +273,7 @@ def full(shape, fill_value, dtype=None, order='C'):
         else:
             dtype = numpy.array(fill_value).dtype
     a = cupy.ndarray(shape, dtype, order=order)
-    a.fill(fill_value)
+    cupy.copyto(a, fill_value, casting='unsafe')
     return a
 
 
@@ -310,5 +310,5 @@ def full_like(a, fill_value, dtype=None, order='K', subok=None, shape=None):
                                                          shape)
     shape = shape if shape else a.shape
     a = cupy.ndarray(shape, dtype, memptr, strides, order)
-    a.fill(fill_value)
+    cupy.copyto(a, fill_value, casting='unsafe')
     return a
diff --git a/cupy/_manipulation/basic.py b/cupy/_manipulation/basic.py
--- a/cupy/_manipulation/basic.py
+++ b/cupy/_manipulation/basic.py
@@ -36,7 +36,7 @@ def copyto(dst, src, casting='same_kind', where=None):
     if src_is_python_scalar:
         src_dtype = numpy.dtype(type(src))
         can_cast = numpy.can_cast(src, dst.dtype, casting)
-    elif isinstance(src, numpy.ndarray):
+    elif isinstance(src, numpy.ndarray) or numpy.isscalar(src):
         if src.size != 1:
             raise ValueError(
                 'non-scalar numpy.ndarray cannot be used for copyto')


Test patch:
diff --git a/tests/cupy_tests/creation_tests/test_basic.py b/tests/cupy_tests/creation_tests/test_basic.py
--- a/tests/cupy_tests/creation_tests/test_basic.py
+++ b/tests/cupy_tests/creation_tests/test_basic.py
@@ -1,5 +1,6 @@
 import numpy
 import pytest
+import warnings
 
 import cupy
 from cupy import testing
@@ -255,10 +256,13 @@ def test_full(self, xp, dtype, order):
     def test_full_default_dtype(self, xp, dtype, order):
         return xp.full((2, 3, 4), xp.array(1, dtype=dtype), order=order)
 
-    @testing.for_all_dtypes()
+    @testing.for_all_dtypes_combination(('dtype1', 'dtype2'))
     @testing.numpy_cupy_array_equal()
-    def test_full_default_dtype_cpu_input(self, xp, dtype):
-        return xp.full((2, 3, 4), numpy.array(1, dtype=dtype))
+    def test_full_dtypes_cpu_input(self, xp, dtype1, dtype2):
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', numpy.ComplexWarning)
+            return xp.full(
+                (2, 3, 4), numpy.array(1, dtype=dtype1), dtype=dtype2)
 
     @testing.for_orders('CFAK')
     @testing.for_all_dtypes()
@@ -267,6 +271,14 @@ def test_full_like(self, xp, dtype, order):
         a = xp.ndarray((2, 3, 4), dtype=dtype)
         return xp.full_like(a, 1, order=order)
 
+    @testing.for_all_dtypes_combination(('dtype1', 'dtype2'))
+    @testing.numpy_cupy_array_equal()
+    def test_full_like_dtypes_cpu_input(self, xp, dtype1, dtype2):
+        a = xp.ndarray((2, 3, 4), dtype=dtype1)
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', numpy.ComplexWarning)
+            return xp.full_like(a, numpy.array(1, dtype=dtype1))
+
     def test_full_like_subok(self):
         a = cupy.ndarray((2, 3, 4))
         with pytest.raises(TypeError):
diff --git a/tests/cupy_tests/manipulation_tests/test_basic.py b/tests/cupy_tests/manipulation_tests/test_basic.py
--- a/tests/cupy_tests/manipulation_tests/test_basic.py
+++ b/tests/cupy_tests/manipulation_tests/test_basic.py
@@ -210,7 +210,7 @@ def test_copyto_where(self, xp, dtype):
 
 @pytest.mark.parametrize(
     'casting', ['no', 'equiv', 'safe', 'same_kind', 'unsafe'])
-class TestCopytoFromNumPyScalar:
+class TestCopytoFromNumpyScalar:
 
     @testing.for_all_dtypes_combination(('dtype1', 'dtype2'))
     @testing.numpy_cupy_allclose(accept_error=TypeError)
@@ -222,6 +222,19 @@ def test_copyto(self, xp, dtype1, dtype2, casting):
             xp.copyto(dst, src, casting)
         return dst
 
+    @testing.for_all_dtypes()
+    @pytest.mark.parametrize('make_src',
+                             [lambda dtype: numpy.array([1], dtype=dtype),
+                              lambda dtype: dtype(1)])
+    @testing.numpy_cupy_allclose(accept_error=TypeError)
+    def test_copyto2(self, xp, make_src, dtype, casting):
+        dst = xp.zeros((2, 3, 4), dtype=dtype)
+        src = make_src(dtype)
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', numpy.ComplexWarning)
+            xp.copyto(dst, src, casting)
+        return dst
+
     @testing.for_all_dtypes_combination(('dtype1', 'dtype2'))
     @testing.numpy_cupy_allclose(accept_error=TypeError)
     def test_copyto_where(self, xp, dtype1, dtype2, casting):
@@ -234,16 +247,6 @@ def test_copyto_where(self, xp, dtype1, dtype2, casting):
             xp.copyto(dst, src, casting=casting, where=mask)
         return dst
 
-    @testing.for_all_dtypes()
-    @testing.numpy_cupy_allclose(accept_error=TypeError)
-    def test_copyto_size_one(self, xp, dtype, casting):
-        dst = xp.zeros((2, 3, 4), dtype=dtype)
-        src = numpy.array([1], dtype=dtype)
-        with warnings.catch_warnings():
-            warnings.simplefilter('ignore', numpy.ComplexWarning)
-            xp.copyto(dst, src, casting)
-        return dst
-
 
 @pytest.mark.parametrize('shape', [(3, 2), (0,)])
 @pytest.mark.parametrize('where', [