# Buggy code
def _replace_parallel_functions(self, blocks):
    """
    Replace functions with their parallel implementation in
    replace_functions_map if available.
    The implementation code is inlined to enable more optimization.
    """
    swapped = self.swapped
    from numba.inline_closurecall import inline_closure_call

    work_list = list(blocks.items())
    while work_list:
        label, block = work_list.pop()
        for i, instr in enumerate(block.body):
            if isinstance(instr, ir.Assign):
                lhs = instr.target
                lhs_typ = self.typemap[lhs.name]
                expr = instr.value
                if isinstance(expr, ir.Expr) and expr.op == "call":
                    # Try and inline known calls with their parallel implementations
                    def replace_func():
                        func_def = get_definition(self.func_ir, expr.func)
                        callname = find_callname(self.func_ir, expr)
                        repl_func = replace_functions_map.get(callname, None)
                        require(repl_func != None)
                        typs = tuple(self.typemap[x.name] for x in expr.args)
                        try:
                            new_func = repl_func(lhs_typ, *typs)
                        except:
                            new_func = None
                        require(new_func != None)
                        g = copy.copy(self.func_ir.func_id.func.__globals__)
                        g["numba"] = numba
                        g["np"] = numpy
                        g["math"] = math
                        # if the function being inlined has a function
                        # checking the inputs, find it and add it to globals
                        check = replace_functions_checkers_map.get(callname, None)
                        if check is not None:
                            g[check.name] = check.func
                        # inline the parallel implementation
                        new_blocks, _ = inline_closure_call(
                            self.func_ir,
                            g,
                            block,
                            i,
                            new_func,
                            self.typingctx,
                            typs,
                            self.typemap,
                            self.calltypes,
                            work_list,
                        )
                        call_table = get_call_table(
                            new_blocks, topological_ordering=False
                        )

                        # find the prange in the new blocks and record it for use in diagnostics
                        for call in call_table:
                            for k, v in call.items():
                                if v[0] == "internal_prange":
                                    swapped[k] = [
                                        callname,
                                        repl_func.__name__,
                                        func_def,
                                        block.body[i].loc,
                                    ]
                                    break
                        return True

                    if guard(replace_func):
                        break
                elif (
                    isinstance(expr, ir.Expr)
                    and expr.op == "getattr"
                    and expr.attr == "dtype"
                ):
                    # Replace getattr call "A.dtype" with numpy.dtype(<actual type>).
                    # This helps remove superfluous dependencies from parfor.
                    typ = self.typemap[expr.value.name]
                    if isinstance(typ, types.npytypes.Array):
                        # Convert A.dtype to four statements.
                        # 1) Get numpy global.
                        # 2) Create var for known type of array as string
                        #    constant. e.g. 'float64'
                        # 3) Get dtype function from numpy module.
                        # 4) Create var for numpy.dtype(var from #2).

                        # Create var for numpy module.
                        dtype = typ.dtype
                        scope = block.scope
                        loc = instr.loc
                        g_np_var = ir.Var(scope, mk_unique_var("$np_g_var"), loc)
                        self.typemap[g_np_var.name] = types.misc.Module(numpy)
                        g_np = ir.Global("np", numpy, loc)
                        g_np_assign = ir.Assign(g_np, g_np_var, loc)

                        # Create var for the inferred type of the array
                        # e.g., 'float64'
                        dtype_str = str(dtype)
                        if dtype_str == "bool":
                            dtype_str = "bool_"
                        typ_var = ir.Var(scope, mk_unique_var("$np_typ_var"), loc)
                        self.typemap[typ_var.name] = types.StringLiteral(dtype_str)
                        typ_var_assign = ir.Assign(
                            ir.Const(dtype_str, loc), typ_var, loc
                        )

                        # Get the dtype function from the numpy module.
                        dtype_attr_var = ir.Var(
                            scope, mk_unique_var("$dtype_attr_var"), loc
                        )
                        temp = find_template(numpy.dtype)
                        tfunc = numba.types.Function(temp)
                        tfunc.get_call_type(
                            self.typingctx, (self.typemap[typ_var.name],), {}
                        )
                        self.typemap[dtype_attr_var.name] = types.functions.Function(
                            temp
                        )
                        dtype_attr_getattr = ir.Expr.getattr(g_np_var, "dtype", loc)
                        dtype_attr_assign = ir.Assign(
                            dtype_attr_getattr, dtype_attr_var, loc
                        )

                        # Call numpy.dtype on the statically coded type two steps above.
                        dtype_var = ir.Var(scope, mk_unique_var("$dtype_var"), loc)
                        self.typemap[dtype_var.name] = types.npytypes.DType(dtype)
                        dtype_getattr = ir.Expr.call(dtype_attr_var, [typ_var], (), loc)
                        dtype_assign = ir.Assign(dtype_getattr, dtype_var, loc)
                        self.calltypes[dtype_getattr] = signature(
                            self.typemap[dtype_var.name], self.typemap[typ_var.name]
                        )

                        # The original A.dtype rhs is replaced with result of this call.
                        instr.value = dtype_var
                        # Add statements to body of the code.
                        block.body.insert(0, dtype_assign)
                        block.body.insert(0, dtype_attr_assign)
                        block.body.insert(0, typ_var_assign)
                        block.body.insert(0, g_np_assign)
                        break


# Traceback (IndexError)
Traceback (most recent call last):
File "/home/ibutygin/miniconda3/envs/NUMBA_CUSTOM/lib/python3.7/site-packages/llvmlite/ir/instructions.py", line 619, in __init__
typ = typ.elements[i]
IndexError: tuple index out of range

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/home/ibutygin/src/numba/numba/numba/errors.py", line 717, in new_error_context
yield
File "/home/ibutygin/src/numba/numba/numba/lowering.py", line 260, in lower_block
self.lower_inst(inst)
File "/home/ibutygin/src/numba/numba/numba/lowering.py", line 303, in lower_inst
val = self.lower_assign(ty, inst)
File "/home/ibutygin/src/numba/numba/numba/lowering.py", line 467, in lower_assign
return self.lower_expr(ty, value)
File "/home/ibutygin/src/numba/numba/numba/lowering.py", line 932, in lower_expr
res = self.lower_call(resty, expr)
File "/home/ibutygin/src/numba/numba/numba/lowering.py", line 724, in lower_call
res = self._lower_call_normal(fnty, expr, signature)
File "/home/ibutygin/src/numba/numba/numba/lowering.py", line 903, in _lower_call_normal
res = impl(self.builder, argvals, self.loc)
File "/home/ibutygin/src/numba/numba/numba/targets/base.py", line 1132, in __call__
res = self._imp(self._context, builder, self._sig, args, loc=loc)
File "/home/ibutygin/src/numba/numba/numba/targets/base.py", line 1157, in wrapper
return fn(*args, **kwargs)
File "/home/ibutygin/src/numba/numba/numba/typing/templates.py", line 778, in method_impl
return call(builder, args)
File "/home/ibutygin/src/numba/numba/numba/targets/base.py", line 1132, in __call__
res = self._imp(self._context, builder, self._sig, args, loc=loc)
File "/home/ibutygin/src/numba/numba/numba/targets/base.py", line 1157, in wrapper
return fn(*args, **kwargs)
File "/home/ibutygin/src/numba/numba/numba/targets/imputils.py", line 193, in imp
builder, func, fndesc.restype, fndesc.argtypes, args)
File "/home/ibutygin/src/numba/numba/numba/targets/callconv.py", line 477, in call_function
args = list(arginfo.as_arguments(builder, args))
File "/home/ibutygin/src/numba/numba/numba/datamodel/packer.py", line 100, in as_arguments
for dm, val in zip(self._dm_args, values)
File "/home/ibutygin/src/numba/numba/numba/datamodel/packer.py", line 100, in <listcomp>
for dm, val in zip(self._dm_args, values)
File "/home/ibutygin/src/numba/numba/numba/datamodel/models.py", line 447, in as_argument
v = builder.extract_value(value, [i])
File "/home/ibutygin/miniconda3/envs/NUMBA_CUSTOM/lib/python3.7/site-packages/llvmlite/ir/builder.py", line 927, in extract_value
instr = instructions.ExtractValue(self.block, agg, idx, name=name)
File "/home/ibutygin/miniconda3/envs/NUMBA_CUSTOM/lib/python3.7/site-packages/llvmlite/ir/instructions.py", line 622, in __init__
% (list(indices), agg.type))
TypeError: Can't index at [0] in {}

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "test.py", line 73, in <module>
print(bar(Dummy()))
File "/home/ibutygin/src/numba/numba/numba/dispatcher.py", line 420, in _compile_for_args
raise e
File "/home/ibutygin/src/numba/numba/numba/dispatcher.py", line 353, in _compile_for_args
return self.compile(tuple(argtypes))
File "/home/ibutygin/src/numba/numba/numba/compiler_lock.py", line 32, in _acquire_compile_lock
return func(*args, **kwargs)
File "/home/ibutygin/src/numba/numba/numba/dispatcher.py", line 768, in compile
cres = self._compiler.compile(args, return_type)
File "/home/ibutygin/src/numba/numba/numba/dispatcher.py", line 77, in compile
status, retval = self._compile_cached(args, return_type)
File "/home/ibutygin/src/numba/numba/numba/dispatcher.py", line 91, in _compile_cached
retval = self._compile_core(args, return_type)
File "/home/ibutygin/src/numba/numba/numba/dispatcher.py", line 109, in _compile_core
pipeline_class=self.pipeline_class)
File "/home/ibutygin/src/numba/numba/numba/compiler.py", line 543, in compile_extra
return pipeline.compile_extra(func)
File "/home/ibutygin/src/numba/numba/numba/compiler.py", line 328, in compile_extra
return self._compile_bytecode()
File "/home/ibutygin/src/numba/numba/numba/compiler.py", line 389, in _compile_bytecode
return self._compile_core()
File "/home/ibutygin/src/numba/numba/numba/compiler.py", line 368, in _compile_core
raise e
File "/home/ibutygin/src/numba/numba/numba/compiler.py", line 359, in _compile_core
pm.run(self.state)
File "/home/ibutygin/src/numba/numba/numba/compiler_machinery.py", line 331, in run
raise patched_exception
File "/home/ibutygin/src/numba/numba/numba/compiler_machinery.py", line 322, in run
self._runPass(idx, pass_inst, state)
File "/home/ibutygin/src/numba/numba/numba/compiler_lock.py", line 32, in _acquire_compile_lock
return func(*args, **kwargs)
File "/home/ibutygin/src/numba/numba/numba/compiler_machinery.py", line 281, in _runPass
mutated |= check(pss.run_pass, internal_state)
File "/home/ibutygin/src/numba/numba/numba/compiler_machinery.py", line 268, in check
mangled = func(compiler_state)
File "/home/ibutygin/src/numba/numba/numba/typed_passes.py", line 391, in run_pass
NativeLowering().run_pass(state)
File "/home/ibutygin/src/numba/numba/numba/typed_passes.py", line 333, in run_pass
lower.lower()
File "/home/ibutygin/src/numba/numba/numba/lowering.py", line 179, in lower
self.lower_normal_function(self.fndesc)
File "/home/ibutygin/src/numba/numba/numba/lowering.py", line 220, in lower_normal_function
entry_block_tail = self.lower_function_body()
File "/home/ibutygin/src/numba/numba/numba/lowering.py", line 245, in lower_function_body
self.lower_block(block)
File "/home/ibutygin/src/numba/numba/numba/lowering.py", line 260, in lower_block
self.lower_inst(inst)
File "/home/ibutygin/miniconda3/envs/NUMBA_CUSTOM/lib/python3.7/contextlib.py", line 130, in __exit__
self.gen.throw(type, value, traceback)
File "/home/ibutygin/src/numba/numba/numba/errors.py", line 725, in new_error_context
six.reraise(type(newerr), newerr, tb)
File "/home/ibutygin/src/numba/numba/numba/six.py", line 669, in reraise
raise value
numba.errors.LoweringError: Failed in nopython mode pipeline (step: nopython mode backend)
Can't index at [0] in {}

File "test.py", line 71:
def bar(obj):
return obj.inline_method(123,456)
^

[1] During: lowering "$10call_method.4 = call $4load_method.1($const6.2, $const8.3, func=$4load_method.1, args=[Var($const6.2, test.py:71), Var($const8.3, test.py:71)], kws=(), vararg=None)" at test.py (71)

# Fixed code
def _replace_parallel_functions(self, blocks):
    """
    Replace functions with their parallel implementation in
    replace_functions_map if available.
    The implementation code is inlined to enable more optimization.
    """
    swapped = self.swapped
    from numba.inline_closurecall import inline_closure_call

    work_list = list(blocks.items())
    while work_list:
        label, block = work_list.pop()
        for i, instr in enumerate(block.body):
            if isinstance(instr, ir.Assign):
                lhs = instr.target
                lhs_typ = self.typemap[lhs.name]
                expr = instr.value
                if isinstance(expr, ir.Expr) and expr.op == "call":
                    # Try and inline known calls with their parallel implementations
                    def replace_func():
                        func_def = get_definition(self.func_ir, expr.func)
                        callname = find_callname(self.func_ir, expr)
                        repl_func = replace_functions_map.get(callname, None)
                        if (
                            repl_func is None
                            and len(callname) == 2
                            and isinstance(callname[1], ir.Var)
                            and isinstance(
                                self.typemap[callname[1].name], types.npytypes.Array
                            )
                        ):
                            repl_func = replace_functions_ndarray.get(callname[0], None)
                            if repl_func is not None:
                                # Add the array that the method is on to the arg list.
                                expr.args.insert(0, callname[1])

                        require(repl_func != None)
                        typs = tuple(self.typemap[x.name] for x in expr.args)
                        try:
                            new_func = repl_func(lhs_typ, *typs)
                        except:
                            new_func = None
                        require(new_func != None)
                        g = copy.copy(self.func_ir.func_id.func.__globals__)
                        g["numba"] = numba
                        g["np"] = numpy
                        g["math"] = math
                        # if the function being inlined has a function
                        # checking the inputs, find it and add it to globals
                        check = replace_functions_checkers_map.get(callname, None)
                        if check is not None:
                            g[check.name] = check.func
                        # inline the parallel implementation
                        new_blocks, _ = inline_closure_call(
                            self.func_ir,
                            g,
                            block,
                            i,
                            new_func,
                            self.typingctx,
                            typs,
                            self.typemap,
                            self.calltypes,
                            work_list,
                        )
                        call_table = get_call_table(
                            new_blocks, topological_ordering=False
                        )

                        # find the prange in the new blocks and record it for use in diagnostics
                        for call in call_table:
                            for k, v in call.items():
                                if v[0] == "internal_prange":
                                    swapped[k] = [
                                        callname,
                                        repl_func.__name__,
                                        func_def,
                                        block.body[i].loc,
                                    ]
                                    break
                        return True

                    if guard(replace_func):
                        break
                elif (
                    isinstance(expr, ir.Expr)
                    and expr.op == "getattr"
                    and expr.attr == "dtype"
                ):
                    # Replace getattr call "A.dtype" with numpy.dtype(<actual type>).
                    # This helps remove superfluous dependencies from parfor.
                    typ = self.typemap[expr.value.name]
                    if isinstance(typ, types.npytypes.Array):
                        # Convert A.dtype to four statements.
                        # 1) Get numpy global.
                        # 2) Create var for known type of array as string
                        #    constant. e.g. 'float64'
                        # 3) Get dtype function from numpy module.
                        # 4) Create var for numpy.dtype(var from #2).

                        # Create var for numpy module.
                        dtype = typ.dtype
                        scope = block.scope
                        loc = instr.loc
                        g_np_var = ir.Var(scope, mk_unique_var("$np_g_var"), loc)
                        self.typemap[g_np_var.name] = types.misc.Module(numpy)
                        g_np = ir.Global("np", numpy, loc)
                        g_np_assign = ir.Assign(g_np, g_np_var, loc)

                        # Create var for the inferred type of the array
                        # e.g., 'float64'
                        dtype_str = str(dtype)
                        if dtype_str == "bool":
                            dtype_str = "bool_"
                        typ_var = ir.Var(scope, mk_unique_var("$np_typ_var"), loc)
                        self.typemap[typ_var.name] = types.StringLiteral(dtype_str)
                        typ_var_assign = ir.Assign(
                            ir.Const(dtype_str, loc), typ_var, loc
                        )

                        # Get the dtype function from the numpy module.
                        dtype_attr_var = ir.Var(
                            scope, mk_unique_var("$dtype_attr_var"), loc
                        )
                        temp = find_template(numpy.dtype)
                        tfunc = numba.types.Function(temp)
                        tfunc.get_call_type(
                            self.typingctx, (self.typemap[typ_var.name],), {}
                        )
                        self.typemap[dtype_attr_var.name] = types.functions.Function(
                            temp
                        )
                        dtype_attr_getattr = ir.Expr.getattr(g_np_var, "dtype", loc)
                        dtype_attr_assign = ir.Assign(
                            dtype_attr_getattr, dtype_attr_var, loc
                        )

                        # Call numpy.dtype on the statically coded type two steps above.
                        dtype_var = ir.Var(scope, mk_unique_var("$dtype_var"), loc)
                        self.typemap[dtype_var.name] = types.npytypes.DType(dtype)
                        dtype_getattr = ir.Expr.call(dtype_attr_var, [typ_var], (), loc)
                        dtype_assign = ir.Assign(dtype_getattr, dtype_var, loc)
                        self.calltypes[dtype_getattr] = signature(
                            self.typemap[dtype_var.name], self.typemap[typ_var.name]
                        )

                        # The original A.dtype rhs is replaced with result of this call.
                        instr.value = dtype_var
                        # Add statements to body of the code.
                        block.body.insert(0, dtype_assign)
                        block.body.insert(0, dtype_attr_assign)
                        block.body.insert(0, typ_var_assign)
                        block.body.insert(0, g_np_assign)
                        break