Repository: numba/numba

Issue:
literal_unroll needs to know about `types.Named*Tuple`
<!--

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 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).

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->
This was reported here: https://gitter.im/numba/numba?at=5e57e6a186e6ec2f5c6466a6
```python
from collections import namedtuple
from numba import njit, literal_unroll

ABC = namedtuple('ABC', ['a', 'b', 'c'])

@njit
def unroll(x):
    for z in literal_unroll(x):
        print(z)

abc = ABC(1, 2, 3)
print(unroll(abc))
```
does this
```
Invalid use of literal_unroll with a function argument, only tuples are supported as function arguments, found ABC(int64 x 3)
```
As the Numba internal Python 3.8 tuple `__hash__` alg uses `literal_unroll`, the above issue breaks hashing on named tuples in Python 3.8.

Discussion:

Patch:
diff --git a/numba/core/untyped_passes.py b/numba/core/untyped_passes.py
index 1a2a42481..157b4d198 100644
--- a/numba/core/untyped_passes.py
+++ b/numba/core/untyped_passes.py
@@ -602,7 +602,7 @@ class TransformLiteralUnrollConstListToTuple(FunctionPass):
     """
     _name = "transform_literal_unroll_const_list_to_tuple"
 
-    _accepted_types = (types.Tuple, types.UniTuple)
+    _accepted_types = (types.BaseTuple,)
 
     def __init__(self):
         FunctionPass.__init__(self)
@@ -716,7 +716,7 @@ class MixedContainerUnroller(FunctionPass):
 
     _DEBUG = False
 
-    _accepted_types = (types.Tuple, types.UniTuple)
+    _accepted_types = (types.BaseTuple,)
 
     def __init__(self):
         FunctionPass.__init__(self)
@@ -1234,7 +1234,7 @@ class IterLoopCanonicalization(FunctionPass):
     _DEBUG = False
 
     # if partial typing info is available it will only look at these types
-    _accepted_types = (types.Tuple, types.UniTuple)
+    _accepted_types = (types.BaseTuple,)
     _accepted_calls = (literal_unroll,)
 
     def __init__(self):


Test patch:
diff --git a/numba/tests/test_mixed_tuple_unroller.py b/numba/tests/test_mixed_tuple_unroller.py
index 0c3099276..53297f7c2 100644
--- a/numba/tests/test_mixed_tuple_unroller.py
+++ b/numba/tests/test_mixed_tuple_unroller.py
@@ -1,3 +1,4 @@
+from collections import namedtuple
 import numpy as np
 
 from numba.tests.support import (TestCase, MemoryLeakMixin,
@@ -1650,6 +1651,60 @@ class TestMore(TestCase):
             ['a 1', 'b 2', '3 c', '4 d'],
         )
 
+    def test_unroll_named_tuple(self):
+        ABC = namedtuple('ABC', ['a', 'b', 'c'])
+
+        @njit
+        def foo():
+            abc = ABC(1, 2j, 3.4)
+            out = 0
+            for i in literal_unroll(abc):
+                out += i
+            return out
+
+        self.assertEqual(foo(), foo.py_func())
+
+    def test_unroll_named_tuple_arg(self):
+        ABC = namedtuple('ABC', ['a', 'b', 'c'])
+
+        @njit
+        def foo(x):
+            out = 0
+            for i in literal_unroll(x):
+                out += i
+            return out
+
+        abc = ABC(1, 2j, 3.4)
+
+        self.assertEqual(foo(abc), foo.py_func(abc))
+
+    def test_unroll_named_unituple(self):
+        ABC = namedtuple('ABC', ['a', 'b', 'c'])
+
+        @njit
+        def foo():
+            abc = ABC(1, 2, 3)
+            out = 0
+            for i in literal_unroll(abc):
+                out += i
+            return out
+
+        self.assertEqual(foo(), foo.py_func())
+
+    def test_unroll_named_unituple_arg(self):
+        ABC = namedtuple('ABC', ['a', 'b', 'c'])
+
+        @njit
+        def foo(x):
+            out = 0
+            for i in literal_unroll(x):
+                out += i
+            return out
+
+        abc = ABC(1, 2, 3)
+
+        self.assertEqual(foo(abc), foo.py_func(abc))
+
     def test_unroll_global_tuple(self):
 
         @njit