Repository: numba/numba

Issue:
parfors type inference issue, locked types
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

For more general "how do I do X?" type questions, please speak to us in real
time on https://gitter.im/numba/numba or post to the Numba mailing list
https://groups.google.com/a/continuum.io/forum/#!forum/numba-users.

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [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).

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->

From here, https://gitter.im/numba/numba?at=5c3fc06e8318994524ff75dd, this:
```python
from numba import njit, prange
import numpy as np

def foo():
    B = 0
    f = np.negative
    for i in prange(1):
        this_matters = f(1.)
        B += f(np.zeros(1,))[0]

    return B

print(foo())
print(njit(foo)())
print(njit(parallel=True)(foo)())
```
produces:
```
  File "<path>/numba/numba/typeinfer.py", line 76, in lock
    assert not self.locked
numba.errors.LoweringError: Failed in nopython mode pipeline (step: nopython mode backend)
Failed in nopython mode pipeline (step: nopython frontend)


File "gi53.py", line 7:
def foo():
    <source elided>
    f = np.negative
    for i in prange(1):
    ^

[1] During: lowering "id=2[LoopNest(index_variable = parfor_index.14, range = (0, $const12.2, 1))]{22: <ir.Block at gi53.py (7)>}Var(parfor_index.14, gi53.py (7))" at gi53.py (7)
```



Discussion:

Resolving patch:
diff --git a/numba/parfor.py b/numba/parfor.py
--- a/numba/parfor.py
+++ b/numba/parfor.py
@@ -3729,7 +3729,7 @@ def apply_copies_parfor(parfor, var_dict, name_var_table,
 ir_utils.apply_copy_propagate_extensions[Parfor] = apply_copies_parfor
 
 
-def push_call_vars(blocks, saved_globals, saved_getattrs):
+def push_call_vars(blocks, saved_globals, saved_getattrs, nested=False):
     """push call variables to right before their call site.
     assuming one global/getattr is created for each call site and control flow
     doesn't change it.
@@ -3753,11 +3753,11 @@ def process_assign(stmt):
                             saved_getattrs[lhs.name] = stmt
                             block_defs.add(lhs.name)
 
-            if isinstance(stmt, Parfor):
+            if not nested and isinstance(stmt, Parfor):
                 for s in stmt.init_block.body:
                     process_assign(s)
                 pblocks = stmt.loop_body.copy()
-                push_call_vars(pblocks, saved_globals, saved_getattrs)
+                push_call_vars(pblocks, saved_globals, saved_getattrs, nested=True)
                 new_body.append(stmt)
                 continue
             else:


Test patch:
diff --git a/numba/tests/test_parfors.py b/numba/tests/test_parfors.py
--- a/numba/tests/test_parfors.py
+++ b/numba/tests/test_parfors.py
@@ -2027,6 +2027,28 @@ def test_impl(x, y):
                         "in non-deterministic or unintended results.")
         self.assertIn(expected_msg, str(warning_obj.message))
 
+    @skip_unsupported
+    def test_nested_parfor_push_call_vars(self):
+        """ issue 3686: if a prange has something inside it that causes
+            a nested parfor to be generated and both the inner and outer
+            parfor use the same call variable defined outside the parfors
+            then ensure that when that call variable is pushed into the
+            parfor that the call variable isn't duplicated with the same
+            name resulting in a redundant type lock.
+        """
+        def test_impl():
+            B = 0
+            f = np.negative
+            for i in range(1):
+                this_matters = f(1.)
+                B += f(np.zeros(1,))[0]
+            for i in range(2):
+                this_matters = f(1.)
+                B += f(np.zeros(1,))[0]
+
+            return B
+        self.prange_tester(test_impl)
+
     @skip_unsupported
     def test_multiple_call_getattr_object(self):
         def test_impl(n):