Repository: numba/numba

Issue:
Regression in 0.53.0rc1 for boolean conversion
See reproducer below. Reverting the temporary assignment change (d938288) solves the issue, but the underlying bug is most probably something else. This only happens with inlining so maybe inliner needs a closer look.

```python
import numba
import numpy as np
import pandas as pd

val_one = True

def f(S):
    pass

@numba.extending.overload(f, inline="always")
def f_overload(A):

    def impl(A):
        for i in range(len(A)):
            val = val_one
            if A[i]:
                val = A[i]
        return val
    return impl

@numba.njit
def f2(A):
    return f(A)

print(f2(np.array([True, False, True])))
```
```
Traceback (most recent call last):
  File "../bool_cast_err2.py", line 25, in <module>
    print(f2(np.array([True, False, True])))
  File "/Users/ehsan/tmp/numba/numba/core/dispatcher.py", line 420, in _compile_for_args
    error_rewrite(e, 'typing')
  File "/Users/ehsan/tmp/numba/numba/core/dispatcher.py", line 361, in error_rewrite
    raise e.with_traceback(None)
numba.core.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
No implementation of function Function(<function f at 0x7fc275ff3f70>) found for signature:

 >>> f(array(bool, 1d, C))

There are 2 candidate implementations:
  - Of which 2 did not match due to:
  Overload in function 'f_overload': File: ../bool_cast_err2.py: Line 10.
    With argument(s): '(array(bool, 1d, C))':
   Rejected as the implementation raised a specific error:
     TypingError: No conversion from bool to Literal[bool](True) for 'val', defined at ../bool_cast_err2.py (15)

   File "../bool_cast_err2.py", line 17:
       def impl(A):
           <source elided>
               if A[i]:
                   val = A[i]
                   ^

   During: typing of intrinsic-call at ../bool_cast_err2.py (17)

   File "../bool_cast_err2.py", line 17:
       def impl(A):
           <source elided>
               if A[i]:
                   val = A[i]
                   ^

  raised from /Users/ehsan/tmp/numba/numba/core/typeinfer.py:1071

During: resolving callee type: Function(<function f at 0x7fc275ff3f70>)
During: typing of call at ../bool_cast_err2.py (23)


File "../bool_cast_err2.py", line 23:
def f2(A):
    return f(A)
    ^
```


Discussion:
Many thanks for the report!

I can reproduce the issue with current master, and also observe that reverting the merge d938288 resolves the issue. The issue doesn't reproduce with Numba 0.52, hence is a regression.

Resolving patch:
diff --git a/numba/core/inline_closurecall.py b/numba/core/inline_closurecall.py
--- a/numba/core/inline_closurecall.py
+++ b/numba/core/inline_closurecall.py
@@ -413,6 +413,8 @@ def copy_ir(the_ir):
             if arg_typs is None:
                 raise TypeError('arg_typs should have a value not None')
             self.update_type_and_call_maps(callee_ir, arg_typs)
+            # update_type_and_call_maps replaces blocks
+            callee_blocks = callee_ir.blocks
 
         self.debug_print("After arguments rename: ")
         _debug_dump(callee_ir)
@@ -462,10 +464,13 @@ def inline_function(self, caller_ir, block, i, function, arg_typs=None):
         return self.inline_ir(caller_ir, block, i, callee_ir, freevars,
                               arg_typs=arg_typs)
 
-    def run_untyped_passes(self, func):
+    def run_untyped_passes(self, func, enable_ssa=False):
         """
         Run the compiler frontend's untyped passes over the given Python
         function, and return the function's canonical Numba IR.
+
+        Disable SSA transformation by default, since the call site won't be in SSA
+        form and self.inline_ir depends on this being the case.
         """
         from numba.core.compiler import StateDict, _CompileStatus
         from numba.core.untyped_passes import ExtractByteCode, WithLifting
@@ -478,10 +483,7 @@ def run_untyped_passes(self, func):
         state.locals = self.locals
         state.pipeline = self.pipeline
         state.flags = self.flags
-
-        # Disable SSA transformation, the call site won't be in SSA form and
-        # self.inline_ir depends on this being the case.
-        state.flags.enable_ssa = False
+        state.flags.enable_ssa = enable_ssa
 
         state.func_id = bytecode.FunctionIdentity.from_function(func)
 
@@ -507,6 +509,9 @@ def run_untyped_passes(self, func):
     def update_type_and_call_maps(self, callee_ir, arg_typs):
         """ Updates the type and call maps based on calling callee_ir with arguments
         from arg_typs"""
+        from numba.core.ssa import reconstruct_ssa
+        from numba.core.typed_passes import PreLowerStripPhis
+
         if not self._permit_update_type_and_call_maps:
             msg = ("InlineWorker instance not configured correctly, typemap or "
                    "calltypes missing in initialization.")
@@ -515,8 +520,13 @@ def update_type_and_call_maps(self, callee_ir, arg_typs):
         # call branch pruning to simplify IR and avoid inference errors
         callee_ir._definitions = ir_utils.build_definitions(callee_ir.blocks)
         numba.core.analysis.dead_branch_prune(callee_ir, arg_typs)
+        # callee's typing may require SSA
+        callee_ir = reconstruct_ssa(callee_ir)
+        callee_ir._definitions = ir_utils.build_definitions(callee_ir.blocks)
         f_typemap, f_return_type, f_calltypes, _ = typed_passes.type_inference_stage(
                 self.typingctx, callee_ir, arg_typs, None)
+        callee_ir = PreLowerStripPhis()._strip_phi_nodes(callee_ir)
+        callee_ir._definitions = ir_utils.build_definitions(callee_ir.blocks)
         canonicalize_array_math(callee_ir, f_typemap,
                                 f_calltypes, self.typingctx)
         # remove argument entries like arg.a from typemap
diff --git a/numba/core/typing/templates.py b/numba/core/typing/templates.py
--- a/numba/core/typing/templates.py
+++ b/numba/core/typing/templates.py
@@ -594,6 +594,8 @@ def generic(self, args, kws):
         Type the overloaded function by compiling the appropriate
         implementation for the given args.
         """
+        from numba.core.typed_passes import PreLowerStripPhis
+
         disp, new_args = self._get_impl(args, kws)
         if disp is None:
             return
@@ -633,7 +635,9 @@ def generic(self, args, kws):
             # situations that will succeed. For context see #5887.
             resolve = disp_type.dispatcher.get_call_template
             template, pysig, folded_args, kws = resolve(new_args, kws)
-            ir = inline_worker.run_untyped_passes(disp_type.dispatcher.py_func)
+            ir = inline_worker.run_untyped_passes(
+                disp_type.dispatcher.py_func, enable_ssa=True
+            )
 
             (
                 typemap,
@@ -642,6 +646,9 @@ def generic(self, args, kws):
                 _
             ) = typed_passes.type_inference_stage(
                 self.context, ir, folded_args, None)
+            ir = PreLowerStripPhis()._strip_phi_nodes(ir)
+            ir._definitions = numba.core.ir_utils.build_definitions(ir.blocks)
+
             sig = Signature(return_type, folded_args, None)
             # this stores a load of info for the cost model function if supplied
             # it by default is None


Test patch:
diff --git a/numba/tests/test_ir_inlining.py b/numba/tests/test_ir_inlining.py
--- a/numba/tests/test_ir_inlining.py
+++ b/numba/tests/test_ir_inlining.py
@@ -1024,6 +1024,33 @@ def foo(dtype):
         dtype = 'int64'
         self.check(test_impl, dtype, inline_expect={'foo': True})
 
+    def test_inline_always_ssa(self):
+        """make sure IR inlining uses SSA properly. Test for #6721.
+        """
+
+        dummy_true = True
+
+        def foo(A):
+            return True
+
+        @overload(foo, inline="always")
+        def foo_overload(A):
+
+            def impl(A):
+                s = dummy_true
+                for i in range(len(A)):
+                    dummy = dummy_true
+                    if A[i]:
+                        dummy = A[i]
+                    s *= dummy
+                return s
+            return impl
+
+        def impl():
+            return foo(np.array([True, False, True]))
+
+        self.check(impl, block_count='SKIP', inline_expect={'foo': True})
+
 
 class TestOverloadMethsAttrsInlining(InliningBase):
     def setUp(self):