Repository: numba/numba

Issue:
Python 3.10 kwarg peephole rewrite failing in valid case (breaks mainline)
<!--

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).

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I have tried using the latest released version of Numba (most recent is
 visible in the change log (https://github.com/numba/numba/blob/main/CHANGE_LOG).
- [x] I have included a self contained code sample to reproduce the problem.
  i.e. it's possible to run as 'python bug.py'.

This breaks on mainline and is a blocking issue for 0.56.
```python
from numba import njit, objmode

def func(*args):
    pass

@njit
def log(*args):
    with objmode():
        func(*args)

log()
```
the above reproducer is extracted from the failing tests found  by running on Python 3.10:
```
./runtests.py numba.tests.test_ndarray_subclasses
```


Discussion:
CC @njriasan 
Running the above on Python 3.10 results in:
```
numba/core/interpreter.py", line 708, in peep_hole_call_function_ex_to_call_function_kw
    raise UnsupportedError(errmsg)
numba.core.errors.UnsupportedError: Failed in nopython mode pipeline (step: analyzing bytecode)

CALL_FUNCTION_EX with **kwargs not supported.
If you are not using **kwargs this may indicate that
you have a large number of kwargs and are using inlined control
flow. You can resolve this issue by moving the control flow out of
the function call. For example, if you have

    f(a=1 if flag else 0, ...)

Replace that with:

    a_val = 1 if flag else 0
    f(a=a_val, ...)
```
@stuartarchibald. I'll take a look. Thanks for the ping.

Resolving patch:
diff --git a/numba/core/interpreter.py b/numba/core/interpreter.py
--- a/numba/core/interpreter.py
+++ b/numba/core/interpreter.py
@@ -90,19 +90,6 @@ def _remove_assignment_definition(old_body, idx, func_ir):
     rhs = old_body[idx].value
     if rhs in func_ir._definitions[lhs]:
         func_ir._definitions[lhs].remove(rhs)
-    elif len(func_ir._definitions[lhs]) == 1:
-        # There appears to be an issue where the RHS of the
-        # definitions doesn't match after the list -> tuple
-        # conversion.
-        # For example the statement is:
-        #
-        #   172list_to_tuple.116 = $170list_append.115
-        #
-        # but the definition is:
-        #
-        #   list_to_tuple(info=('$4build_list.1',))
-        #
-        func_ir._definitions[lhs].clear()
     else:
         raise UnsupportedError(
             "Inconsistency found in the definitions while executing"
@@ -684,32 +671,14 @@ def peep_hole_call_function_ex_to_call_function_kw(func_ir):
                 # if the list -> tuple conversion failed and if so
                 # throw an error.
                 call = stmt.value
-                args = call.args
-                vararg = call.vararg
-                if args:
-                    # If we have vararg then args is expected to
-                    # be an empty list.
-                    raise UnsupportedError(errmsg)
-                vararg_loc = i - 1
-                args_def = None
-                found = False
-                while vararg_loc >= 0 and not found:
-                    args_def = blk.body[vararg_loc]
-                    if (
-                        isinstance(args_def, ir.Assign)
-                        and args_def.target.name == vararg.name
-                    ):
-                        found = True
-                    else:
-                        vararg_loc -= 1
-                if not found:
-                    # If we couldn't find where the args are created
-                    # then we can't handle this format.
-                    raise UnsupportedError(errmsg)
-                else:
+                vararg_name = call.vararg.name
+                if (
+                    vararg_name in func_ir._definitions
+                    and len(func_ir._definitions[vararg_name]) == 1
+                ):
                     # If this value is still a list to tuple raise the
                     # exception.
-                    expr = blk.body[vararg_loc].value
+                    expr = func_ir._definitions[vararg_name][0]
                     if isinstance(expr, ir.Expr) and expr.op == "list_to_tuple":
                         raise UnsupportedError(errmsg)
 
@@ -897,8 +866,8 @@ def append_and_fix(x):
                         new_hole.append(x)
                 # Finally write the result back into the original build list as
                 # everything refers to it.
-                new_hole.append(ir.Assign(acc, t2l_agn.target,
-                                          the_build_list.loc))
+                append_and_fix(ir.Assign(acc, t2l_agn.target,
+                                         the_build_list.loc))
                 if _DEBUG:
                     print("\nNEW HOLE:")
                     for x in new_hole:


Test patch: