# Buggy code
def _stencil_wrapper(self, result, sigret, return_type, typemap, calltypes, *args):
    # Overall approach:
    # 1) Construct a string containing a function definition for the stencil function
    #    that will execute the stencil kernel.  This function definition includes a
    #    unique stencil function name, the parameters to the stencil kernel, loop
    #    nests across the dimenions of the input array.  Those loop nests use the
    #    computed stencil kernel size so as not to try to compute elements where
    #    elements outside the bounds of the input array would be needed.
    # 2) The but of the loop nest in this new function is a special sentinel
    #    assignment.
    # 3) Get the IR of this new function.
    # 4) Split the block containing the sentinel assignment and remove the sentinel
    #    assignment.  Insert the stencil kernel IR into the stencil function IR
    #    after label and variable renaming of the stencil kernel IR to prevent
    #    conflicts with the stencil function IR.
    # 5) Compile the combined stencil function IR + stencil kernel IR into existence.

    # Copy the kernel so that our changes for this callsite
    # won't effect other callsites.
    (kernel_copy, copy_calltypes) = self.copy_ir_with_calltypes(
        self.kernel_ir, calltypes
    )
    # The stencil kernel body becomes the body of a loop, for which args aren't needed.
    ir_utils.remove_args(kernel_copy.blocks)
    first_arg = kernel_copy.arg_names[0]

    in_cps, out_cps = ir_utils.copy_propagate(kernel_copy.blocks, typemap)
    name_var_table = ir_utils.get_name_var_table(kernel_copy.blocks)
    ir_utils.apply_copy_propagate(
        kernel_copy.blocks, in_cps, name_var_table, typemap, copy_calltypes
    )

    if "out" in name_var_table:
        raise ValueError("Cannot use the reserved word 'out' in stencil kernels.")

    sentinel_name = ir_utils.get_unused_var_name("__sentinel__", name_var_table)
    if config.DEBUG_ARRAY_OPT >= 1:
        print("name_var_table", name_var_table, sentinel_name)

    the_array = args[0]

    if config.DEBUG_ARRAY_OPT >= 1:
        print(
            "_stencil_wrapper",
            return_type,
            return_type.dtype,
            type(return_type.dtype),
            args,
        )
        ir_utils.dump_blocks(kernel_copy.blocks)

    # We generate a Numba function to execute this stencil and here
    # create the unique name of this function.
    stencil_func_name = "__numba_stencil_%s_%s" % (
        hex(id(the_array)).replace("-", "_"),
        self.id,
    )

    # We will put a loop nest in the generated function for each
    # dimension in the input array.  Here we create the name for
    # the index variable for each dimension.  index0, index1, ...
    index_vars = []
    for i in range(the_array.ndim):
        index_var_name = ir_utils.get_unused_var_name("index" + str(i), name_var_table)
        index_vars += [index_var_name]

    # Create extra signature for out and neighborhood.
    out_name = ir_utils.get_unused_var_name("out", name_var_table)
    neighborhood_name = ir_utils.get_unused_var_name("neighborhood", name_var_table)
    sig_extra = ""
    if result is not None:
        sig_extra += ", {}=None".format(out_name)
    if "neighborhood" in dict(self.kws):
        sig_extra += ", {}=None".format(neighborhood_name)

    # Get a list of the standard indexed array names.
    standard_indexed = self.options.get("standard_indexing", [])

    if first_arg in standard_indexed:
        raise ValueError(
            "The first argument to a stencil kernel must "
            "use relative indexing, not standard indexing."
        )

    if len(set(standard_indexed) - set(kernel_copy.arg_names)) != 0:
        raise ValueError(
            "Standard indexing requested for an array name "
            "not present in the stencil kernel definition."
        )

    # Add index variables to getitems in the IR to transition the accesses
    # in the kernel from relative to regular Python indexing.  Returns the
    # computed size of the stencil kernel and a list of the relatively indexed
    # arrays.
    kernel_size, relatively_indexed = self.add_indices_to_kernel(
        kernel_copy,
        index_vars,
        the_array.ndim,
        self.neighborhood,
        standard_indexed,
        typemap,
        copy_calltypes,
    )
    if self.neighborhood is None:
        self.neighborhood = kernel_size

    if config.DEBUG_ARRAY_OPT >= 1:
        print("After add_indices_to_kernel")
        ir_utils.dump_blocks(kernel_copy.blocks)

    # The return in the stencil kernel becomes a setitem for that
    # particular point in the iteration space.
    ret_blocks = self.replace_return_with_setitem(
        kernel_copy.blocks, index_vars, out_name
    )

    if config.DEBUG_ARRAY_OPT >= 1:
        print("After replace_return_with_setitem", ret_blocks)
        ir_utils.dump_blocks(kernel_copy.blocks)

    # Start to form the new function to execute the stencil kernel.
    func_text = "def {}({}{}):\n".format(
        stencil_func_name, ",".join(kernel_copy.arg_names), sig_extra
    )

    # Get loop ranges for each dimension, which could be either int
    # or variable. In the latter case we'll use the extra neighborhood
    # argument to the function.
    ranges = []
    for i in range(the_array.ndim):
        if isinstance(kernel_size[i][0], int):
            lo = kernel_size[i][0]
            hi = kernel_size[i][1]
        else:
            lo = "{}[{}][0]".format(neighborhood_name, i)
            hi = "{}[{}][1]".format(neighborhood_name, i)
        ranges.append((lo, hi))

    # If there are more than one relatively indexed arrays, add a call to
    # a function that will raise an error if any of the relatively indexed
    # arrays are of different size than the first input array.
    if len(relatively_indexed) > 1:
        func_text += "    raise_if_incompatible_array_sizes(" + first_arg
        for other_array in relatively_indexed:
            if other_array != first_arg:
                func_text += "," + other_array
        func_text += ")\n"

    # Get the shape of the first input array.
    shape_name = ir_utils.get_unused_var_name("full_shape", name_var_table)
    func_text += "    {} = {}.shape\n".format(shape_name, first_arg)

    # If we have to allocate the output array (the out argument was not used)
    # then us numpy.full if the user specified a cval stencil decorator option
    # or np.zeros if they didn't to allocate the array.
    if result is None:
        return_type_name = numpy_support.as_dtype(return_type.dtype).type.__name__
        if "cval" in self.options:
            cval = self.options["cval"]
            if return_type.dtype != typing.typeof.typeof(cval):
                raise ValueError("cval type does not match stencil return type.")
            out_init = "{} = np.full({}, {}, dtype=np.{})\n".format(
                out_name, shape_name, cval, return_type_name
            )
        else:
            out_init = "{} = np.zeros({}, dtype=np.{})\n".format(
                out_name, shape_name, return_type_name
            )
        func_text += "    " + out_init
    else:  # result is present, if cval is set then use it
        if "cval" in self.options:
            cval = self.options["cval"]
            cval_ty = typing.typeof.typeof(cval)
            if not self._typingctx.can_convert(cval_ty, return_type.dtype):
                msg = "cval type does not match stencil return type."
                raise ValueError(msg)
            out_init = "{}[:] = {}\n".format(out_name, cval)
            func_text += "    " + out_init

    offset = 1
    # Add the loop nests to the new function.
    for i in range(the_array.ndim):
        for j in range(offset):
            func_text += "    "
        # ranges[i][0] is the minimum index used in the i'th dimension
        # but minimum's greater than 0 don't preclude any entry in the array.
        # So, take the minimum of 0 and the minimum index found in the kernel
        # and this will be a negative number (potentially -0).  Then, we do
        # unary - on that to get the positive offset in this dimension whose
        # use is precluded.
        # ranges[i][1] is the maximum of 0 and the observed maximum index
        # in this dimension because negative maximums would not cause us to
        # preclude any entry in the array from being used.
        func_text += ("for {} in range(-min(0,{}),{}[{}]-max(0,{})):\n").format(
            index_vars[i], ranges[i][0], shape_name, i, ranges[i][1]
        )
        offset += 1

    for j in range(offset):
        func_text += "    "
    # Put a sentinel in the code so we can locate it in the IR.  We will
    # remove this sentinel assignment and replace it with the IR for the
    # stencil kernel body.
    func_text += "{} = 0\n".format(sentinel_name)
    func_text += "    return {}\n".format(out_name)

    if config.DEBUG_ARRAY_OPT >= 1:
        print("new stencil func text")
        print(func_text)

    # Force the new stencil function into existence.
    exec_(func_text) in globals(), locals()
    stencil_func = eval(stencil_func_name)
    if sigret is not None:
        pysig = utils.pysignature(stencil_func)
        sigret.pysig = pysig
    # Get the IR for the newly created stencil function.
    stencil_ir = compiler.run_frontend(stencil_func)
    ir_utils.remove_dels(stencil_ir.blocks)

    # rename all variables in stencil_ir afresh
    var_table = ir_utils.get_name_var_table(stencil_ir.blocks)
    new_var_dict = {}
    reserved_names = (
        [sentinel_name, out_name, neighborhood_name, shape_name]
        + kernel_copy.arg_names
        + index_vars
    )
    for name, var in var_table.items():
        if not name in reserved_names:
            new_var_dict[name] = ir_utils.mk_unique_var(name)
    ir_utils.replace_var_names(stencil_ir.blocks, new_var_dict)

    stencil_stub_last_label = max(stencil_ir.blocks.keys()) + 1

    # Shift lables in the kernel copy so they are guaranteed unique
    # and don't conflict with any labels in the stencil_ir.
    kernel_copy.blocks = ir_utils.add_offset_to_labels(
        kernel_copy.blocks, stencil_stub_last_label
    )
    new_label = max(kernel_copy.blocks.keys()) + 1
    # Adjust ret_blocks to account for addition of the offset.
    ret_blocks = [x + stencil_stub_last_label for x in ret_blocks]

    if config.DEBUG_ARRAY_OPT >= 1:
        print("ret_blocks w/ offsets", ret_blocks, stencil_stub_last_label)
        print("before replace sentinel stencil_ir")
        ir_utils.dump_blocks(stencil_ir.blocks)
        print("before replace sentinel kernel_copy")
        ir_utils.dump_blocks(kernel_copy.blocks)

    # Search all the block in the stencil outline for the sentinel.
    for label, block in stencil_ir.blocks.items():
        for i, inst in enumerate(block.body):
            if isinstance(inst, ir.Assign) and inst.target.name == sentinel_name:
                # We found the sentinel assignment.
                loc = inst.loc
                scope = block.scope
                # split block across __sentinel__
                # A new block is allocated for the statements prior to the
                # sentinel but the new block maintains the current block
                # label.
                prev_block = ir.Block(scope, loc)
                prev_block.body = block.body[:i]
                # The current block is used for statements after sentinel.
                block.body = block.body[i + 1 :]
                # But the current block gets a new label.
                body_first_label = min(kernel_copy.blocks.keys())

                # The previous block jumps to the minimum labelled block of
                # the parfor body.
                prev_block.append(ir.Jump(body_first_label, loc))
                # Add all the parfor loop body blocks to the gufunc
                # function's IR.
                for l, b in kernel_copy.blocks.items():
                    stencil_ir.blocks[l] = b

                stencil_ir.blocks[new_label] = block
                stencil_ir.blocks[label] = prev_block
                # Add a jump from all the blocks that previously contained
                # a return in the stencil kernel to the block
                # containing statements after the sentinel.
                for ret_block in ret_blocks:
                    stencil_ir.blocks[ret_block].append(ir.Jump(new_label, loc))
                break
        else:
            continue
        break

    stencil_ir.blocks = ir_utils.rename_labels(stencil_ir.blocks)
    ir_utils.remove_dels(stencil_ir.blocks)

    assert isinstance(the_array, types.Type)
    array_types = args

    new_stencil_param_types = list(array_types)

    if config.DEBUG_ARRAY_OPT >= 1:
        print("new_stencil_param_types", new_stencil_param_types)
        ir_utils.dump_blocks(stencil_ir.blocks)

    # Compile the combined stencil function with the replaced loop
    # body in it.
    new_func = compiler.compile_ir(
        self._typingctx,
        self._targetctx,
        stencil_ir,
        new_stencil_param_types,
        None,
        compiler.DEFAULT_FLAGS,
        {},
    )
    return new_func


# 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 _stencil_wrapper(self, result, sigret, return_type, typemap, calltypes, *args):
    # Overall approach:
    # 1) Construct a string containing a function definition for the stencil function
    #    that will execute the stencil kernel.  This function definition includes a
    #    unique stencil function name, the parameters to the stencil kernel, loop
    #    nests across the dimensions of the input array.  Those loop nests use the
    #    computed stencil kernel size so as not to try to compute elements where
    #    elements outside the bounds of the input array would be needed.
    # 2) The but of the loop nest in this new function is a special sentinel
    #    assignment.
    # 3) Get the IR of this new function.
    # 4) Split the block containing the sentinel assignment and remove the sentinel
    #    assignment.  Insert the stencil kernel IR into the stencil function IR
    #    after label and variable renaming of the stencil kernel IR to prevent
    #    conflicts with the stencil function IR.
    # 5) Compile the combined stencil function IR + stencil kernel IR into existence.

    # Copy the kernel so that our changes for this callsite
    # won't effect other callsites.
    (kernel_copy, copy_calltypes) = self.copy_ir_with_calltypes(
        self.kernel_ir, calltypes
    )
    # The stencil kernel body becomes the body of a loop, for which args aren't needed.
    ir_utils.remove_args(kernel_copy.blocks)
    first_arg = kernel_copy.arg_names[0]

    in_cps, out_cps = ir_utils.copy_propagate(kernel_copy.blocks, typemap)
    name_var_table = ir_utils.get_name_var_table(kernel_copy.blocks)
    ir_utils.apply_copy_propagate(
        kernel_copy.blocks, in_cps, name_var_table, typemap, copy_calltypes
    )

    if "out" in name_var_table:
        raise ValueError("Cannot use the reserved word 'out' in stencil kernels.")

    sentinel_name = ir_utils.get_unused_var_name("__sentinel__", name_var_table)
    if config.DEBUG_ARRAY_OPT >= 1:
        print("name_var_table", name_var_table, sentinel_name)

    the_array = args[0]

    if config.DEBUG_ARRAY_OPT >= 1:
        print(
            "_stencil_wrapper",
            return_type,
            return_type.dtype,
            type(return_type.dtype),
            args,
        )
        ir_utils.dump_blocks(kernel_copy.blocks)

    # We generate a Numba function to execute this stencil and here
    # create the unique name of this function.
    stencil_func_name = "__numba_stencil_%s_%s" % (
        hex(id(the_array)).replace("-", "_"),
        self.id,
    )

    # We will put a loop nest in the generated function for each
    # dimension in the input array.  Here we create the name for
    # the index variable for each dimension.  index0, index1, ...
    index_vars = []
    for i in range(the_array.ndim):
        index_var_name = ir_utils.get_unused_var_name("index" + str(i), name_var_table)
        index_vars += [index_var_name]

    # Create extra signature for out and neighborhood.
    out_name = ir_utils.get_unused_var_name("out", name_var_table)
    neighborhood_name = ir_utils.get_unused_var_name("neighborhood", name_var_table)
    sig_extra = ""
    if result is not None:
        sig_extra += ", {}=None".format(out_name)
    if "neighborhood" in dict(self.kws):
        sig_extra += ", {}=None".format(neighborhood_name)

    # Get a list of the standard indexed array names.
    standard_indexed = self.options.get("standard_indexing", [])

    if first_arg in standard_indexed:
        raise ValueError(
            "The first argument to a stencil kernel must "
            "use relative indexing, not standard indexing."
        )

    if len(set(standard_indexed) - set(kernel_copy.arg_names)) != 0:
        raise ValueError(
            "Standard indexing requested for an array name "
            "not present in the stencil kernel definition."
        )

    # Add index variables to getitems in the IR to transition the accesses
    # in the kernel from relative to regular Python indexing.  Returns the
    # computed size of the stencil kernel and a list of the relatively indexed
    # arrays.
    kernel_size, relatively_indexed = self.add_indices_to_kernel(
        kernel_copy,
        index_vars,
        the_array.ndim,
        self.neighborhood,
        standard_indexed,
        typemap,
        copy_calltypes,
    )
    if self.neighborhood is None:
        self.neighborhood = kernel_size

    if config.DEBUG_ARRAY_OPT >= 1:
        print("After add_indices_to_kernel")
        ir_utils.dump_blocks(kernel_copy.blocks)

    # The return in the stencil kernel becomes a setitem for that
    # particular point in the iteration space.
    ret_blocks = self.replace_return_with_setitem(
        kernel_copy.blocks, index_vars, out_name
    )

    if config.DEBUG_ARRAY_OPT >= 1:
        print("After replace_return_with_setitem", ret_blocks)
        ir_utils.dump_blocks(kernel_copy.blocks)

    # Start to form the new function to execute the stencil kernel.
    func_text = "def {}({}{}):\n".format(
        stencil_func_name, ",".join(kernel_copy.arg_names), sig_extra
    )

    # Get loop ranges for each dimension, which could be either int
    # or variable. In the latter case we'll use the extra neighborhood
    # argument to the function.
    ranges = []
    for i in range(the_array.ndim):
        if isinstance(kernel_size[i][0], int):
            lo = kernel_size[i][0]
            hi = kernel_size[i][1]
        else:
            lo = "{}[{}][0]".format(neighborhood_name, i)
            hi = "{}[{}][1]".format(neighborhood_name, i)
        ranges.append((lo, hi))

    # If there are more than one relatively indexed arrays, add a call to
    # a function that will raise an error if any of the relatively indexed
    # arrays are of different size than the first input array.
    if len(relatively_indexed) > 1:
        func_text += "    raise_if_incompatible_array_sizes(" + first_arg
        for other_array in relatively_indexed:
            if other_array != first_arg:
                func_text += "," + other_array
        func_text += ")\n"

    # Get the shape of the first input array.
    shape_name = ir_utils.get_unused_var_name("full_shape", name_var_table)
    func_text += "    {} = {}.shape\n".format(shape_name, first_arg)

    # If we have to allocate the output array (the out argument was not used)
    # then us numpy.full if the user specified a cval stencil decorator option
    # or np.zeros if they didn't to allocate the array.
    if result is None:
        return_type_name = numpy_support.as_dtype(return_type.dtype).type.__name__
        if "cval" in self.options:
            cval = self.options["cval"]
            if return_type.dtype != typing.typeof.typeof(cval):
                raise ValueError("cval type does not match stencil return type.")
            out_init = "{} = np.full({}, {}, dtype=np.{})\n".format(
                out_name, shape_name, cval, return_type_name
            )
        else:
            out_init = "{} = np.zeros({}, dtype=np.{})\n".format(
                out_name, shape_name, return_type_name
            )
        func_text += "    " + out_init
    else:  # result is present, if cval is set then use it
        if "cval" in self.options:
            cval = self.options["cval"]
            cval_ty = typing.typeof.typeof(cval)
            if not self._typingctx.can_convert(cval_ty, return_type.dtype):
                msg = "cval type does not match stencil return type."
                raise ValueError(msg)
            out_init = "{}[:] = {}\n".format(out_name, cval)
            func_text += "    " + out_init

    offset = 1
    # Add the loop nests to the new function.
    for i in range(the_array.ndim):
        for j in range(offset):
            func_text += "    "
        # ranges[i][0] is the minimum index used in the i'th dimension
        # but minimum's greater than 0 don't preclude any entry in the array.
        # So, take the minimum of 0 and the minimum index found in the kernel
        # and this will be a negative number (potentially -0).  Then, we do
        # unary - on that to get the positive offset in this dimension whose
        # use is precluded.
        # ranges[i][1] is the maximum of 0 and the observed maximum index
        # in this dimension because negative maximums would not cause us to
        # preclude any entry in the array from being used.
        func_text += ("for {} in range(-min(0,{}),{}[{}]-max(0,{})):\n").format(
            index_vars[i], ranges[i][0], shape_name, i, ranges[i][1]
        )
        offset += 1

    for j in range(offset):
        func_text += "    "
    # Put a sentinel in the code so we can locate it in the IR.  We will
    # remove this sentinel assignment and replace it with the IR for the
    # stencil kernel body.
    func_text += "{} = 0\n".format(sentinel_name)
    func_text += "    return {}\n".format(out_name)

    if config.DEBUG_ARRAY_OPT >= 1:
        print("new stencil func text")
        print(func_text)

    # Force the new stencil function into existence.
    exec_(func_text) in globals(), locals()
    stencil_func = eval(stencil_func_name)
    if sigret is not None:
        pysig = utils.pysignature(stencil_func)
        sigret.pysig = pysig
    # Get the IR for the newly created stencil function.
    stencil_ir = compiler.run_frontend(stencil_func)
    ir_utils.remove_dels(stencil_ir.blocks)

    # rename all variables in stencil_ir afresh
    var_table = ir_utils.get_name_var_table(stencil_ir.blocks)
    new_var_dict = {}
    reserved_names = (
        [sentinel_name, out_name, neighborhood_name, shape_name]
        + kernel_copy.arg_names
        + index_vars
    )
    for name, var in var_table.items():
        if not name in reserved_names:
            new_var_dict[name] = ir_utils.mk_unique_var(name)
    ir_utils.replace_var_names(stencil_ir.blocks, new_var_dict)

    stencil_stub_last_label = max(stencil_ir.blocks.keys()) + 1

    # Shift lables in the kernel copy so they are guaranteed unique
    # and don't conflict with any labels in the stencil_ir.
    kernel_copy.blocks = ir_utils.add_offset_to_labels(
        kernel_copy.blocks, stencil_stub_last_label
    )
    new_label = max(kernel_copy.blocks.keys()) + 1
    # Adjust ret_blocks to account for addition of the offset.
    ret_blocks = [x + stencil_stub_last_label for x in ret_blocks]

    if config.DEBUG_ARRAY_OPT >= 1:
        print("ret_blocks w/ offsets", ret_blocks, stencil_stub_last_label)
        print("before replace sentinel stencil_ir")
        ir_utils.dump_blocks(stencil_ir.blocks)
        print("before replace sentinel kernel_copy")
        ir_utils.dump_blocks(kernel_copy.blocks)

    # Search all the block in the stencil outline for the sentinel.
    for label, block in stencil_ir.blocks.items():
        for i, inst in enumerate(block.body):
            if isinstance(inst, ir.Assign) and inst.target.name == sentinel_name:
                # We found the sentinel assignment.
                loc = inst.loc
                scope = block.scope
                # split block across __sentinel__
                # A new block is allocated for the statements prior to the
                # sentinel but the new block maintains the current block
                # label.
                prev_block = ir.Block(scope, loc)
                prev_block.body = block.body[:i]
                # The current block is used for statements after sentinel.
                block.body = block.body[i + 1 :]
                # But the current block gets a new label.
                body_first_label = min(kernel_copy.blocks.keys())

                # The previous block jumps to the minimum labelled block of
                # the parfor body.
                prev_block.append(ir.Jump(body_first_label, loc))
                # Add all the parfor loop body blocks to the gufunc
                # function's IR.
                for l, b in kernel_copy.blocks.items():
                    stencil_ir.blocks[l] = b

                stencil_ir.blocks[new_label] = block
                stencil_ir.blocks[label] = prev_block
                # Add a jump from all the blocks that previously contained
                # a return in the stencil kernel to the block
                # containing statements after the sentinel.
                for ret_block in ret_blocks:
                    stencil_ir.blocks[ret_block].append(ir.Jump(new_label, loc))
                break
        else:
            continue
        break

    stencil_ir.blocks = ir_utils.rename_labels(stencil_ir.blocks)
    ir_utils.remove_dels(stencil_ir.blocks)

    assert isinstance(the_array, types.Type)
    array_types = args

    new_stencil_param_types = list(array_types)

    if config.DEBUG_ARRAY_OPT >= 1:
        print("new_stencil_param_types", new_stencil_param_types)
        ir_utils.dump_blocks(stencil_ir.blocks)

    # Compile the combined stencil function with the replaced loop
    # body in it.
    new_func = compiler.compile_ir(
        self._typingctx,
        self._targetctx,
        stencil_ir,
        new_stencil_param_types,
        None,
        compiler.DEFAULT_FLAGS,
        {},
    )
    return new_func