Repository: numba/numba

Issue:
Jit produces wrong code with version 0.49.0
[Our library's](https://github.com/pymatting/pymatting/issues/15) tests [fail on all platforms](https://travis-ci.org/github/pymatting/pymatting/builds/678580669) when upgrading from numba version `0.48.0` to `0.49.0`.

The smallest test case that I could come up with that still reproduces the issue is the following:

```Python
import numpy as np
from numba import jit

def foo():
    values = np.zeros(1)
    tmp = 666
    if tmp:
        values[0] = tmp
        print(values)

print("Without jit")
foo()
print("With jit")
jit(foo)()
```

It prints `666.0` with Python and `1.0` when jitted.

- [ 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).


Discussion:

Patch:
diff --git a/numba/core/analysis.py b/numba/core/analysis.py
index 54342b488..9c5423738 100644
--- a/numba/core/analysis.py
+++ b/numba/core/analysis.py
@@ -283,6 +283,10 @@ def _fix_loop_exit(cfg, loop):
         return loop
 
 
+# Used to describe a nullified condition in dead branch pruning
+nullified = namedtuple('nullified', 'condition, taken_br, rewrite_stmt')
+
+
 # Functions to manipulate IR
 def dead_branch_prune(func_ir, called_args):
     """
@@ -429,7 +433,8 @@ def dead_branch_prune(func_ir, called_args):
                 prune_stat, taken = prune(branch, condition, blk, *const_conds)
                 if(prune_stat):
                     # add the condition to the list of nullified conditions
-                    nullified_conditions.append((condition, taken))
+                    nullified_conditions.append(nullified(condition, taken,
+                                                          True))
         else:
             # see if this is a branch on a constant value predicate
             resolved_const = Unknown()
@@ -444,7 +449,8 @@ def dead_branch_prune(func_ir, called_args):
                 prune_stat, taken = prune_by_predicate(branch, condition, blk)
                 if(prune_stat):
                     # add the condition to the list of nullified conditions
-                    nullified_conditions.append((condition, taken))
+                    nullified_conditions.append(nullified(condition, taken,
+                                                          False))
 
     # 'ERE BE DRAGONS...
     # It is the evaluation of the condition expression that often trips up type
@@ -461,18 +467,22 @@ def dead_branch_prune(func_ir, called_args):
     # completeness the func_ir._definitions and ._consts are also updated to
     # make the IR state self consistent.
 
-    deadcond = [x[0] for x in nullified_conditions]
+    deadcond = [x.condition for x in nullified_conditions]
     for _, cond, blk in branch_info:
         if cond in deadcond:
             for x in blk.body:
                 if isinstance(x, ir.Assign) and x.value is cond:
                     # rewrite the condition as a true/false bit
-                    branch_bit = nullified_conditions[deadcond.index(cond)][1]
-                    x.value = ir.Const(branch_bit, loc=x.loc)
-                    # update the specific definition to the new const
-                    defns = func_ir._definitions[x.target.name]
-                    repl_idx = defns.index(cond)
-                    defns[repl_idx] = x.value
+                    nullified_info = nullified_conditions[deadcond.index(cond)]
+                    # only do a rewrite of conditions, predicates need to retain
+                    # their value as they may be used later.
+                    if nullified_info.rewrite_stmt:
+                        branch_bit = nullified_info.taken_br
+                        x.value = ir.Const(branch_bit, loc=x.loc)
+                        # update the specific definition to the new const
+                        defns = func_ir._definitions[x.target.name]
+                        repl_idx = defns.index(cond)
+                        defns[repl_idx] = x.value
 
     # Remove dead blocks, this is safe as it relies on the CFG only.
     cfg = compute_cfg_from_blocks(func_ir.blocks)
diff --git a/numba/typed/typedlist.py b/numba/typed/typedlist.py
index 82259b5bc..2d3efd195 100644
--- a/numba/typed/typedlist.py
+++ b/numba/typed/typedlist.py
@@ -355,14 +355,13 @@ class List(MutableSequence):
         return _pop(self, i)
 
     def extend(self, iterable):
-        # Empty iterable, do nothing
-        if len(iterable) == 0:
-            return self
         if not self._typed:
             # Need to get the first element of the iterable to initialise the
             # type of the list. FIXME: this may be a problem if the iterable
             # can not be sliced.
             self._initialise_list(iterable[0])
+            self.append(iterable[0])
+            return _extend(self, iterable[1:])
         return _extend(self, iterable)
 
     def remove(self, item):


Test patch:
diff --git a/numba/tests/test_analysis.py b/numba/tests/test_analysis.py
index 89336d3b8..72ae9a935 100644
--- a/numba/tests/test_analysis.py
+++ b/numba/tests/test_analysis.py
@@ -697,7 +697,7 @@ class TestBranchPrunePredicates(TestBranchPruneBase, SerialMixin):
 
                 def func(x):
                     if const:
-                        return 3.14159
+                        return 3.14159, const
                 self.assert_prune(func, (types.NoneType('none'),), [prune],
                                   None)
 
@@ -707,7 +707,7 @@ class TestBranchPrunePredicates(TestBranchPruneBase, SerialMixin):
 
                 def func(x):
                     if not const:
-                        return 3.14159
+                        return 3.14159, const
                 self.assert_prune(func, (types.NoneType('none'),), [prune],
                                   None)
 
@@ -717,9 +717,9 @@ class TestBranchPrunePredicates(TestBranchPruneBase, SerialMixin):
 
                 def func(x):
                     if const:
-                        return 3.14159
+                        return 3.14159, const
                     else:
-                        return 1.61803
+                        return 1.61803, const
                 self.assert_prune(func, (types.NoneType('none'),), [prune],
                                   None)
 
@@ -729,9 +729,9 @@ class TestBranchPrunePredicates(TestBranchPruneBase, SerialMixin):
 
                 def func(x):
                     if not const:
-                        return 3.14159
+                        return 3.14159, const
                     else:
-                        return 1.61803
+                        return 1.61803, const
                 self.assert_prune(func, (types.NoneType('none'),), [prune],
                                   None)
 
@@ -746,7 +746,7 @@ class TestBranchPrunePredicates(TestBranchPruneBase, SerialMixin):
 
                 def func(x):
                     if c_test_single_if_global:
-                        return 3.14159
+                        return 3.14159, c_test_single_if_global
 
                 self.assert_prune(func, (types.NoneType('none'),), [prune],
                                   None)
@@ -760,7 +760,7 @@ class TestBranchPrunePredicates(TestBranchPruneBase, SerialMixin):
 
                 def func(x):
                     if c_test_single_if_negate_global:
-                        return 3.14159
+                        return 3.14159, c_test_single_if_negate_global
 
                 self.assert_prune(func, (types.NoneType('none'),), [prune],
                                   None)
@@ -774,9 +774,9 @@ class TestBranchPrunePredicates(TestBranchPruneBase, SerialMixin):
 
                 def func(x):
                     if c_test_single_if_else_global:
-                        return 3.14159
+                        return 3.14159, c_test_single_if_else_global
                     else:
-                        return 1.61803
+                        return 1.61803, c_test_single_if_else_global
                 self.assert_prune(func, (types.NoneType('none'),), [prune],
                                   None)
 
@@ -789,12 +789,25 @@ class TestBranchPrunePredicates(TestBranchPruneBase, SerialMixin):
 
                 def func(x):
                     if not c_test_single_if_else_negate_global:
-                        return 3.14159
+                        return 3.14159, c_test_single_if_else_negate_global
                     else:
-                        return 1.61803
+                        return 1.61803, c_test_single_if_else_negate_global
                 self.assert_prune(func, (types.NoneType('none'),), [prune],
                                   None)
 
+    def test_issue_5618(self):
+
+        @njit
+        def foo():
+            values = np.zeros(1)
+            tmp = 666
+            if tmp:
+                values[0] = tmp
+            return values
+
+        self.assertPreciseEqual(foo.py_func()[0], 666.)
+        self.assertPreciseEqual(foo()[0], 666.)
+
 
 class TestBranchPrunePostSemanticConstRewrites(TestBranchPruneBase):
     # Tests that semantic constants rewriting works by virtue of branch pruning
diff --git a/numba/tests/test_typedlist.py b/numba/tests/test_typedlist.py
index 3ce886f07..72f17fb5a 100644
--- a/numba/tests/test_typedlist.py
+++ b/numba/tests/test_typedlist.py
@@ -738,33 +738,6 @@ class TestExtend(MemoryLeakMixin, TestCase):
         got = impl()
         self.assertEqual(expected, got)
 
-    def test_extend_single_value_container(self):
-        @njit
-        def impl():
-            l = List()
-            l.extend((100,))
-            return l
-
-        expected = impl.py_func()
-        got = impl()
-        self.assertEqual(expected, got)
-
-    def test_extend_empty_unrefined(self):
-        # Extending an unrefined list with an empty iterable doesn't work in a
-        # jit compiled function as the list remains untyped.
-        l = List()
-        l.extend(tuple())
-        self.assertEqual(len(l), 0)
-        self.assertFalse(l._typed)
-
-    def test_extend_empty_refiend(self):
-        # Extending a refined list with an empty iterable doesn't work in a
-        # jit compiled function as the (empty) argument can't be typed
-        l = List((1,))
-        l.extend(tuple())
-        self.assertEqual(len(l), 1)
-        self.assertTrue(l._typed)
-
 
 @njit
 def cmp(a, b):