Repository: numba/numba

Issue:
Cannot iterate over fields of record array
When iterating over the fields of a record array, numba complains about a not implemented getitem. 
Accessing the fields by name outside a loop works. I am using numba 0.52, the problem occurs on both Windows and linux

## This is the ipython cell that produces the error:

```python
%pylab
import numpy.random
from numba import njit

@njit
def recarriterator(rec):
    for o in 'a','b','c':
        print(rec[o][0])
    return
        
@njit
def recarriterator2(rec):
    print(rec['a'][0])
    print(rec['b'][0])
    print(rec['c'][0])
    return
        
ldd=[]
ldk=[]
keys='a','b','c'
for o in keys:
    #if not (o == 'observation_id' or o == 'report_id' or o == 'sensor_id' or o == 'source_id'):
        ldd.append(random.rand(5))
        ldk.append((o,ldd[-1].dtype))
rec = numpy.rec.fromarrays(ldd, dtype=ldk)
    
recarriterator2(rec)
recarriterator(rec)
```
## The error code:
```
Using matplotlib backend: Qt5Agg
Populating the interactive namespace from numpy and matplotlib
0.26156099390229226
0.4919898206995005
0.363624558244539
---------------------------------------------------------------------------
TypingError                               Traceback (most recent call last)
<ipython-input-1-56c6c55bddcf> in <module>
     26 
     27 recarriterator2(rec)
---> 28 recarriterator(rec)

~\anaconda3\lib\site-packages\numba\core\dispatcher.py in _compile_for_args(self, *args, **kws)
    413                 e.patch_message(msg)
    414 
--> 415             error_rewrite(e, 'typing')
    416         except errors.UnsupportedError as e:
    417             # Something unsupported is present in the user code, add help info

~\anaconda3\lib\site-packages\numba\core\dispatcher.py in error_rewrite(e, issue_type)
    356                 raise e
    357             else:
--> 358                 reraise(type(e), e, None)
    359 
    360         argtypes = []

~\anaconda3\lib\site-packages\numba\core\utils.py in reraise(tp, value, tb)
     78         value = tp()
     79     if value.__traceback__ is not tb:
---> 80         raise value.with_traceback(tb)
     81     raise value
     82 

TypingError: Failed in nopython mode pipeline (step: nopython frontend)
No implementation of function Function(<built-in function getitem>) found for signature:
 
 >>> getitem(unaligned array(Record(a[type=float64;offset=0],b[type=float64;offset=8],c[type=float64;offset=16];24;False), 1d, C), unicode_type)
 
There are 22 candidate implementations:
  - Of which 20 did not match due to:
  Overload of function 'getitem': File: <numerous>: Line N/A.
    With argument(s): '(unaligned array(Record(a[type=float64;offset=0],b[type=float64;offset=8],c[type=float64;offset=16];24;False), 1d, C), unicode_type)':
   No match.
  - Of which 2 did not match due to:
  Overload in function 'GetItemBuffer.generic': File: numba\core\typing\arraydecl.py: Line 162.
    With argument(s): '(unaligned array(Record(a[type=float64;offset=0],b[type=float64;offset=8],c[type=float64;offset=16];24;False), 1d, C), unicode_type)':
   Rejected as the implementation raised a specific error:
     TypeError: unsupported array index type unicode_type in [unicode_type]
  raised from C:\Users\leopo\anaconda3\lib\site-packages\numba\core\typing\arraydecl.py:68

During: typing of intrinsic-call at <ipython-input-1-56c6c55bddcf> (8)

File "<ipython-input-1-56c6c55bddcf>", line 8:
def recarriterator(rec):
    <source elided>
    for o in 'a','b','c':
        print(rec[o][0])
        ^
```


Discussion:
Thanks for the report. RE formatting in GitHub issues, for future reference triple backticks are needed around a code block (with optional language syntax specified), for example: ` ```python <your code> ``` `. GitHub also has a basic guide to its markdown syntax available [here](https://help.github.com/articles/basic-writing-and-formatting-syntax). I've edited the above so it renders correctly.
I think the issue here is that Numba cannot work out what to do with a `unicode_type` as an index. In this case:
```python
@njit
def recarriterator(rec):
    for o in 'a','b','c':
        print(rec[o][0])
    return
```
Induction variable `o` is a `unicode_type` and whilst we can "see" that `rec[o]` will return the same type regardless of the runtime value of `o`, Numba cannot.

In this case:
```python
@njit
def recarriterator2(rec):
    print(rec['a'][0])
    print(rec['b'][0])
    print(rec['c'][0])
    return
```
the equivalent of the induction variable `o` above is actually now a compile time constant and so Numba can work out precisely what is going on.

I half hoped that loop body versioning with [`literal_unroll`](https://numba.readthedocs.io/en/stable/reference/pysupported.html#heterogeneous-tuples) might help and tried:
```python
import numpy.random as random
from numba import njit, literal_unroll
import numpy

keys = ('a','b','c')

@njit
def recarriterator(rec):
    for o in literal_unroll(keys):
        print(rec[o])
    return

ldd=[]
ldk=[]
for o in keys:
    ldd.append(random.rand(5))
    ldk.append((o,ldd[-1].dtype))
rec = numpy.rec.fromarrays(ldd, dtype=ldk)

recarriterator(rec)
```
but this still fails because because the `getitem` on the recarray is still dynamic. This suggests that the dynamic->static getitem rewrite pass needs running after the transform that deals with loop body versioning, and if I do that, it works:
```patch
diff --git a/numba/core/compiler.py b/numba/core/compiler.py
index <???> 100644
--- a/numba/core/compiler.py
+++ b/numba/core/compiler.py
@@ -538,6 +538,7 @@ class DefaultPassBuilder(object):
 
         pm.add_pass(FindLiterallyCalls, "find literally calls")
         pm.add_pass(LiteralUnroll, "handles literal_unroll")
+        pm.add_pass(GenericRewrites, "nopython rewrites")
 
         if state.flags.enable_ssa:
             pm.add_pass(ReconstructSSA, "ssa")
```
example with above patch:
```
$ cat issue6634.py
import numpy.random as random
from numba import njit, literal_unroll
import numpy

keys = ('a','b','c')

@njit
def recarriterator(rec):
    for o in literal_unroll(keys):
        print(rec[o])
    return

ldd=[]
ldk=[]
for o in keys:
    ldd.append(random.rand(5))
    ldk.append((o,ldd[-1].dtype))
rec = numpy.rec.fromarrays(ldd, dtype=ldk)

$ python issue6634.py
[0.65043893 0.44405371 0.39395911 0.73871605 0.33635819]
[0.91916801 0.48012113 0.03028403 0.89127616 0.09568529]
[0.78778575 0.17708125 0.18378633 0.44769406 0.22908587]
```

Resolving patch:
diff --git a/numba/core/untyped_passes.py b/numba/core/untyped_passes.py
--- a/numba/core/untyped_passes.py
+++ b/numba/core/untyped_passes.py
@@ -1464,6 +1464,9 @@ def run_pass(self, state):
         pm.add_pass(RewriteSemanticConstants, "rewrite semantic constants")
         # do the unroll
         pm.add_pass(MixedContainerUnroller, "performs mixed container unroll")
+        # rewrite dynamic getitem to static getitem as it's possible some more
+        # getitems will now be statically resolvable
+        pm.add_pass(GenericRewrites, "Generic Rewrites")
         pm.finalize()
         pm.run(state)
         return True


Test patch:
diff --git a/numba/tests/test_record_dtype.py b/numba/tests/test_record_dtype.py
--- a/numba/tests/test_record_dtype.py
+++ b/numba/tests/test_record_dtype.py
@@ -1103,6 +1103,26 @@ def test_error_w_invalid_field(self):
         self.assertIn("Field 'f' was not found in record with fields "
                       "('first', 'second')", str(raises.exception))
 
+    def test_literal_unroll_dynamic_to_static_getitem_transform(self):
+        # See issue #6634
+        keys = ('a', 'b', 'c')
+        n = 5
+
+        def pyfunc(rec):
+            x = np.zeros((n,))
+            for o in literal_unroll(keys):
+                x += rec[o]
+            return x
+
+        dt = np.float64
+        ldd = [np.arange(dt(n)) for x in keys]
+        ldk = [(x, np.float64,) for x in keys]
+        rec = np.rec.fromarrays(ldd, dtype=ldk)
+
+        expected = pyfunc(rec)
+        got = njit(pyfunc)(rec)
+        np.testing.assert_allclose(expected, got)
+
 
 class TestRecordArraySetItem(unittest.TestCase):
     """