Repository: numba/numba

Issue:
Loop lift case causes incorrect liveness analysis
This:
```python
from numba import jit

def bar(x):
    return x

@jit
def foo(x):
    h = 0.
    for k in range(x):
        h = h + k
    h = h - bar(x)
    return h

print(foo(10))
```
gives:
```
    assert name in self._live_vars, name
numba.errors.LoweringError: Failed at object (object mode frontend)
Failed at object (object mode backend)
```
The IR looks like:
```
----------------------------------IR DUMP: foo----------------------------------
label 0:
    x = arg(0, name=x)                       ['x']
    $const0.1 = const(float, 0.0)            ['$const0.1']
    h = $const0.1                            ['$const0.1', 'h']
    del $const0.1                            []
    jump 6                                   []
label 6:
    jump 9                                   []
label 9:
    $25 = const(LiftedLoop, LiftedLoop(<function foo at 0x7f99ebb18bf8>)) ['$25']
    $26 = call $25(h.1, x)                   ['$25', '$26', 'h.1', 'x']
    del $25                                  []
    h.1 = static_getitem(index_var=None, value=$26, index=0) ['$26', 'h.1']
    del h.1                                  []
    x = static_getitem(index_var=None, value=$26, index=1) ['$26', 'x']
    del $26                                  []
    jump 39                                  []
label 39:
    $39.2 = global(bar: <function bar at 0x7f99fe2fa048>) ['$39.2']
    $39.4 = call $39.2(x)                    ['$39.2', '$39.4', 'x']
    del x                                    []
    del $39.2                                []
    $39.5 = h - $39.4                        ['$39.4', '$39.5', 'h']
    del h                                    []
    del $39.4                                []
    h.1 = $39.5                              ['$39.5', 'h.1']
    del $39.5                                []
    $39.7 = cast(value=h.1)                  ['$39.7', 'h.1']
    del h.1                                  []
    return $39.7                             ['$39.7']
```
Prior to the loop lift the IR appears to be valid.


Discussion:

Resolving patch:
diff --git a/numba/ir.py b/numba/ir.py
--- a/numba/ir.py
+++ b/numba/ir.py
@@ -644,10 +644,17 @@ def define(self, name, loc):
 
     def get(self, name):
         """
-        Refer to a variable
+        Refer to a variable.  Returns the latest version.
         """
         if name in self.redefined:
             name = "%s.%d" % (name, self.redefined[name])
+        return self.get_exact(name)
+
+    def get_exact(self, name):
+        """
+        Refer to a variable.  The returned variable has the exact
+        name (exact variable version).
+        """
         try:
             return self.localvars.get(name)
         except NotDefinedError:
diff --git a/numba/transforms.py b/numba/transforms.py
--- a/numba/transforms.py
+++ b/numba/transforms.py
@@ -71,20 +71,19 @@ def _loop_lift_modify_call_block(liftedloop, block, inputs, outputs, returnto):
     scope = block.scope
     loc = block.loc
     blk = ir.Block(scope=scope, loc=loc)
-
     # load loop
     fn = ir.Const(value=liftedloop, loc=loc)
     fnvar = scope.make_temp(loc=loc)
     blk.append(ir.Assign(target=fnvar, value=fn, loc=loc))
     # call loop
-    args = [scope.get(name) for name in inputs]
+    args = [scope.get_exact(name) for name in inputs]
     callexpr = ir.Expr.call(func=fnvar, args=args, kws=(), loc=loc)
     # temp variable for the return value
     callres = scope.make_temp(loc=loc)
     blk.append(ir.Assign(target=callres, value=callexpr, loc=loc))
     # unpack return value
     for i, out in enumerate(outputs):
-        target = scope.get(out)
+        target = scope.get_exact(out)
         getitem = ir.Expr.static_getitem(value=callres, index=i,
                                          index_var=None, loc=loc)
         blk.append(ir.Assign(target=target, value=getitem, loc=loc))
@@ -128,7 +127,7 @@ def make_epilogue():
 
         block = ir.Block(scope=scope, loc=loc)
         # prepare tuples to return
-        vals = [scope.get(name=name) for name in loopinfo.outputs]
+        vals = [scope.get_exact(name=name) for name in loopinfo.outputs]
         tupexpr = ir.Expr.build_tuple(items=vals, loc=loc)
         tup = scope.make_temp(loc=loc)
         block.append(ir.Assign(target=tup, value=tupexpr, loc=loc))


Test patch:
diff --git a/numba/tests/test_looplifting.py b/numba/tests/test_looplifting.py
--- a/numba/tests/test_looplifting.py
+++ b/numba/tests/test_looplifting.py
@@ -406,6 +406,27 @@ def pyfunc(a):
             cfunc = jit(forceobj=True)(pyfunc)
             self.assertEqual(pyfunc(True), cfunc(True))
 
+    def test_variable_scope_bug(self):
+        """
+        https://github.com/numba/numba/issues/2179
+
+        Looplifting transformation is using the wrong verion of variable `h`.
+        """
+        from numba import jit
+
+        def bar(x):
+            return x
+
+        def foo(x):
+            h = 0.
+            for k in range(x):
+                h = h + k
+            h = h - bar(x)
+            return h
+
+        cfoo = jit(foo)
+        self.assertEqual(foo(10), cfoo(10))
+
 
 if __name__ == '__main__':
     unittest.main()