Repository: numba/numba

Issue:
literal_unroll fails when mixing iteration over untyped list
- [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).

Hi,

I've encountered the following problem trying to apply literal_unroll with mixed accessing to other homogeneous list, which fails at "step: performs mixed container unroll" with KeyError. Strangely it only fails when building a list inside nopython function (either from literal values or iterating over some sequence), but passes if typed.List is used or if mixed container is a reflected_list passed as an argument to the function. Here's the reproducer:

```
import numba
from numba import types
from numba.typed import List
from numba import literal_unroll

import unittest

class TestSuite(unittest.TestCase):

    # literal_unroll with mixed iteration doesn't work if mixed container
    # is an untyped list built inside jitted function
    def test_numba_literal_unroll_1(self):
        def test_impl(cont, n):
            i = 0
            # failing case #1:
            normal_list = [0, 1, 2, 3]

            # failing case #2:
            # normal_list = [j for j in range(n)]

            # failing case #2:
            # normal_list = [a for a in cont]

            # working case:
            typed_list = List.empty_list(types.int64)
            typed_list.extend(normal_list)

            mixed_cont = normal_list            # this fails to compile
#             mixed_cont = typed_list           # this can be a workaround
            heter_tuple = ('a', 25, 0.23, None)
            for item in literal_unroll(heter_tuple):
                print(item, ":", mixed_cont[i])
                i += 1

            return i
        hpat_func = numba.njit(test_impl)

        n = 4
        data = [j for j in range(n)]
        hpat_func(data, n)


if __name__ == "__main__":
    unittest.main()

```

Gives:
```
ERROR: test_numba_literal_unroll_1 (__main__.TestSuite)
----------------------------------------------------------------------
Traceback (most recent call last):
  File ".\example_test.py", line 40, in test_numba_literal_unroll_1
    hpat_func(data, n)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\dispatcher.py", line 420, in _compile_for_args
    raise e
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\dispatcher.py", line 353, in _compile_for_args
    return self.compile(tuple(argtypes))
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler_lock.py", line 32, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\dispatcher.py", line 794, in compile
    cres = self._compiler.compile(args, return_type)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\dispatcher.py", line 77, in compile
    status, retval = self._compile_cached(args, return_type)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\dispatcher.py", line 91, in _compile_cached
    retval = self._compile_core(args, return_type)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\dispatcher.py", line 104, in _compile_core
    cres = compiler.compile_extra(self.targetdescr.typing_context,
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler.py", line 568, in compile_extra
    return pipeline.compile_extra(func)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler.py", line 339, in compile_extra
    return self._compile_bytecode()
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler.py", line 401, in _compile_bytecode
    return self._compile_core()
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler.py", line 381, in _compile_core
    raise e
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler.py", line 372, in _compile_core
    pm.run(self.state)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler_machinery.py", line 341, in run
    raise patched_exception
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler_machinery.py", line 332, in run
    self._runPass(idx, pass_inst, state)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler_lock.py", line 32, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler_machinery.py", line 291, in _runPass
    mutated |= check(pss.run_pass, internal_state)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler_machinery.py", line 264, in check
    mangled = func(compiler_state)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\untyped_passes.py", line 1447, in run_pass
    pm.run(state)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler_machinery.py", line 341, in run
    raise patched_exception
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler_machinery.py", line 332, in run
    self._runPass(idx, pass_inst, state)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler_lock.py", line 32, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler_machinery.py", line 291, in _runPass
    mutated |= check(pss.run_pass, internal_state)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\compiler_machinery.py", line 264, in check
    mangled = func(compiler_state)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\untyped_passes.py", line 1224, in run_pass
    stat = self.apply_transform(state)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\untyped_passes.py", line 1092, in apply_transform
    self.unroll_loop(state, info)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\untyped_passes.py", line 1130, in unroll_loop
    args = getattr(dfn, 'args', False)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\nu
mba\core\ir.py", line 382, in __getattr__
    return self._kws[name]
KeyError: 'Failed in nopython mode pipeline (step: handles literal_unroll)\n"Fai
led in literal_unroll_subpipeline mode pipeline (step: performs mixed container
unroll)\\n\'args\'"'

----------------------------------------------------------------------
Ran 1 test in 0.418s

FAILED (errors=1)
```

**Numba version:**  0.49.0.dev0+1044.gcd242db80

Can you please check it? 

Best Regards,
Alexey.


Discussion:

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
@@ -1054,7 +1054,10 @@ def get_call_args(init_arg, want):
                                                     stmt.value.value)
                                         if dfn is None:
                                             continue
-                                        args = getattr(dfn, 'args', False)
+                                        try:
+                                            args = getattr(dfn, 'args', False)
+                                        except KeyError:
+                                            continue
                                         if not args:
                                             continue
                                         if not args[0] == arg:
@@ -1127,7 +1130,10 @@ def unroll_loop(self, state, loop_info):
                         # try a couple of spellings... a[i] and ref(a)[i]
                         if stmt.value.value != getitem_target:
                             dfn = func_ir.get_definition(stmt.value.value)
-                            args = getattr(dfn, 'args', False)
+                            try:
+                                args = getattr(dfn, 'args', False)
+                            except KeyError:
+                                continue
                             if not args:
                                 continue
                             if not args[0] == getitem_target:


Test patch:
diff --git a/numba/tests/test_mixed_tuple_unroller.py b/numba/tests/test_mixed_tuple_unroller.py
--- a/numba/tests/test_mixed_tuple_unroller.py
+++ b/numba/tests/test_mixed_tuple_unroller.py
@@ -2,7 +2,7 @@
 import numpy as np
 
 from numba.tests.support import (TestCase, MemoryLeakMixin,
-                                 skip_parfors_unsupported)
+                                 skip_parfors_unsupported, captured_stdout)
 from numba import njit, typed, literal_unroll, prange
 from numba.core import types, errors, ir
 from numba.testing import unittest
@@ -1653,7 +1653,7 @@ def foo():
         self.assertRegexpMatches(str(raises.exception), re)
 
     def test_unroll_tuple_of_dict(self):
-        from numba.tests.support import captured_stdout
+
         @njit
         def foo():
             x = {}
@@ -1772,6 +1772,35 @@ def foo():
 
         self.assertEqual(foo(), foo.py_func())
 
+    def test_unroll_indexing_list(self):
+        # See issue #5477
+        @njit
+        def foo(cont):
+            i = 0
+            acc = 0
+            normal_list = [a for a in cont]
+            heter_tuple = ('a', 25, 0.23, None)
+            for item in literal_unroll(heter_tuple):
+                acc += normal_list[i]
+                i += 1
+                print(item)
+            return i, acc
+
+        data = [j for j in range(4)]
+
+        # send stdout to nowhere, just check return values
+        with captured_stdout():
+            self.assertEqual(foo(data), foo.py_func(data))
+
+        # now capture stdout for jit function and check
+        with captured_stdout() as stdout:
+            foo(data)
+        lines = stdout.getvalue().splitlines()
+        self.assertEqual(
+            lines,
+            ['a', '25', '0.23', 'None'],
+        )
+
 
 def capture(real_pass):
     """ Returns a compiler pass that captures the mutation state reported