Repository: numba/numba

Issue:
problem typing a global tuple of specific integer types as literal
<!--

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).

For more general "how do I do X?" type questions, please speak to us in real
time on https://gitter.im/numba/numba or post to the Numba mailing list
https://groups.google.com/a/continuum.io/forum/#!forum/numba-users.

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I am using the ~latest released~ master version of Numba (most recent is visible in
 the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included below a minimal working reproducer (if you are unsure how
 to write one see http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports).

Since: https://github.com/numba/numba/pull/5182

This:
```python
from numba import njit, types
import numpy as np

x = (types.uint64(10),)

@njit
def foo():
    return x[0]

foo()
```
does this
```
    raise LiteralTypingError("{} cannot be used as a literal".format(ty))
numba.core.errors.LiteralTypingError: Failed in nopython mode pipeline (step: nopython frontend)
<class 'numpy.uint64'> cannot be used as a literal
```
it's because `x` is typed as a `UniTuple(uint64 x 1)`, but the actual value of the global is a tuple size 1 containing a `np.uint64` of value 10. This in turn is because `type.uint64(value)` when evaluated by the interpreter instantiates a `np.uint64(value)`.

As original author, any thoughts on fixing this @luk-f-a ? Should `np.?int??` be special cased in the conversion global value typing so that they can be used as literals? Would such a change of a knock-on effect of erasing e.g. the signedness which may be a valuable property?

This bit of code needs looking at I think:
https://github.com/numba/numba/pull/5182/files#diff-30d845b63b04ca21d10c7f123ade6e57R1482-R1485

CC @gmarkall for CUDA RNG problem that hit this.
problem typing a global tuple of specific integer types as literal
<!--

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).

For more general "how do I do X?" type questions, please speak to us in real
time on https://gitter.im/numba/numba or post to the Numba mailing list
https://groups.google.com/a/continuum.io/forum/#!forum/numba-users.

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I am using the ~latest released~ master version of Numba (most recent is visible in
 the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included below a minimal working reproducer (if you are unsure how
 to write one see http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports).

Since: https://github.com/numba/numba/pull/5182

This:
```python
from numba import njit, types
import numpy as np

x = (types.uint64(10),)

@njit
def foo():
    return x[0]

foo()
```
does this
```
    raise LiteralTypingError("{} cannot be used as a literal".format(ty))
numba.core.errors.LiteralTypingError: Failed in nopython mode pipeline (step: nopython frontend)
<class 'numpy.uint64'> cannot be used as a literal
```
it's because `x` is typed as a `UniTuple(uint64 x 1)`, but the actual value of the global is a tuple size 1 containing a `np.uint64` of value 10. This in turn is because `type.uint64(value)` when evaluated by the interpreter instantiates a `np.uint64(value)`.

As original author, any thoughts on fixing this @luk-f-a ? Should `np.?int??` be special cased in the conversion global value typing so that they can be used as literals? Would such a change of a knock-on effect of erasing e.g. the signedness which may be a valuable property?

This bit of code needs looking at I think:
https://github.com/numba/numba/pull/5182/files#diff-30d845b63b04ca21d10c7f123ade6e57R1482-R1485

CC @gmarkall for CUDA RNG problem that hit this.


Discussion:
quite a milestone for me, the first bug found on something I contributed to Numba. :-)
I'll have a look later and let you know.
 @stuartarchibald , as you already realized, the problem comes from assuming that any subtype of `Integer` can be converted to Literal. So there are three ways forward:
- correct that assumption, refine the testing of the types such that only types in `Literal.ctor_map` get to pass to the next line `typ = ...`. This will remove the regression, keeping the PR improvement for Integer, but not for np.uint
- expand the definition of IntegerLiteral to include uint64, that means adding `Literal.ctor_map[np.uint64] = IntegerLiteral` in `scalars.py`.
- cast np.uint64 to int before attempting to retrieve the literal value.

I am favour in option 2, although we should be aware that it could have unwanted side effects. Option 3 is also good, since we get to cover this use case, but with lower risk of side effects. Option 1 would be the most conservative, maybe more appropriate for a release candidate.

It depends on your risk-tolerance at this point of the release cycle. 

quite a milestone for me, the first bug found on something I contributed to Numba. :-)
I'll have a look later and let you know.
 @stuartarchibald , as you already realized, the problem comes from assuming that any subtype of `Integer` can be converted to Literal. So there are three ways forward:
- correct that assumption, refine the testing of the types such that only types in `Literal.ctor_map` get to pass to the next line `typ = ...`. This will remove the regression, keeping the PR improvement for Integer, but not for np.uint
- expand the definition of IntegerLiteral to include uint64, that means adding `Literal.ctor_map[np.uint64] = IntegerLiteral` in `scalars.py`.
- cast np.uint64 to int before attempting to retrieve the literal value.

I am favour in option 2, although we should be aware that it could have unwanted side effects. Option 3 is also good, since we get to cover this use case, but with lower risk of side effects. Option 1 would be the most conservative, maybe more appropriate for a release candidate.

It depends on your risk-tolerance at this point of the release cycle. 


Resolving patch:
diff --git a/numba/core/typeinfer.py b/numba/core/typeinfer.py
--- a/numba/core/typeinfer.py
+++ b/numba/core/typeinfer.py
@@ -1491,9 +1491,11 @@ def typeof_global(self, inst, target, gvar):
             typ = typ.copy(readonly=True)
 
         if isinstance(typ, types.BaseAnonymousTuple):
-            types_with_literals = (types.Integer, types.UnicodeType)
-            if all(isinstance(ty, types_with_literals) for ty in typ):
-                typ = types.Tuple([types.literal(val) for val in gvar.value])
+            # if it's a tuple of literal types, swap the type for the more
+            # specific literal version
+            literaled = [types.maybe_literal(x) for x in gvar.value]
+            if all(literaled):
+                typ = types.Tuple(literaled)
 
         self.sentry_modified_builtin(inst, gvar)
         # Setting literal_value for globals because they are handled


Test patch:
diff --git a/numba/tests/test_globals.py b/numba/tests/test_globals.py
--- a/numba/tests/test_globals.py
+++ b/numba/tests/test_globals.py
@@ -76,6 +76,7 @@ def global_module_func(x, y):
 tup_str = ('a', 'b')
 tup_mixed = (1, 'a')
 tup_float = (1.2, 3.5)
+tup_npy_ints = (np.uint64(12), np.int8(3))
 
 def global_int_tuple():
     return tup_int[0] + tup_int[1]
@@ -95,6 +96,10 @@ def global_float_tuple():
     return tup_float[0] + tup_float[1]
 
 
+def global_npy_int_tuple():
+    return tup_npy_ints[0] + tup_npy_ints[1]
+
+
 class TestGlobals(unittest.TestCase):
 
     def check_global_ndarray(self, **jitargs):
@@ -209,6 +214,11 @@ def test_global_float_tuple(self):
         jitfunc = njit(pyfunc)
         self.assertEqual(pyfunc(), jitfunc())
 
+    def test_global_npy_int_tuple(self):
+        pyfunc = global_npy_int_tuple
+        jitfunc = njit(pyfunc)
+        self.assertEqual(pyfunc(), jitfunc())
+
 
 if __name__ == '__main__':
     unittest.main()