Repository: numba/numba

Issue:
invalid undef variable when using unpacking optional type
``` python
from numba import njit, types
import numpy as np

optty = types.Optional(types.Tuple([types.intp, types.intp]))
sig = (optty,)
@njit(sig)
def bar(x):
    result = x
    if result is not None:
        a, b = result

bar(None)
```

yields

```
numba.errors.TypingError: Failed at nopython (nopython frontend)
Undefined variable 'a'
```

invalid undef variable when using unpacking optional type
``` python
from numba import njit, types
import numpy as np

optty = types.Optional(types.Tuple([types.intp, types.intp]))
sig = (optty,)
@njit(sig)
def bar(x):
    result = x
    if result is not None:
        a, b = result

bar(None)
```

yields

```
numba.errors.TypingError: Failed at nopython (nopython frontend)
Undefined variable 'a'
```



Discussion:

Resolving patch:
diff --git a/numba/lowering.py b/numba/lowering.py
--- a/numba/lowering.py
+++ b/numba/lowering.py
@@ -734,6 +734,11 @@ def lower_expr(self, resty, expr):
         elif expr.op == 'exhaust_iter':
             val = self.loadvar(expr.value.name)
             ty = self.typeof(expr.value.name)
+            # Unpack optional
+            if isinstance(ty, types.Optional):
+                val = self.context.cast(self.builder, val, ty, ty.type)
+                ty = ty.type
+
             # If we have a tuple, we needn't do anything
             # (and we can't iterate over the heterogenous ones).
             if isinstance(ty, types.BaseTuple):
diff --git a/numba/typeinfer.py b/numba/typeinfer.py
--- a/numba/typeinfer.py
+++ b/numba/typeinfer.py
@@ -245,9 +245,12 @@ def __call__(self, typeinfer):
             typevars = typeinfer.typevars
             oset = typevars[self.target]
             for tp in typevars[self.iterator.name].get():
+                # unpack optional
+                tp = tp.type if isinstance(tp, types.Optional) else tp
                 if isinstance(tp, types.BaseTuple):
                     if len(tp) == self.count:
                         typeinfer.add_type(self.target, tp, loc=self.loc)
+                        break
                     else:
                         raise ValueError("wrong tuple length for %r: "
                                          "expected %d, got %d"
@@ -256,6 +259,9 @@ def __call__(self, typeinfer):
                     tup = types.UniTuple(dtype=tp.iterator_type.yield_type,
                                          count=self.count)
                     typeinfer.add_type(self.target, tup, loc=self.loc)
+                    break
+            else:
+                raise TypingError("failed to unpack {}".format(tp), loc=self.loc)
 
 
 class PairFirstConstraint(object):


Test patch:
diff --git a/numba/tests/test_optional.py b/numba/tests/test_optional.py
--- a/numba/tests/test_optional.py
+++ b/numba/tests/test_optional.py
@@ -220,6 +220,24 @@ def pyfunc(x=None):
         self.assertEqual(pyfunc(), cfunc())
         self.assertEqual(pyfunc(3), cfunc(3))
 
+    def test_optional_unpack(self):
+        """
+        Issue 2171
+        """
+        def pyfunc(x):
+            if x is None:
+                return
+            else:
+                a, b = x
+                return a, b
+
+        tup = types.Tuple([types.intp] * 2)
+        opt_tup = types.Optional(tup)
+        sig = (opt_tup,)
+        cfunc = njit(sig)(pyfunc)
+        self.assertEqual(pyfunc(None), cfunc(None))
+        self.assertEqual(pyfunc((1, 2)), cfunc((1, 2)))
+
 
 if __name__ == '__main__':
     unittest.main()
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
@@ -232,6 +232,12 @@ def test_unpack_nrt(self):
         cfunc = cr.entry_point
         self.assertPreciseEqual(cfunc(), pyfunc())
 
+    def test_invalid_unpack(self):
+        pyfunc = unpack_arbitrary
+        with self.assertRaises(errors.TypingError) as raises:
+            compile_isolated(pyfunc, (types.int32,), flags=no_pyobj_flags)
+        self.assertIn("failed to unpack int32", str(raises.exception))
+
 
 if __name__ == '__main__':
     unittest.main()