Repository: numba/numba

Issue:
Support alternative spellings of dtypes in dtype kwarg of numpy array constructors
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I have tried using the latest released version of Numba (most recent is
 visible in the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included a self contained code sample to reproduce the problem.
  i.e. it's possible to run as 'python bug.py'.

Since #6262 some of the commonly used array constructors from NumPy, e.g. `np.zeros` have supported string literals in the `dtype` kwarg. There's a minor bug in the typing at present which means that the `dtype` spelling has to be an attribute on the `numpy` top level module, e.g. `np.int64`. This sort of thing doesn't work:
```python
import numpy as np
from numba import njit

@njit
def foo():
    return np.zeros((1, 2), dtype='i8')

foo()
```
fix is along the lines of:
```patch
diff --git a/numba/core/typing/npydecl.py b/numba/core/typing/npydecl.py
index 50cff1c6a..f84f483fb 100644
--- a/numba/core/typing/npydecl.py
+++ b/numba/core/typing/npydecl.py
@@ -440,6 +440,11 @@ def parse_dtype(dtype):
         return dtype.instance_type
     elif isinstance(dtype, types.StringLiteral):
         dt = getattr(np, dtype.literal_value, None)
+        if dt is None: # try again with an actual dtype
+            try:
+                dt = np.dtype(dtype.literal_value).type
+            except ValueError:
+                pass
         if dt is not None:
             return from_dtype(dt)
```

Discussion:

Patch:
diff --git a/numba/core/typing/npydecl.py b/numba/core/typing/npydecl.py
index 50cff1c6a..5381a9287 100644
--- a/numba/core/typing/npydecl.py
+++ b/numba/core/typing/npydecl.py
@@ -439,9 +439,13 @@ def parse_dtype(dtype):
     elif isinstance(dtype, types.TypeRef):
         return dtype.instance_type
     elif isinstance(dtype, types.StringLiteral):
-        dt = getattr(np, dtype.literal_value, None)
-        if dt is not None:
-            return from_dtype(dt)
+        dtstr = dtype.literal_value
+        try:
+            dt = np.dtype(dtstr)
+        except TypeError:
+            msg = f"Invalid NumPy dtype specified: '{dtstr}'"
+            raise TypingError(msg)
+        return from_dtype(dt)
 
 def _parse_nested_sequence(context, typ):
     """
diff --git a/numba/np/numpy_support.py b/numba/np/numpy_support.py
index 975e78e96..0234b3816 100644
--- a/numba/np/numpy_support.py
+++ b/numba/np/numpy_support.py
@@ -95,13 +95,18 @@ def from_dtype(dtype):
     try:
         return FROM_DTYPE[dtype]
     except KeyError:
-        char = dtype.char
+        pass
 
+    try:
+        char = dtype.char
+    except AttributeError:
+        pass
+    else:
         if char in 'SU':
             return _from_str_dtype(dtype)
         if char in 'mM':
             return _from_datetime_dtype(dtype)
-        if char in 'V':
+        if char in 'V' and dtype.subdtype is not None:
             subtype = from_dtype(dtype.subdtype[0])
             return types.NestedArray(subtype, dtype.shape)
 


Test patch:
diff --git a/numba/tests/test_dyn_array.py b/numba/tests/test_dyn_array.py
index 0877781d1..c3b2c43eb 100644
--- a/numba/tests/test_dyn_array.py
+++ b/numba/tests/test_dyn_array.py
@@ -625,6 +625,26 @@ class TestNdZeros(ConstructorBaseTest, TestCase):
             return pyfunc(n, 'complex128')
         self.check_1d(func)
 
+    def test_1d_dtype_str_alternative_spelling(self):
+        # like test_1d_dtype_str but using the shorthand type spellings
+        pyfunc = self.pyfunc
+        _dtype = 'i4'
+        def func(n):
+            return pyfunc(n, _dtype)
+        self.check_1d(func)
+
+        def func(n):
+            return pyfunc(n, 'c8')
+        self.check_1d(func)
+
+    def test_1d_dtype_str_structured_dtype(self):
+        # test_1d_dtype_str but using a structured dtype
+        pyfunc = self.pyfunc
+        _dtype = "i4, (2,3)f8"
+        def func(n):
+            return pyfunc(n, _dtype)
+        self.check_1d(func)
+
     def test_1d_dtype_non_const_str(self):
         pyfunc = self.pyfunc
 
@@ -641,6 +661,19 @@ class TestNdZeros(ConstructorBaseTest, TestCase):
         regex = re.compile(restr.format(pyfunc.__name__))
         self.assertRegex(excstr, regex)
 
+    def test_1d_dtype_invalid_str(self):
+        pyfunc = self.pyfunc
+
+        @njit
+        def func(n):
+            return pyfunc(n, 'ABCDEF')
+
+        with self.assertRaises(TypingError) as raises:
+            func(5)
+
+        excstr = str(raises.exception)
+        self.assertIn("Invalid NumPy dtype specified: 'ABCDEF'", excstr)
+
     def test_2d(self):
         pyfunc = self.pyfunc
         def func(m, n):
@@ -675,6 +708,13 @@ class TestNdZeros(ConstructorBaseTest, TestCase):
             return pyfunc((m, n), dtype='complex64')
         self.check_2d(func)
 
+    def test_2d_dtype_str_kwarg_alternative_spelling(self):
+        # as test_2d_dtype_str_kwarg but with the numpy shorthand type spelling
+        pyfunc = self.pyfunc
+        def func(m, n):
+            return pyfunc((m, n), dtype='c8')
+        self.check_2d(func)
+
     def test_alloc_size(self):
         pyfunc = self.pyfunc
         width = types.intp.bitwidth
@@ -691,6 +731,10 @@ class TestNdOnes(TestNdZeros):
         super(TestNdOnes, self).setUp()
         self.pyfunc = np.ones
 
+    @unittest.expectedFailure
+    def test_1d_dtype_str_structured_dtype(self):
+        super().test_1d_dtype_str_structured_dtype()
+
 
 class TestNdFull(ConstructorBaseTest, TestCase):
 
@@ -723,6 +767,12 @@ class TestNdFull(ConstructorBaseTest, TestCase):
             return np.full(n, 4.5, 'bool_')
         self.check_1d(func)
 
+    def test_1d_dtype_str_alternative_spelling(self):
+        # like test_1d_dtype_str but using the shorthand type spelling
+        def func(n):
+            return np.full(n, 4.5, '?')
+        self.check_1d(func)
+
     def test_1d_dtype_non_const_str(self):
 
         @njit
@@ -738,6 +788,18 @@ class TestNdFull(ConstructorBaseTest, TestCase):
         regex = re.compile(restr)
         self.assertRegex(excstr, regex)
 
+    def test_1d_dtype_invalid_str(self):
+
+        @njit
+        def func(n, fv):
+            return np.full(n, fv, 'ABCDEF')
+
+        with self.assertRaises(TypingError) as raises:
+            func(np.ones(4), 4.5)
+
+        excstr = str(raises.exception)
+        self.assertIn("Invalid NumPy dtype specified: 'ABCDEF'", excstr)
+
     def test_2d(self):
         def func(m, n):
             return np.full((m, n), 4.5)
@@ -888,10 +950,10 @@ class TestNdEmptyLike(ConstructorLikeBaseTest, TestCase):
             return pyfunc(arr, dtype='int32')
         self.check_like(func, np.float64)
 
-    def test_like_dtype_str_kwarg(self):
+    def test_like_dtype_str_kwarg_alternative_spelling(self):
         pyfunc = self.pyfunc
         def func(arr):
-            return pyfunc(arr, dtype='int32')
+            return pyfunc(arr, dtype='i4')
         self.check_like(func, np.float64)
 
     def test_like_dtype_non_const_str(self):
@@ -911,6 +973,19 @@ class TestNdEmptyLike(ConstructorLikeBaseTest, TestCase):
             '{}(array(float64, 1d, C), unicode_type)'.format(pyfunc.__name__),
             excstr)
 
+    def test_like_dtype_invalid_str(self):
+        pyfunc = self.pyfunc
+
+        @njit
+        def func(n):
+            return pyfunc(n, 'ABCDEF')
+
+        with self.assertRaises(TypingError) as raises:
+            func(np.ones(4))
+
+        excstr = str(raises.exception)
+        self.assertIn("Invalid NumPy dtype specified: 'ABCDEF'", excstr)
+
 
 class TestNdZerosLike(TestNdEmptyLike):
 
@@ -985,6 +1060,11 @@ class TestNdFullLike(ConstructorLikeBaseTest, TestCase):
             return np.full_like(arr, 4.5, 'bool_')
         self.check_like(func, np.float64)
 
+    def test_like_dtype_str_kwarg_alternative_spelling(self):
+        def func(arr):
+            return np.full_like(arr, 4.5, dtype='?')
+        self.check_like(func, np.float64)
+
     def test_like_dtype_non_const_str_kwarg(self):
 
         @njit
@@ -999,6 +1079,18 @@ class TestNdFullLike(ConstructorLikeBaseTest, TestCase):
         self.assertIn('full_like(array(float64, 1d, C), float64, unicode_type)',
                       excstr)
 
+    def test_like_dtype_invalid_str(self):
+
+        @njit
+        def func(arr, fv):
+            return np.full_like(arr, fv, "ABCDEF")
+
+        with self.assertRaises(TypingError) as raises:
+            func(np.ones(4), 3.4)
+
+        excstr = str(raises.exception)
+        self.assertIn("Invalid NumPy dtype specified: 'ABCDEF'", excstr)
+
 
 class TestNdIdentity(BaseTest):