Repository: numba/numba

Issue:
Typing errors in device functions aren't properly reported
- [X] I am using the latest released version of Numba (most recent is visible in
 the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [X] I have included below a minimal working reproducer (if you are unsure how
 to write one see http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports).

With current master, the following:

```python
from numba import cuda

@cuda.jit(device=True)
def dev_func(x):
    return floor(x)


@cuda.jit
def kernel_func():
    dev_func(1.5)

kernel_func[1, 1]()
```

where `floor` is untyped because it isn't imported, results in the message:

```
numba.core.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Internal error at <numba.core.typeinfer.CallConstraint object at 0x7f9e808a3dc0>.
'DeviceFunctionTemplate' object has no attribute '__name__'
During: resolving callee type: Function(<numba.cuda.compiler.DeviceFunctionTemplate object at 0x7f9e8b7fdca0>)
During: typing of call at bug.py (10)

Enable logging at debug level for details.

File "bug.py", line 10:
def kernel_func():
    dev_func(1.5)
    ^
```

The error message used to say:

```
numba.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Invalid use of Function(<numba.cuda.compiler.DeviceFunctionTemplate object at 0x7faffda40e90>) with argument(s) of type(s): (float64)
 * parameterized
In definition 0:
    TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Untyped global name 'floor': cannot determine Numba type of <class 'numba.ir.UndefinedType'>

File "bug.py", line 5:
def dev_func(x):
    return floor(x)
    ^

    raised from /home/gmarkall/miniconda3/envs/numba048/lib/python3.7/site-packages/numba/typeinfer.py:1366
In definition 1:
    TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Untyped global name 'floor': cannot determine Numba type of <class 'numba.ir.UndefinedType'>

File "bug.py", line 5:
def dev_func(x):
    return floor(x)
    ^

    raised from /home/gmarkall/miniconda3/envs/numba048/lib/python3.7/site-packages/numba/typeinfer.py:1366
This error is usually caused by passing an argument of a type that is unsupported by the named function.
[1] During: resolving callee type: Function(<numba.cuda.compiler.DeviceFunctionTemplate object at 0x7faffda40e90>)
[2] During: typing of call at bug.py (10)


File "bug.py", line 10:
def kernel_func():
    dev_func(1.5)
```

I think this is probably related to recent changes to error messages.
Refactor source location info into Template
For code like: https://github.com/numba/numba/pull/5833/files/831da5efbbf4dbc547b216e9c61eca18931b8170#diff-758157deb494d50a6d6c93447b370a05

Reference:
https://github.com/numba/numba/pull/5833#discussion_r437701066



Discussion:
Thanks for the report. Yes, this is unfortunately from the error message changes, seems like this is another instance of the issue arising from there being numerous classes of a "thing that wraps some source function", xref: https://github.com/numba/numba/issues/5844. Fix is likely to be that everything in this class has to have a `.get_source()` method on it that returns the wrapped source or closest thing to it.
Note to self. `Boundfunction` types suffer the same problem.


Resolving patch:
diff --git a/numba/core/types/functions.py b/numba/core/types/functions.py
--- a/numba/core/types/functions.py
+++ b/numba/core/types/functions.py
@@ -37,7 +37,7 @@ def _wrapper(tmp, indent=0):
     return textwrap.indent(tmp, ' ' * indent,  lambda line: True)
 
 _overload_template = ("- Of which {nduplicates} did not match due to:\n"
-                      "Overload in function '{function}': File: {file}: "
+                      "{kind} {inof} function '{function}': File: {file}: "
                       "Line {line}.\n  With argument(s): '({args})':")
 
 _err_reasons = {}
@@ -86,21 +86,6 @@ def add_error(self, calltemplate, matched, error, literal):
         self._failures[key].append(_FAILURE(calltemplate, matched, error,
                                             literal))
 
-    def _get_source_info(self, source_fn):
-        source_line = "<N/A>"
-        if 'builtin_function' in str(type(source_fn)):
-            source_file = "<built-in>"
-        else:
-            try:
-                source_file = path.abspath(inspect.getsourcefile(source_fn))
-                source_line = inspect.getsourcelines(source_fn)[1]
-                here = path.abspath(__file__)
-                common = path.commonpath([here, source_file])
-                source_file = source_file.replace(common, 'numba')
-            except:
-                source_file = "Unknown"
-        return source_file, source_line
-
     def format(self):
         """Return a formatted error message from all the gathered errors.
         """
@@ -108,12 +93,14 @@ def format(self):
         argstr = argsnkwargs_to_str(self._args, self._kwargs)
         ncandidates = sum([len(x) for x in self._failures.values()])
 
+
         # sort out a display name for the function
         tykey = self._function_type.typing_key
         # most things have __name__
         fname = getattr(tykey, '__name__', None)
         is_external_fn_ptr = isinstance(self._function_type,
                                         ExternalFunctionPointer)
+
         if fname is None:
             if is_external_fn_ptr:
                 fname = "ExternalFunctionPointer"
@@ -128,55 +115,87 @@ def format(self):
         nolitkwargs = {k: unliteral(v) for k, v in self._kwargs.items()}
         nolitargstr = argsnkwargs_to_str(nolitargs, nolitkwargs)
 
-        key = self._function_type.key[0]
-        fn_name = getattr(key, '__name__', str(key))
-
         # depth could potentially get massive, so limit it.
         ldepth = min(max(self._depth, 0), self._max_depth)
 
+        def template_info(tp):
+            src_info = tp.get_template_info()
+            unknown = "unknown"
+            source_name = src_info.get('name', unknown)
+            source_file = src_info.get('filename', unknown)
+            source_lines = src_info.get('lines', unknown)
+            source_kind = src_info.get('kind', 'Unknown template')
+            return source_name, source_file, source_lines, source_kind
+
         for i, (k, err_list) in enumerate(self._failures.items()):
             err = err_list[0]
             nduplicates = len(err_list)
             template, error = err.template, err.error
-            source_fn = template.key
-            if isinstance(source_fn, numba.core.extending._Intrinsic):
-                source_fn = template.key._defn
-            elif (is_external_fn_ptr and
-                  isinstance(source_fn, numba.core.typing.templates.Signature)):
-                source_fn = template.__class__
-            elif hasattr(template, '_overload_func'):
-                source_fn = template._overload_func
-            source_file, source_line = self._get_source_info(source_fn)
+            ifo = template_info(template)
+            source_name, source_file, source_lines, source_kind = ifo
             largstr = argstr if err.literal else nolitargstr
-            msgbuf.append(_termcolor.errmsg(
-                _wrapper(_overload_template.format(nduplicates=nduplicates,
-                                                  function=source_fn.__name__,
-                                                  file=source_file,
-                                                  line=source_line,
-                                                  args=largstr),
-                ldepth + 1)))
-
-            if isinstance(error, BaseException):
-                reason = indent + self.format_error(error)
-                errstr = _err_reasons['specific_error'].format(reason)
+
+            if err.error == "No match.":
+                err_dict = defaultdict(set)
+                for errs in err_list:
+                    err_dict[errs.template].add(errs.literal)
+                # if there's just one template, and it's erroring on
+                # literal/nonliteral be specific
+                if len(err_dict) == 1:
+                    template = [_ for _ in err_dict.keys()][0]
+                    source_name, source_file, source_lines, source_kind = \
+                        template_info(template)
+                    source_lines = source_lines[0]
+                else:
+                    source_file = "<numerous>"
+                    source_lines = "N/A"
+
+                msgbuf.append(_termcolor.errmsg(
+                    _wrapper(_overload_template.format(nduplicates=nduplicates,
+                                                    kind = source_kind.title(),
+                                                    function=fname,
+                                                    inof='of',
+                                                    file=source_file,
+                                                    line=source_lines,
+                                                    args=largstr),
+                    ldepth + 1)))
+                msgbuf.append(_termcolor.highlight(_wrapper(err.error,
+                                                            ldepth + 2)))
             else:
-                errstr = error
-            # if you are a developer, show the back traces
-            if config.DEVELOPER_MODE:
+                # There was at least one match in this failure class, but it
+                # failed for a specific reason try and report this.
+                msgbuf.append(_termcolor.errmsg(
+                    _wrapper(_overload_template.format(nduplicates=nduplicates,
+                                                    kind = source_kind.title(),
+                                                    function=source_name,
+                                                    inof='in',
+                                                    file=source_file,
+                                                    line=source_lines[0],
+                                                    args=largstr),
+                    ldepth + 1)))
+
                 if isinstance(error, BaseException):
-                    # if the error is an actual exception instance, trace it
-                    bt = traceback.format_exception(type(error), error,
-                                                    error.__traceback__)
+                    reason = indent + self.format_error(error)
+                    errstr = _err_reasons['specific_error'].format(reason)
                 else:
-                    bt = [""]
-                bt_as_lines = _bt_as_lines(bt)
-                nd2indent = '\n{}'.format(2 * indent)
-                errstr += _termcolor.reset(nd2indent +
-                                            nd2indent.join(bt_as_lines))
-            msgbuf.append(_termcolor.highlight(_wrapper(errstr, ldepth + 2)))
-            loc = self.get_loc(template, error)
-            if loc:
-                msgbuf.append('{}raised from {}'.format(indent, loc))
+                    errstr = error
+                # if you are a developer, show the back traces
+                if config.DEVELOPER_MODE:
+                    if isinstance(error, BaseException):
+                        # if the error is an actual exception instance, trace it
+                        bt = traceback.format_exception(type(error), error,
+                                                        error.__traceback__)
+                    else:
+                        bt = [""]
+                    bt_as_lines = _bt_as_lines(bt)
+                    nd2indent = '\n{}'.format(2 * indent)
+                    errstr += _termcolor.reset(nd2indent +
+                                                nd2indent.join(bt_as_lines))
+                msgbuf.append(_termcolor.highlight(_wrapper(errstr,
+                                                            ldepth + 2)))
+                loc = self.get_loc(template, error)
+                if loc:
+                    msgbuf.append('{}raised from {}'.format(indent, loc))
 
         # the commented bit rewraps each block, may not be helpful?!
         return _wrapper('\n'.join(msgbuf) + '\n') # , self._scale * ldepth)
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
@@ -2,6 +2,7 @@
 Define typing templates
 """
 
+from abc import ABC, abstractmethod
 import functools
 import sys
 import inspect
@@ -246,7 +247,7 @@ def fold_arguments(pysig, args, kws, normal_handler, default_handler,
     return args
 
 
-class FunctionTemplate(object):
+class FunctionTemplate(ABC):
     # Set to true to disable unsafe cast.
     # subclass overide-able
     unsafe_casting = True
@@ -277,6 +278,24 @@ def get_impl_key(self, sig):
             key = key.im_func
         return key
 
+    @abstractmethod
+    def get_template_info(self):
+        """
+        Returns a dictionary with information specific to the template that will
+        govern how error messages are displayed to users. The dictionary must
+        be of the form:
+        info = {
+            'kind': "unknown", # str: The kind of template, e.g. "Overload"
+            'name': "unknown", # str: The name of the source function
+            'sig': "unknown",  # str: The signature(s) of the source function
+            'filename': "unknown", # str: The filename of the source function
+            'lines': ("start", "end"), # tuple(int, int): The start and
+                                         end line of the source function.
+            'docstring': "unknown" # str: The docstring of the source function
+        }
+        """
+        pass
+
 
 class AbstractTemplate(FunctionTemplate):
     """
@@ -310,6 +329,22 @@ def unpack_opt(x):
 
         return sig
 
+    def get_template_info(self):
+        impl = getattr(self, "generic")
+        basepath = os.path.dirname(os.path.dirname(numba.__file__))
+        code, firstlineno = inspect.getsourcelines(impl)
+        path = inspect.getsourcefile(impl)
+        sig = str(utils.pysignature(impl))
+        info = {
+            'kind': "overload",
+            'name': getattr(impl, '__qualname__', impl.__name__),
+            'sig': sig,
+            'filename': os.path.relpath(path, start=basepath),
+            'lines': (firstlineno, firstlineno + len(code) - 1),
+            'docstring': impl.__doc__
+        }
+        return info
+
 
 class CallableTemplate(FunctionTemplate):
     """
@@ -369,6 +404,23 @@ def unpack_opt(x):
         cases = [sig]
         return self._select(cases, bound.args, bound.kwargs)
 
+    def get_template_info(self):
+        impl = getattr(self, "generic")
+        basepath = os.path.dirname(os.path.dirname(numba.__file__))
+        code, firstlineno = inspect.getsourcelines(impl)
+        path = inspect.getsourcefile(impl)
+        sig = str(utils.pysignature(impl))
+        info = {
+            'kind': "overload",
+            'name': getattr(self.key, '__name__',
+                            getattr(impl, '__qualname__', impl.__name__),),
+            'sig': sig,
+            'filename': os.path.relpath(path, start=basepath),
+            'lines': (firstlineno, firstlineno + len(code) - 1),
+            'docstring': impl.__doc__
+        }
+        return info
+
 
 class ConcreteTemplate(FunctionTemplate):
     """
@@ -380,6 +432,25 @@ def apply(self, args, kws):
         cases = getattr(self, 'cases')
         return self._select(cases, args, kws)
 
+    def get_template_info(self):
+        import operator
+        name = getattr(self.key, '__name__', "unknown")
+        op_func = getattr(operator, name, None)
+
+        kind = "Type restricted function"
+        if op_func is not None:
+            if self.key is op_func:
+                kind = "operator overload"
+        info = {
+            'kind': kind,
+            'name': name,
+            'sig': "unknown",
+            'filename': "unknown",
+            'lines': ("unknown", "unknown"),
+            'docstring': "unknown"
+        }
+        return info
+
 
 class _EmptyImplementationEntry(InternalError):
     def __init__(self, reason):
@@ -667,6 +738,22 @@ def get_source_info(cls):
         }
         return info
 
+    def get_template_info(self):
+        basepath = os.path.dirname(os.path.dirname(numba.__file__))
+        impl = self._overload_func
+        code, firstlineno = inspect.getsourcelines(impl)
+        path = inspect.getsourcefile(impl)
+        sig = str(utils.pysignature(impl))
+        info = {
+            'kind': "overload",
+            'name': getattr(impl, '__qualname__', impl.__name__),
+            'sig': sig,
+            'filename': os.path.relpath(path, start=basepath),
+            'lines': (firstlineno, firstlineno + len(code) - 1),
+            'docstring': impl.__doc__
+        }
+        return info
+
 
 def make_overload_template(func, overload_func, jit_options, strict,
                            inline):
@@ -720,6 +807,22 @@ def get_impl_key(self, sig):
         """
         return self._overload_cache[sig.args]
 
+    def get_template_info(self):
+        basepath = os.path.dirname(os.path.dirname(numba.__file__))
+        impl = self._definition_func
+        code, firstlineno = inspect.getsourcelines(impl)
+        path = inspect.getsourcefile(impl)
+        sig = str(utils.pysignature(impl))
+        info = {
+            'kind': "intrinsic",
+            'name': getattr(impl, '__qualname__', impl.__name__),
+            'sig': sig,
+            'filename': os.path.relpath(path, start=basepath),
+            'lines': (firstlineno, firstlineno + len(code) - 1),
+            'docstring': impl.__doc__
+        }
+        return info
+
 
 def make_intrinsic_template(handle, defn, name):
     """
@@ -877,7 +980,7 @@ def make_overload_attribute_template(typ, attr, overload_func, inline,
     *overload_func*.
     """
     assert isinstance(typ, types.Type) or issubclass(typ, types.Type)
-    name = "OverloadTemplate_%s_%s" % (typ, attr)
+    name = "OverloadAttributeTemplate_%s_%s" % (typ, attr)
     # Note the implementation cache is subclass-specific
     dct = dict(key=typ, _attr=attr, _impl_cache={},
                _inline=staticmethod(InlineOptions(inline)),
diff --git a/numba/cuda/compiler.py b/numba/cuda/compiler.py
--- a/numba/cuda/compiler.py
+++ b/numba/cuda/compiler.py
@@ -1,4 +1,5 @@
 import ctypes
+import inspect
 import os
 import sys
 
@@ -8,7 +9,7 @@
 from numba.core import (types, typing, utils, funcdesc, serialize, config,
                         compiler, sigutils)
 from numba.core.compiler_lock import global_compiler_lock
-
+import numba
 from .cudadrv.devices import get_context
 from .cudadrv import nvvm, driver
 from .errors import normalize_kernel_dimensions
@@ -137,6 +138,8 @@ def __init__(self, pyfunc, debug, inline):
         self.debug = debug
         self.inline = inline
         self._compileinfos = {}
+        name = getattr(pyfunc, '__name__', 'unknown')
+        self.__name__ = f"{name} <CUDA device function>".format(name)
 
     def __reduce__(self):
         glbls = serialize._get_function_globals_for_reduction(self.py_func)
@@ -231,6 +234,21 @@ def generic(self, args, kws):
             assert not kws
             return dft.compile(args).signature
 
+        def get_template_info(cls):
+            basepath = os.path.dirname(os.path.dirname(numba.__file__))
+            code, firstlineno = inspect.getsourcelines(pyfunc)
+            path = inspect.getsourcefile(pyfunc)
+            sig = str(utils.pysignature(pyfunc))
+            info = {
+                'kind': "overload",
+                'name': getattr(cls.key, '__name__', "unknown"),
+                'sig': sig,
+                'filename': os.path.relpath(path, start=basepath),
+                'lines': (firstlineno, firstlineno + len(code) - 1),
+                'docstring': pyfunc.__doc__
+            }
+            return info
+
     typingctx = CUDATargetDesc.typingctx
     typingctx.insert_user_function(dft, device_function_template)
     return dft


Test patch:
diff --git a/numba/cuda/tests/cudapy/test_errors.py b/numba/cuda/tests/cudapy/test_errors.py
--- a/numba/cuda/tests/cudapy/test_errors.py
+++ b/numba/cuda/tests/cudapy/test_errors.py
@@ -1,7 +1,8 @@
 import numpy as np
 
 from numba import cuda
-from numba.cuda.testing import unittest, CUDATestCase
+from numba.core.errors import TypingError
+from numba.cuda.testing import unittest, CUDATestCase, skip_on_cudasim
 
 
 def noop(x):
@@ -53,6 +54,26 @@ def test_unconfigured_untyped_cudakernel(self):
         kernfunc = cuda.jit(noop)
         self._test_unconfigured(kernfunc)
 
+    @skip_on_cudasim('TypingError does not occur on simulator')
+    def test_typing_error(self):
+        # see #5860, this is present to catch changes to error reporting
+        # accidentally breaking the CUDA target
+
+        @cuda.jit(device=True)
+        def dev_func(x):
+            return floor(x) # oops, forgot to import `floor`.
+
+        @cuda.jit
+        def kernel_func():
+            dev_func(1.5)
+
+        with self.assertRaises(TypingError) as raises:
+            kernel_func[1, 1]()
+        excstr = str(raises.exception)
+        self.assertIn("Overload in function 'dev_func <CUDA device function>'",
+                      excstr)
+        self.assertIn("NameError: name 'floor' is not defined", excstr)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/numba/tests/test_errorhandling.py b/numba/tests/test_errorhandling.py
--- a/numba/tests/test_errorhandling.py
+++ b/numba/tests/test_errorhandling.py
@@ -2,21 +2,20 @@
 Unspecified error handling tests
 """
 
-import os
-from numba import jit, njit, typed, int64, intp, types
-from numba.core import errors, utils
-from numba.extending import overload, intrinsic
 import numpy as np
+import os
 
-
-from numba.core.untyped_passes import (ExtractByteCode, TranslateByteCode, FixupArgs,
+from numba import jit, njit, typed, int64, types
+from numba.core import errors
+import numba.core.typing.cffi_utils as cffi_support
+from numba.extending import (overload, intrinsic, overload_method,
+                             overload_attribute)
+from numba.core.compiler import CompilerBase
+from numba.core.untyped_passes import (TranslateByteCode, FixupArgs,
                                        IRProcessing,)
-
 from numba.core.typed_passes import (NopythonTypeInference, DeadCodeElimination,
-                                     NativeLowering, IRLegalization,
                                      NoPythonBackend)
-
-from numba.core.compiler_machinery import FunctionPass, PassManager, register_pass
+from numba.core.compiler_machinery import PassManager
 from numba.core.types.functions import _err_reasons as error_reasons
 
 from numba.tests.support import skip_parfors_unsupported
@@ -26,6 +25,7 @@
 _global_list = [1, 2, 3, 4]
 _global_dict = typed.Dict.empty(int64, int64)
 
+
 class TestErrorHandlingBeforeLowering(unittest.TestCase):
 
     def test_unsupported_make_function_return_inner_func(self):
@@ -96,7 +96,6 @@ def foo_docstring():
 
     def test_use_of_ir_unknown_loc(self):
         # for context see # 3390
-        from numba.core.compiler import CompilerBase
         class TestPipeline(CompilerBase):
             def define_pipelines(self):
                 name = 'bad_DCE_pipeline'
@@ -104,8 +103,8 @@ def define_pipelines(self):
                 pm.add_pass(TranslateByteCode, "analyzing bytecode")
                 pm.add_pass(FixupArgs, "fix up args")
                 pm.add_pass(IRProcessing, "processing IR")
-                # remove dead before type inference so that the Arg node is removed
-                # and the location of the arg cannot be found
+                # remove dead before type inference so that the Arg node is
+                # removed and the location of the arg cannot be found
                 pm.add_pass(DeadCodeElimination, "DCE")
                 # typing
                 pm.add_pass(NopythonTypeInference, "nopython frontend")
@@ -131,7 +130,6 @@ def check_write_to_globals(self, func):
         for ex in expected:
             self.assertIn(ex, str(raises.exception))
 
-
     def test_handling_of_write_to_reflected_global(self):
         @njit
         def foo():
@@ -150,7 +148,7 @@ def foo():
     def test_handling_forgotten_numba_internal_import(self):
         @njit(parallel=True)
         def foo():
-            for i in prange(10): # prange is not imported
+            for i in prange(10): # noqa: F821 prange is not imported
                 pass
 
         with self.assertRaises(errors.TypingError) as raises:
@@ -162,7 +160,7 @@ def foo():
 
     def test_handling_unsupported_generator_expression(self):
         def foo():
-            y = (x for x in range(10))
+            (x for x in range(10))
 
         expected = "The use of yield in a closure is unsupported."
 
@@ -174,7 +172,7 @@ def foo():
     def test_handling_undefined_variable(self):
         @njit
         def foo():
-            return a
+            return a # noqa: F821
 
         expected = "NameError: name 'a' is not defined"
 
@@ -182,6 +180,7 @@ def foo():
             foo()
         self.assertIn(expected, str(raises.exception))
 
+
 class TestConstantInferenceErrorHandling(unittest.TestCase):
 
     def test_basic_error(self):
@@ -244,8 +243,91 @@ def call_foo():
         excstr = str(raises.exception)
         self.assertIn("No match", excstr)
 
-    def test_error_in_intrinsic(self):
+    def test_error_function_source_is_correct(self):
+        """ Checks that the reported source location for an overload is the
+        overload implementation source, not the actual function source from the
+        target library."""
+
+        @njit
+        def foo():
+            np.linalg.svd("chars")
+
+        with self.assertRaises(errors.TypingError) as raises:
+            foo()
+
+        excstr = str(raises.exception)
+        self.assertIn(error_reasons['specific_error'].splitlines()[0], excstr)
+        expected_file = os.path.join("numba", "np", "linalg.py")
+        expected = f"Overload in function 'svd_impl': File: {expected_file}:"
+        self.assertIn(expected.format(expected_file), excstr)
+
+    def test_concrete_template_source(self):
+        # hits ConcreteTemplate
+        @njit
+        def foo():
+            return 'a' + 1
+
+        with self.assertRaises(errors.TypingError) as raises:
+            foo()
+
+        excstr = str(raises.exception)
+
+        self.assertIn("Operator Overload in function 'add'", excstr)
+        # there'll be numerous matched templates that don't work
+        self.assertIn("<numerous>", excstr)
+
+    def test_abstract_template_source(self):
+        # hits AbstractTemplate
+        @njit
+        def foo():
+            return len(1)
+
+        with self.assertRaises(errors.TypingError) as raises:
+            foo()
+
+        excstr = str(raises.exception)
+        self.assertIn("Overload of function 'len'", excstr)
+
+    def test_callable_template_source(self):
+        # hits CallableTemplate
+        @njit
+        def foo():
+            return np.angle(1)
+
+        with self.assertRaises(errors.TypingError) as raises:
+            foo()
+
+        excstr = str(raises.exception)
+        self.assertIn("Overload of function 'angle'", excstr)
 
+    def test_overloadfunction_template_source(self):
+        # hits _OverloadFunctionTemplate
+        def bar(x):
+            pass
+
+        @overload(bar)
+        def ol_bar(x):
+            pass
+
+        @njit
+        def foo():
+            return bar(1)
+
+        with self.assertRaises(errors.TypingError) as raises:
+            foo()
+
+        excstr = str(raises.exception)
+        # there will not be "numerous" matched templates, there's just one,
+        # the one above, so assert it is reported
+        self.assertNotIn("<numerous>", excstr)
+        expected_file = os.path.join("numba", "tests",
+                                     "test_errorhandling.py")
+        expected_ol = f"Overload of function 'bar': File: {expected_file}:"
+        self.assertIn(expected_ol.format(expected_file), excstr)
+        self.assertIn("No match.", excstr)
+
+    def test_intrinsic_template_source(self):
+        # hits _IntrinsicTemplate
         given_reason1 = "x must be literal"
         given_reason2 = "array.ndim must be 1"
 
@@ -275,24 +357,70 @@ def call_intrin():
         self.assertIn(error_reasons['specific_error'].splitlines()[0], excstr)
         self.assertIn(given_reason1, excstr)
         self.assertIn(given_reason2, excstr)
+        self.assertIn("Intrinsic in function", excstr)
 
-    def test_error_function_source_is_correct(self):
-        """ Checks that the reported source location for an overload is the
-        overload implementation source, not the actual function source from the
-        target library."""
+    def test_overloadmethod_template_source(self):
+        # doesn't hit _OverloadMethodTemplate for source as it's a nested
+        # exception
+        @overload_method(types.UnicodeType, 'isnonsense')
+        def ol_unicode_isnonsense(self):
+            pass
 
         @njit
         def foo():
-            np.linalg.svd("chars")
+            "abc".isnonsense()
 
         with self.assertRaises(errors.TypingError) as raises:
             foo()
 
         excstr = str(raises.exception)
-        self.assertIn(error_reasons['specific_error'].splitlines()[0], excstr)
-        expected_file = os.path.join("numba", "np", "linalg.py")
-        expected = f"Overload in function 'svd_impl': File: {expected_file}:"
-        self.assertIn(expected.format(expected_file), excstr)
+        self.assertIn("Overload of function 'ol_unicode_isnonsense'", excstr)
+
+    def test_overloadattribute_template_source(self):
+        # doesn't hit _OverloadMethodTemplate for source as it's a nested
+        # exception
+        @overload_attribute(types.UnicodeType, 'isnonsense')
+        def ol_unicode_isnonsense(self):
+            pass
+
+        @njit
+        def foo():
+            "abc".isnonsense
+
+        with self.assertRaises(errors.TypingError) as raises:
+            foo()
+
+        excstr = str(raises.exception)
+        self.assertIn("Overload of function 'ol_unicode_isnonsense'", excstr)
+
+    def test_external_function_pointer_template_source(self):
+        from numba.tests.ctypes_usecases import c_cos
+
+        @njit
+        def foo():
+            c_cos('a')
+
+        with self.assertRaises(errors.TypingError) as raises:
+            foo()
+
+        excstr = str(raises.exception)
+        self.assertIn("Type Restricted Function in function 'unknown'", excstr)
+
+    @unittest.skipUnless(cffi_support.SUPPORTED, "CFFI not supported")
+    def test_cffi_function_pointer_template_source(self):
+        from numba.tests import cffi_usecases as mod
+        mod.init()
+        func = mod.cffi_cos
+
+        @njit
+        def foo():
+            func('a')
+
+        with self.assertRaises(errors.TypingError) as raises:
+            foo()
+
+        excstr = str(raises.exception)
+        self.assertIn("Type Restricted Function in function 'unknown'", excstr)
 
 
 if __name__ == '__main__':