Repository: numba/numba

Issue:
Tuple unpacking has incomplete error checking
```
>>> ff = jit(f, nopython=True)
>>> ff((4,5,6,7))
(4, 5, 6)
>>> ff((4,5))
Traceback (most recent call last):
[...]
numba.lowering.LoweringError: Failed at nopython (nopython mode backend)
Internal error:
TypeError: Can't index at [2] in [2 x i32]
File "<stdin>", line 2
```



Discussion:

Resolving patch:
diff --git a/numba/lowering.py b/numba/lowering.py
--- a/numba/lowering.py
+++ b/numba/lowering.py
@@ -541,9 +541,9 @@ def lower_expr(self, resty, expr):
         elif expr.op == 'exhaust_iter':
             val = self.loadvar(expr.value.name)
             ty = self.typeof(expr.value.name)
-            # If we have a heterogenous tuple, we needn't do anything,
-            # and we can't iterate over it anyway.
-            if isinstance(ty, types.Tuple):
+            # If we have a tuple, we needn't do anything
+            # (and we can't iterate over the heterogenous ones).
+            if isinstance(ty, types.BaseTuple):
                 return val
 
             itemty = ty.iterator_type.yield_type
diff --git a/numba/typeinfer.py b/numba/typeinfer.py
--- a/numba/typeinfer.py
+++ b/numba/typeinfer.py
@@ -155,11 +155,12 @@ def __init__(self, target, count, iterator, loc):
     def __call__(self, context, typevars):
         oset = typevars[self.target]
         for tp in typevars[self.iterator.name].get():
-            if isinstance(tp, types.IterableType):
+            if isinstance(tp, types.BaseTuple):
+                if len(tp) == self.count:
+                    oset.add_types(tp)
+            elif isinstance(tp, types.IterableType):
                 oset.add_types(types.UniTuple(dtype=tp.iterator_type.yield_type,
                                               count=self.count))
-            elif isinstance(tp, types.Tuple):
-                oset.add_types(tp)
 
 
 class PairFirstConstrain(object):


Test patch:
diff --git a/numba/tests/test_unpack_sequence.py b/numba/tests/test_unpack_sequence.py
--- a/numba/tests/test_unpack_sequence.py
+++ b/numba/tests/test_unpack_sequence.py
@@ -56,6 +56,16 @@ def unpack_tuple_too_large():
     return a + b + c
 
 
+def unpack_heterogenous_tuple_too_small():
+    a, b, c = (1, 2.5j)
+    return a + b + c
+
+
+def unpack_heterogenous_tuple_too_large():
+    a, b, c = (1, 2.5, 3j, 4)
+    return a + b + c
+
+
 def unpack_heterogenous_tuple():
     a, b, c = (1, 2.5, 3j)
     return a + b + c
@@ -137,23 +147,31 @@ def test_chained_unpack_assign(self, flags=force_pyobj_flags):
     def test_chained_unpack_assign_npm(self):
         self.test_chained_unpack_assign(flags=no_pyobj_flags)
 
-    def check_unpack_error(self, pyfunc, flags=force_pyobj_flags):
-        cr = compile_isolated(pyfunc, (), flags=flags)
-        cfunc = cr.entry_point
-        with self.assertRaises(ValueError):
+    def check_unpack_error(self, pyfunc, flags=force_pyobj_flags, exc=ValueError):
+        with self.assertRaises(exc):
+            cr = compile_isolated(pyfunc, (), flags=flags)
+            cfunc = cr.entry_point
             cfunc()
 
     def test_unpack_tuple_too_small(self):
         self.check_unpack_error(unpack_tuple_too_small)
+        self.check_unpack_error(unpack_heterogenous_tuple_too_small)
 
     def test_unpack_tuple_too_small_npm(self):
-        self.check_unpack_error(unpack_tuple_too_small, no_pyobj_flags)
+        self.check_unpack_error(unpack_tuple_too_small, no_pyobj_flags,
+                                TypeError)
+        self.check_unpack_error(unpack_heterogenous_tuple_too_small,
+                                no_pyobj_flags, TypeError)
 
     def test_unpack_tuple_too_large(self):
         self.check_unpack_error(unpack_tuple_too_large)
+        self.check_unpack_error(unpack_heterogenous_tuple_too_large)
 
     def test_unpack_tuple_too_large_npm(self):
-        self.check_unpack_error(unpack_tuple_too_large, no_pyobj_flags)
+        self.check_unpack_error(unpack_tuple_too_large, no_pyobj_flags,
+                                TypeError)
+        self.check_unpack_error(unpack_heterogenous_tuple_too_large,
+                                no_pyobj_flags, TypeError)
 
     def test_unpack_range_too_small(self):
         self.check_unpack_error(unpack_range_too_small)