# Buggy code
def unify(self):
    """
    Run the final unification pass over all inferred types, and
    catch imprecise types.
    """
    typdict = utils.UniqueDict()

    def find_offender(name, exhaustive=False):
        # finds the offending variable definition by name
        # if exhaustive is set it will try and trace through temporary
        # variables to find a concrete offending definition.
        offender = None
        for block in self.func_ir.blocks.values():
            offender = block.find_variable_assignment(name)
            if offender is not None:
                if not exhaustive:
                    break
                try:  # simple assignment
                    hasattr(offender.value, "name")
                    offender_value = offender.value.name
                except (AttributeError, KeyError):
                    break
                orig_offender = offender
                if offender_value.startswith("$"):
                    offender = find_offender(offender_value, exhaustive=exhaustive)
                    if offender is None:
                        offender = orig_offender
                break
        return offender

    def diagnose_imprecision(offender):
        # helper for diagnosing imprecise types

        list_msg = """\n
For Numba to be able to compile a list, the list must have a known and
precise type that can be inferred from the other variables. Whilst sometimes
the type of empty lists can be inferred, this is not always the case, see this
documentation for help:

http://numba.pydata.org/numba-doc/latest/user/troubleshoot.html#my-code-has-an-untyped-list-problem
"""
        if offender is not None:
            # This block deals with imprecise lists
            if hasattr(offender, "value"):
                if hasattr(offender.value, "op"):
                    # might be `foo = []`
                    if offender.value.op == "build_list":
                        return list_msg
                    # or might be `foo = list()`
                    elif offender.value.op == "call":
                        try:  # assignment involving a call
                            call_name = offender.value.func.name
                            # find the offender based on the call name
                            offender = find_offender(call_name)
                            if isinstance(offender.value, ir.Global):
                                if offender.value.name == "list":
                                    return list_msg
                        except (AttributeError, KeyError):
                            pass
        return ""  # no help possible

    def check_var(name):
        tv = self.typevars[name]
        if not tv.defined:
            offender = find_offender(name)
            val = getattr(offender, "value", "unknown operation")
            loc = getattr(offender, "loc", "unknown location")
            msg = "Undefined variable '%s', operation: %s, location: %s"
            raise TypingError(msg % (var, val, loc), loc)
        tp = tv.getone()
        if not tp.is_precise():
            offender = find_offender(name, exhaustive=True)
            msg = (
                "Cannot infer the type of variable '%s'%s, have imprecise type: %s. %s"
            )
            istmp = " (temporary variable)" if var.startswith("$") else ""
            loc = getattr(offender, "loc", "unknown location")
            # is this an untyped list? try and provide help
            extra_msg = diagnose_imprecision(offender)
            raise TypingError(msg % (var, istmp, tp, extra_msg), loc)
        else:  # type is precise, hold it
            typdict[var] = tp

    # For better error display, check first user-visible vars, then
    # temporaries
    temps = set(k for k in self.typevars if not k[0].isalpha())
    others = set(self.typevars) - temps
    for var in sorted(others):
        check_var(var)
    for var in sorted(temps):
        check_var(var)

    retty = self.get_return_type(typdict)
    fntys = self.get_function_types(typdict)
    if self.generator_info:
        retty = self.get_generator_type(typdict, retty)

    self.debug.unify_finished(typdict, retty, fntys)

    return typdict, retty, fntys


# Traceback (AttributeError)
Traceback (most recent call last):
File "examples/hello.py", line 25, in <module>
main()
File "examples/hello.py", line 19, in main
print(hpat(2))
File "numba/numba/dispatcher.py", line 367, in _compile_for_args
raise e
File "numba/numba/dispatcher.py", line 324, in _compile_for_args
return self.compile(tuple(argtypes))
File "numba/numba/compiler_lock.py", line 32, in _acquire_compile_lock
return func(*args, **kwargs)
File "numba/numba/dispatcher.py", line 655, in compile
cres = self._compiler.compile(args, return_type)
File "numba/numba/dispatcher.py", line 82, in compile
pipeline_class=self.pipeline_class)
File "numba/numba/compiler.py", line 905, in compile_extra
return pipeline.compile_extra(func)
File "numba/numba/compiler.py", line 367, in compile_extra
return self._compile_bytecode()
File "numba/numba/compiler.py", line 836, in _compile_bytecode
return self._compile_core()
File "numba/numba/compiler.py", line 823, in _compile_core
res = pm.run(self.status)
File "numba/numba/compiler_lock.py", line 32, in _acquire_compile_lock
return func(*args, **kwargs)
File "numba/numba/compiler.py", line 253, in run
raise patched_exception
File "numba/numba/compiler.py", line 244, in run
stage()
File "numba/numba/compiler.py", line 478, in stage_nopython_frontend
self.locals)
File "numba/numba/compiler.py", line 1009, in type_inference_stage
typemap, restype, calltypes = infer.unify()
File "numba/numba/typeinfer.py", line 924, in unify
check_var(var)
File "numba/numba/typeinfer.py", line 910, in check_var
raise TypingError(msg % (var, val, loc), loc)
File "numba/numba/errors.py", line 406, in __init__
highlight("%s\n%s\n" % (msg, loc.strformat())))
AttributeError: Failed in hpat mode pipeline (step: nopython frontend)
'str' object has no attribute 'strformat'

# Fixed code
def unify(self):
    """
    Run the final unification pass over all inferred types, and
    catch imprecise types.
    """
    typdict = utils.UniqueDict()

    def find_offender(name, exhaustive=False):
        # finds the offending variable definition by name
        # if exhaustive is set it will try and trace through temporary
        # variables to find a concrete offending definition.
        offender = None
        for block in self.func_ir.blocks.values():
            offender = block.find_variable_assignment(name)
            if offender is not None:
                if not exhaustive:
                    break
                try:  # simple assignment
                    hasattr(offender.value, "name")
                    offender_value = offender.value.name
                except (AttributeError, KeyError):
                    break
                orig_offender = offender
                if offender_value.startswith("$"):
                    offender = find_offender(offender_value, exhaustive=exhaustive)
                    if offender is None:
                        offender = orig_offender
                break
        return offender

    def diagnose_imprecision(offender):
        # helper for diagnosing imprecise types

        list_msg = """\n
For Numba to be able to compile a list, the list must have a known and
precise type that can be inferred from the other variables. Whilst sometimes
the type of empty lists can be inferred, this is not always the case, see this
documentation for help:

http://numba.pydata.org/numba-doc/latest/user/troubleshoot.html#my-code-has-an-untyped-list-problem
"""
        if offender is not None:
            # This block deals with imprecise lists
            if hasattr(offender, "value"):
                if hasattr(offender.value, "op"):
                    # might be `foo = []`
                    if offender.value.op == "build_list":
                        return list_msg
                    # or might be `foo = list()`
                    elif offender.value.op == "call":
                        try:  # assignment involving a call
                            call_name = offender.value.func.name
                            # find the offender based on the call name
                            offender = find_offender(call_name)
                            if isinstance(offender.value, ir.Global):
                                if offender.value.name == "list":
                                    return list_msg
                        except (AttributeError, KeyError):
                            pass
        return ""  # no help possible

    def check_var(name):
        tv = self.typevars[name]
        if not tv.defined:
            offender = find_offender(name)
            val = getattr(offender, "value", "unknown operation")
            loc = getattr(offender, "loc", ir.unknown_loc)
            msg = "Undefined variable '%s', operation: %s, location: %s"
            raise TypingError(msg % (var, val, loc), loc)
        tp = tv.getone()
        if not tp.is_precise():
            offender = find_offender(name, exhaustive=True)
            msg = (
                "Cannot infer the type of variable '%s'%s, have imprecise type: %s. %s"
            )
            istmp = " (temporary variable)" if var.startswith("$") else ""
            loc = getattr(offender, "loc", ir.unknown_loc)
            # is this an untyped list? try and provide help
            extra_msg = diagnose_imprecision(offender)
            raise TypingError(msg % (var, istmp, tp, extra_msg), loc)
        else:  # type is precise, hold it
            typdict[var] = tp

    # For better error display, check first user-visible vars, then
    # temporaries
    temps = set(k for k in self.typevars if not k[0].isalpha())
    others = set(self.typevars) - temps
    for var in sorted(others):
        check_var(var)
    for var in sorted(temps):
        check_var(var)

    retty = self.get_return_type(typdict)
    fntys = self.get_function_types(typdict)
    if self.generator_info:
        retty = self.get_generator_type(typdict, retty)

    self.debug.unify_finished(typdict, retty, fntys)

    return typdict, retty, fntys