Repository: numba/numba
Pull request: Add option to catch only Numba `numba.core.errors` derived exceptions.
This makes it so that users of Numba can specify how to handle exceptions that occur during compilation.

The current behaviour (which will remain as the default for now) is to capture any exception that occurs in compilation, e.g. an `AttributeError`, and wrap it as part of a `TypingError` or `LoweringError` depending on when it occurred.

The new behaviour is permitted through the use of the `NUMBA_CAPTURED_ERRORS="new_style"` environment variable. It's purpose it to make it so that any exception that does not inherit from `numba.core.errors.NumbaError` **and** is raised during compilation is a “hard error”, i.e. the exception will propagate and compilation will halt. The purpose of this new style is to differentiate between intentionally raised exceptions and those which occur due to mistakes. For example, if an `AttributeError` occurs in the typing of an `@overload` function, under this new behaviour it is assumed that this a mistake in the implementation and compilation will halt due to this exception. This behaviour will eventually become the default.

The code base is updated to implement the above. The test suite is fixed up to accommodate the above and some implementations are altered for the same. Docs are updated.

NOTE: This PR covers the cases which are triggered through running the test suite, there are almost certainly more cases of exceptions raised in internal implementations which the new style of error capturing will pick up should tests be written.

NOTE: This PR makes public CI use the "new_style" behaviour and all new code will have to adhere to this.

How to test this? I suggest the following.
1. Review this PR as-is.
2. Run it through the build farm to make sure that the "old_style" exception handling works fine.
3. Update the PR to set `NUMBA_CAPTURED_ERRORS="new_style"` in the `conda-build` test phase.
4. Run 3. through the build farm to make sure that the "new_style" exception handling works and Numba is as self consistent with it as possible with respect to the test suite.

Closes #7278


Problem statement:
Make non-`NumbaError` exceptions during compilations hard errors
This issue aims to improve error handling. When any instances of `Exception` is raised during compilation, Numba would try to recover by continuing the function selection to find a working implementation. However, this behavior can be confusing. Some errors should be treated as hard error to indicate to the developer that the code is incorrect and Numba should not fallback to other alternatives. For example, a typo error can cause an `AttributeError`, resulting in the implementation to be never used. 

With the recent addition of target-specific overload support, the problem has became more apparent.  For instance, a target specific `overload` implementation could be skipped because an error in the target-selection, but Numba proceeds to fallback to the generic implementation. The fallback machinery can often hide or obfuscated the true error, making debugging harder. 

The proposed solution is to:

- Adopt the model that any non-`NumbaError` derived error are consider as hard errors.
- Initially, allow extensions (`@overloads`, `@lower_bultins`, etc) to opt in to the new model.
- Replace internal implementations
- Deprecate the old model.

Hints from issue discussion:

Commit message: Fix up use of config module in helper function

Patch to review:
diff --git a/buildscripts/incremental/test.cmd b/buildscripts/incremental/test.cmd
index 57354337320..650e33eee5f 100644
--- a/buildscripts/incremental/test.cmd
+++ b/buildscripts/incremental/test.cmd
@@ -14,6 +14,8 @@ set NUMBA_DISABLE_ERROR_MESSAGE_HIGHLIGHTING=1
 set NUMBA_DEVELOPER_MODE=1
 @rem enable the faulthandler
 set PYTHONFAULTHANDLER=1
+@rem HACK: REMOVE THIS... enable new style error handling
+set NUMBA_CAPTURED_ERRORS="new_style"
 
 @rem First check that the test discovery works
 python -m numba.tests.test_runtests
diff --git a/buildscripts/incremental/test.sh b/buildscripts/incremental/test.sh
index e95dedd228f..00f8f2367c1 100755
--- a/buildscripts/incremental/test.sh
+++ b/buildscripts/incremental/test.sh
@@ -23,6 +23,9 @@ export NUMBA_DEVELOPER_MODE=1
 # enable the fault handler
 export PYTHONFAULTHANDLER=1
 
+# HACK: REMOVE THIS... enable new style error handling
+export NUMBA_CAPTURED_ERRORS="new_style"
+
 # deal with threading layers
 if [ -z ${TEST_THREADING+x} ]; then
     echo "INFO: Threading layer not explicitly set."
diff --git a/docs/source/extending/high-level.rst b/docs/source/extending/high-level.rst
index a23874f7f30..4e4877336f3 100644
--- a/docs/source/extending/high-level.rst
+++ b/docs/source/extending/high-level.rst
@@ -6,6 +6,14 @@ High-level extension API
 
 This extension API is exposed through the :mod:`numba.extending` module.
 
+To aid debugging extensions to Numba, it's recommended to set the following
+environment variable::
+
+    NUMBA_CAPTURED_ERRORS="new_style"
+
+this makes it easy to differentiate between errors in implementation and
+acceptable errors that can take part in e.g. type inference. For more
+information see :envvar:`NUMBA_CAPTURED_ERRORS`.
 
 Implementing functions
 ----------------------
diff --git a/docs/source/reference/envvars.rst b/docs/source/reference/envvars.rst
index de04ee66403..c4910d31ed3 100644
--- a/docs/source/reference/envvars.rst
+++ b/docs/source/reference/envvars.rst
@@ -77,6 +77,28 @@ These variables influence what is printed out during compilation of
 
     If set to non-zero, show resources for getting help. Default is zero.
 
+.. envvar:: NUMBA_CAPTURED_ERRORS
+
+    Alters the way in which Numba captures and handles exceptions that do not
+    inherit from ``numba.core.errors.NumbaError`` during compilation (e.g.
+    standard Python exceptions). This does not impact runtime exception
+    handling. Valid values are:
+
+    - ``"old_style"`` (default): this is the exception handling behaviour that
+      is present in Numba versions <= 0.54.x. Numba will capture and wrap all
+      errors occuring in compilation and depending on the compilation phase they
+      will likely materialize as part of the message in a ``TypingError`` or a
+      ``LoweringError``.
+    - ``"new_style"`` this will treat any exception that does not inherit from
+      ``numba.core.errors.NumbaError`` **and** is raised during compilation as a
+      "hard error", i.e. the exception will propagate and compilation will halt.
+      The purpose of this new style is to differentiate between intentionally
+      raised exceptions and those which occur due to mistakes. For example, if
+      an ``AttributeError`` occurs in the typing of an ``@overload`` function,
+      under this new behaviour it is assumed that this a mistake in the
+      implementation and compilation will halt due to this exception. This
+      behaviour will eventually become the default.
+
 .. envvar:: NUMBA_DISABLE_ERROR_MESSAGE_HIGHLIGHTING
 
     If set to non-zero error message highlighting is disabled. This is useful
diff --git a/numba/core/base.py b/numba/core/base.py
index 830ccc02af1..dc4326ae6de 100644
--- a/numba/core/base.py
+++ b/numba/core/base.py
@@ -13,7 +13,7 @@
 import llvmlite.binding as ll
 
 from numba.core import types, utils, typing, datamodel, debuginfo, funcdesc, config, cgutils, imputils
-from numba.core import event
+from numba.core import event, errors
 from numba import _dynfunc, _helperlib
 from numba.core.compiler_lock import global_compiler_lock
 from numba.core.pythonapi import PythonAPI
@@ -54,7 +54,7 @@ def _find(self, sig):
         if candidates:
             return candidates[self._best_signature(candidates)]
         else:
-            raise NotImplementedError(self, sig)
+            raise errors.NumbaNotImplementedError(f'{self}, {sig}')
 
     def _select_compatible(self, sig):
         """
@@ -80,7 +80,7 @@ def _best_signature(self, candidates):
                 msg = ["{n} ambiguous signatures".format(n=len(same))]
                 for sig in same:
                     msg += ["{0} => {1}".format(sig, candidates[sig])]
-                raise TypeError('\n'.join(msg))
+                raise errors.NumbaTypeError('\n'.join(msg))
         return ordered[0]
 
     def _sort_signatures(self, candidates):
@@ -551,13 +551,13 @@ def get_function(self, fn, sig, _firstcall=True):
 
         try:
             return _wrap_impl(overloads.find(sig.args), self, sig)
-        except NotImplementedError:
+        except errors.NumbaNotImplementedError:
             pass
         if isinstance(fn, types.Type):
             # It's a type instance => try to find a definition for the type class
             try:
                 return self.get_function(type(fn), sig)
-            except NotImplementedError:
+            except errors.NumbaNotImplementedError:
                 # Raise exception for the type instance, for a better error message
                 pass
 
@@ -613,13 +613,13 @@ def imp(context, builder, typ, val, attr):
         overloads = self._getattrs[attr]
         try:
             return overloads.find((typ,))
-        except NotImplementedError:
+        except errors.NumbaNotImplementedError:
             pass
         # Lookup generic getattr implementation for this type
         overloads = self._getattrs[None]
         try:
             return overloads.find((typ,))
-        except NotImplementedError:
+        except errors.NumbaNotImplementedError:
             pass
 
         raise NotImplementedError("No definition for lowering %s.%s" % (typ, attr))
@@ -643,13 +643,13 @@ def wrapped(builder, args):
         overloads = self._setattrs[attr]
         try:
             return wrap_setattr(overloads.find((typ, valty)))
-        except NotImplementedError:
+        except errors.NumbaNotImplementedError:
             pass
         # Lookup generic setattr implementation for this type
         overloads = self._setattrs[None]
         try:
             return wrap_setattr(overloads.find((typ, valty)))
-        except NotImplementedError:
+        except errors.NumbaNotImplementedError:
             pass
 
         raise NotImplementedError("No definition for lowering %s.%s = %s"
@@ -709,8 +709,8 @@ def cast(self, builder, val, fromty, toty):
         try:
             impl = self._casts.find((fromty, toty))
             return impl(self, builder, fromty, toty, val)
-        except NotImplementedError:
-            raise NotImplementedError(
+        except errors.NumbaNotImplementedError:
+            raise errors.NumbaNotImplementedError(
                 "Cannot cast %s to %s: %s" % (fromty, toty, val))
 
     def generic_compare(self, builder, key, argtypes, args):
diff --git a/numba/core/boxing.py b/numba/core/boxing.py
index a4c652f2208..e96f2248824 100644
--- a/numba/core/boxing.py
+++ b/numba/core/boxing.py
@@ -6,6 +6,7 @@
 
 from numba.core import types, cgutils
 from numba.core.pythonapi import box, unbox, reflect, NativeValue
+from numba.core.errors import NumbaNotImplementedError
 
 from numba.cpython import setobj, listobj
 from numba.np import numpy_support
@@ -458,7 +459,7 @@ def unbox_array(typ, obj, c):
     #       need to do better
     try:
         expected_itemsize = numpy_support.as_dtype(typ.dtype).itemsize
-    except NotImplementedError:
+    except NumbaNotImplementedError:
         # Don't check types that can't be `as_dtype()`-ed
         itemsize_mismatch = cgutils.false_bit
     else:
diff --git a/numba/core/compiler.py b/numba/core/compiler.py
index 097ad9a97b3..54b17d94944 100644
--- a/numba/core/compiler.py
+++ b/numba/core/compiler.py
@@ -466,6 +466,10 @@ def _compile_core(self):
                     res = e.result
                     break
                 except Exception as e:
+                    if (utils.use_new_style_errors() and not
+                            isinstance(e, errors.NumbaError)):
+                        raise e
+
                     self.state.status.fail_reason = e
                     if is_final_pipeline:
                         raise e
diff --git a/numba/core/compiler_machinery.py b/numba/core/compiler_machinery.py
index 0696f0cc389..48bf2b59d20 100644
--- a/numba/core/compiler_machinery.py
+++ b/numba/core/compiler_machinery.py
@@ -3,7 +3,7 @@
 from collections import namedtuple, OrderedDict
 import inspect
 from numba.core.compiler_lock import global_compiler_lock
-from numba.core import errors, config, transforms
+from numba.core import errors, config, transforms, utils
 from numba.core.tracing import event
 from numba.core.postproc import PostProcessor
 from numba.core.ir_utils import enforce_no_dels, legalize_single_scope
@@ -337,6 +337,9 @@ def run(self, state):
             except _EarlyPipelineCompletion as e:
                 raise e
             except Exception as e:
+                if (utils.use_new_style_errors() and not
+                        isinstance(e, errors.NumbaError)):
+                    raise e
                 msg = "Failed in %s mode pipeline (step: %s)" % \
                     (self.pipeline_name, pass_desc)
                 patched_exception = self._patch_error(msg, e)
diff --git a/numba/core/config.py b/numba/core/config.py
index e3d6ac336a2..465e7d550b5 100644
--- a/numba/core/config.py
+++ b/numba/core/config.py
@@ -308,6 +308,9 @@ def avx_default():
         # choose parallel backend to use
         THREADING_LAYER = _readenv("NUMBA_THREADING_LAYER", str, 'default')
 
+        # Choose how to handle captured errors
+        CAPTURED_ERRORS = _readenv("NUMBA_CAPTURED_ERRORS", str, 'old_style')
+
         # CUDA Configs
 
         # Whether to warn about kernel launches where a host array
diff --git a/numba/core/errors.py b/numba/core/errors.py
index b69321cd332..9d90b818b4a 100644
--- a/numba/core/errors.py
+++ b/numba/core/errors.py
@@ -747,6 +747,44 @@ class LiteralTypingError(TypingError):
     pass
 
 
+# These Exception classes are just Numba copies of their Python equivalents for
+# use internally in cases where we want e.g. type inference to keep on trying.
+# Exceptions extending from NumbaError are considered "special" by Numba's
+# internals and are treated differently to standard Python exceptions which are
+# permitted to just propagate up the stack.
+
+class NumbaValueError(TypingError):
+    pass
+
+
+class NumbaTypeError(TypingError):
+    pass
+
+
+class NumbaAttributeError(TypingError):
+    pass
+
+
+class NumbaAssertionError(TypingError):
+    pass
+
+
+class NumbaNotImplementedError(TypingError):
+    pass
+
+
+class NumbaKeyError(TypingError):
+    pass
+
+
+class NumbaIndexError(TypingError):
+    pass
+
+
+class NumbaRuntimeError(NumbaError):
+    pass
+
+
 def _format_msg(fmt, args, kwargs):
     return fmt.format(*args, **kwargs)
 
@@ -783,9 +821,17 @@ def new_error_context(fmt_, *args, **kwargs):
         # Let assertion error pass through for shorter traceback in debugging
         raise
     except Exception as e:
-        newerr = errcls(e).add_context(_format_msg(fmt_, args, kwargs))
-        tb = sys.exc_info()[2] if numba.core.config.FULL_TRACEBACKS else None
-        raise newerr.with_traceback(tb)
+        if numba.core.config.CAPTURED_ERRORS == 'old_style':
+            newerr = errcls(e).add_context(_format_msg(fmt_, args, kwargs))
+            if numba.core.config.FULL_TRACEBACKS:
+                tb = sys.exc_info()[2]
+            else:
+                tb = None
+            raise newerr.with_traceback(tb)
+        elif numba.core.config.CAPTURED_ERRORS == 'new_style':
+            raise e
+        else:
+            assert 0
 
 
 __all__ += [name for (name, value) in globals().items()
diff --git a/numba/core/pylowering.py b/numba/core/pylowering.py
index 2b8aa8c6002..bd2fb923244 100644
--- a/numba/core/pylowering.py
+++ b/numba/core/pylowering.py
@@ -11,7 +11,8 @@
 import llvmlite.llvmpy.core as lc
 
 from numba.core import types, utils, ir, generators, cgutils
-from numba.core.errors import ForbiddenConstruct, LoweringError
+from numba.core.errors import (ForbiddenConstruct, LoweringError,
+                               NumbaNotImplementedError)
 from numba.core.lowering import BaseLower
 
 
@@ -165,7 +166,8 @@ def lower_inst(self, inst):
             self.return_exception_raised()
 
         else:
-            raise NotImplementedError(type(inst), inst)
+            msg = f"{type(inst)}, {inst}"
+            raise NumbaNotImplementedError(msg)
 
     @utils.cached_property
     def _omitted_typobj(self):
diff --git a/numba/core/runtime/context.py b/numba/core/runtime/context.py
index 53333bdbc2c..1de043b0e5b 100644
--- a/numba/core/runtime/context.py
+++ b/numba/core/runtime/context.py
@@ -1,6 +1,6 @@
 from llvmlite import ir
 
-from numba.core import types, cgutils
+from numba.core import types, cgutils, errors
 
 
 class NRTContext(object):
@@ -14,7 +14,7 @@ def __init__(self, context, enabled):
 
     def _require_nrt(self):
         if not self._enabled:
-            raise RuntimeError("NRT required but not enabled")
+            raise errors.NumbaRuntimeError("NRT required but not enabled")
 
     def allocate(self, builder, size):
         """
diff --git a/numba/core/target_extension.py b/numba/core/target_extension.py
index 2fabe3b8f4e..d0546671c64 100644
--- a/numba/core/target_extension.py
+++ b/numba/core/target_extension.py
@@ -1,7 +1,7 @@
 from abc import ABC, abstractmethod
 from numba.core.registry import DelayedRegistry, CPUDispatcher
 from numba.core.decorators import jit
-from numba.core.errors import InternalTargetMismatchError
+from numba.core.errors import InternalTargetMismatchError, NumbaValueError
 from threading import local as tls
 
 
@@ -18,7 +18,7 @@ def __getitem__(self, item):
             msg = "No target is registered against '{}', known targets:\n{}"
             known = '\n'.join([f"{k: <{10}} -> {v}"
                                for k, v in target_registry.items()])
-            raise ValueError(msg.format(item, known)) from None
+            raise NumbaValueError(msg.format(item, known)) from None
 
 
 # Registry mapping target name strings to Target classes
diff --git a/numba/core/typed_passes.py b/numba/core/typed_passes.py
index bb7a498cd8f..c84003856d5 100644
--- a/numba/core/typed_passes.py
+++ b/numba/core/typed_passes.py
@@ -145,15 +145,15 @@ def legalize_return_type(return_type, interp, targetctx):
                     cast = caststmts.get(var)
                     if cast is None or cast.value.name not in argvars:
                         if self._raise_errors:
-                            raise TypeError("Only accept returning of array "
-                                            "passed into the function as "
-                                            "argument")
+                            msg = ("Only accept returning of array passed into "
+                                   "the function as argument")
+                            raise errors.NumbaTypeError(msg)
 
             elif (isinstance(return_type, types.Function) or
                     isinstance(return_type, types.Phantom)):
                 if self._raise_errors:
                     msg = "Can't return function object ({}) in nopython mode"
-                    raise TypeError(msg.format(return_type))
+                    raise errors.NumbaTypeError(msg.format(return_type))
 
         with fallback_context(state, 'Function "%s" has invalid return type'
                               % (state.func_id.func_name,)):
diff --git a/numba/core/typeinfer.py b/numba/core/typeinfer.py
index 9a7e3d14ad9..64eec05df21 100644
--- a/numba/core/typeinfer.py
+++ b/numba/core/typeinfer.py
@@ -25,7 +25,7 @@
 from numba.core.typing.templates import Signature
 from numba.core.errors import (TypingError, UntypedAttributeError,
                                new_error_context, termcolor, UnsupportedError,
-                               ForceLiteralArg, CompilerError)
+                               ForceLiteralArg, CompilerError, NumbaValueError)
 from numba.core.funcdesc import qualifying_prefix
 
 _logger = logging.getLogger(__name__)
@@ -162,15 +162,20 @@ def propagate(self, typeinfer):
                     )
                     errors.append(utils.chain_exception(new_exc, e))
                 except Exception as e:
-                    _logger.debug("captured error", exc_info=e)
-                    msg = ("Internal error at {con}.\n"
-                           "{err}\nEnable logging at debug level for details.")
-                    new_exc = TypingError(
-                        msg.format(con=constraint, err=str(e)),
-                        loc=constraint.loc,
-                        highlighting=False,
-                    )
-                    errors.append(utils.chain_exception(new_exc, e))
+                    if config.CAPTURED_ERRORS == 'old_style':
+                        _logger.debug("captured error", exc_info=e)
+                        msg = ("Internal error at {con}.\n{err}\n"
+                               "Enable logging at debug level for details.")
+                        new_exc = TypingError(
+                            msg.format(con=constraint, err=str(e)),
+                            loc=constraint.loc,
+                            highlighting=False,
+                        )
+                        errors.append(utils.chain_exception(new_exc, e))
+                    elif config.CAPTURED_ERRORS == 'new_style':
+                        raise e
+                    else:
+                        assert 0
         return errors
 
 
@@ -389,10 +394,9 @@ def __call__(self, typeinfer):
                         typeinfer.add_type(self.target, tp, loc=self.loc)
                         break
                     else:
-                        raise ValueError("wrong tuple length for %r: "
-                                         "expected %d, got %d"
-                                         % (self.iterator.name, self.count,
-                                            len(tp)))
+                        msg = (f"wrong tuple length for {self.iterator.name}: ",
+                               f"expected {self.count}, got {len(tp)}")
+                        raise NumbaValueError(msg)
                 elif isinstance(tp, types.IterableType):
                     tup = types.UniTuple(dtype=tp.iterator_type.yield_type,
                                          count=self.count)
diff --git a/numba/core/types/common.py b/numba/core/types/common.py
index ddc4a626499..129f109dde0 100644
--- a/numba/core/types/common.py
+++ b/numba/core/types/common.py
@@ -3,6 +3,7 @@
 """
 
 from .abstract import ArrayCompatible, Dummy, IterableType, IteratorType
+from numba.core.errors import NumbaTypeError, NumbaValueError
 
 
 class Opaque(Dummy):
@@ -52,9 +53,9 @@ def __init__(self, dtype, ndim, layout, readonly=False, name=None):
             msg = ("The dtype of a Buffer type cannot itself be a Buffer type, "
                    "this is unsupported behaviour."
                    "\nThe dtype requested for the unsupported Buffer was: {}.")
-            raise TypeError(msg.format(dtype))
+            raise NumbaTypeError(msg.format(dtype))
         if layout not in self.LAYOUTS:
-            raise ValueError("Invalid layout '%s'" % layout)
+            raise NumbaValueError("Invalid layout '%s'" % layout)
         self.dtype = unliteral(dtype)
         self.ndim = ndim
         self.layout = layout
diff --git a/numba/core/types/functions.py b/numba/core/types/functions.py
index 5794f0590c3..2485b270a33 100644
--- a/numba/core/types/functions.py
+++ b/numba/core/types/functions.py
@@ -328,8 +328,12 @@ def key(x):
                                     for k, v in kws.items()}
                         sig = temp.apply(nolitargs, nolitkws)
                 except Exception as e:
-                    sig = None
-                    failures.add_error(temp, False, e, uselit)
+                    if (utils.use_new_style_errors() and not
+                            isinstance(e, errors.NumbaError)):
+                        raise e
+                    else:
+                        sig = None
+                        failures.add_error(temp, False, e, uselit)
                 else:
                     if sig is not None:
                         self._impl_keys[sig.args] = temp.get_impl_key(sig)
@@ -416,6 +420,9 @@ def get_call_type(self, context, args, kws):
                 try:
                     out = template.apply(args, kws)
                 except Exception as exc:
+                    if (utils.use_new_style_errors() and not
+                            isinstance(exc, errors.NumbaError)):
+                        raise exc
                     if isinstance(exc, errors.ForceLiteralArg):
                         raise exc
                     literal_e = exc
diff --git a/numba/core/typing/arraydecl.py b/numba/core/typing/arraydecl.py
index 64b29994b99..6f9b45edf30 100644
--- a/numba/core/typing/arraydecl.py
+++ b/numba/core/typing/arraydecl.py
@@ -9,7 +9,9 @@
 # import time side effect: array operations requires typing support of sequence
 # defined in collections: e.g. array.shape[i]
 from numba.core.typing import collections
-from numba.core.errors import TypingError
+from numba.core.errors import (TypingError, NumbaTypeError,
+                               NumbaNotImplementedError, NumbaAssertionError,
+                               NumbaKeyError, NumbaIndexError)
 
 Indexing = namedtuple("Indexing", ("index", "result", "advanced"))
 
@@ -39,8 +41,8 @@ def get_array_index_type(ary, idx):
     for ty in idx:
         if ty is types.ellipsis:
             if ellipsis_met:
-                raise TypeError("only one ellipsis allowed in array index "
-                                "(got %s)" % (idx,))
+                raise NumbaTypeError("only one ellipsis allowed in array index "
+                                     "(got %s)" % (idx,))
             ellipsis_met = True
         elif isinstance(ty, types.SliceType):
             pass
@@ -62,11 +64,12 @@ def get_array_index_type(ary, idx):
                 # We don't support the complicated combination of
                 # advanced indices (and integers are considered part
                 # of them by Numpy).
-                raise NotImplementedError("only one advanced index supported")
+                msg = "only one advanced index supported"
+                raise NumbaNotImplementedError(msg)
             advanced = True
         else:
-            raise TypeError("unsupported array index type %s in %s"
-                            % (ty, idx))
+            raise NumbaTypeError("unsupported array index type %s in %s"
+                                 % (ty, idx))
         (right_indices if ellipsis_met else left_indices).append(ty)
 
     # Only Numpy arrays support advanced indexing
@@ -81,8 +84,8 @@ def get_array_index_type(ary, idx):
 
     n_indices = len(all_indices) - ellipsis_met
     if n_indices > ary.ndim:
-        raise TypeError("cannot index %s with %d indices: %s"
-                        % (ary, n_indices, idx))
+        raise NumbaTypeError("cannot index %s with %d indices: %s"
+                             % (ary, n_indices, idx))
     if n_indices == ary.ndim and ndim == 0 and not ellipsis_met:
         # Full integer indexing => scalar result
         # (note if ellipsis is present, a 0-d view is returned instead)
@@ -174,7 +177,7 @@ def generic(self, args, kws):
         if not isinstance(ary, types.Buffer):
             return
         if not ary.mutable:
-            raise TypeError("Cannot modify value of type %s" %(ary,))
+            raise NumbaTypeError(f"Cannot modify value of type {ary}")
         out = get_array_index_type(ary, idx)
         if out is None:
             return
@@ -477,7 +480,8 @@ def resolve_flatten(self, ary, args, kws):
 
     @bound_function("array.take")
     def resolve_take(self, ary, args, kws):
-        assert not kws
+        if kws:
+            raise NumbaAssertionError("kws not supported")
         argty, = args
         if isinstance(argty, types.Integer):
             sig = signature(ary.dtype, *args)
@@ -562,15 +566,16 @@ def generic(self, args, kws):
         if isinstance(record, types.Record):
             if isinstance(idx, types.StringLiteral):
                 if idx.literal_value not in record.fields:
-                    raise KeyError(f"Field '{idx.literal_value}' was not found in record with "
-                                   f"fields {tuple(record.fields.keys())}")
+                    msg = (f"Field '{idx.literal_value}' was not found in "
+                           f"record with fields {tuple(record.fields.keys())}")
+                    raise NumbaKeyError(msg)
                 ret = record.typeof(idx.literal_value)
                 assert ret
                 return signature(ret, *args)
             elif isinstance(idx, types.IntegerLiteral):
                 if idx.literal_value >= len(record.fields):
                     msg = f"Requested index {idx.literal_value} is out of range"
-                    raise IndexError(msg)
+                    raise NumbaIndexError(msg)
                 field_names = list(record.fields)
                 ret = record.typeof(field_names[idx.literal_value])
                 assert ret
@@ -593,7 +598,7 @@ def generic(self, args, kws):
             elif isinstance(idx, int):
                 if idx >= len(record.fields):
                     msg = f"Requested index {idx} is out of range"
-                    raise IndexError(msg)
+                    raise NumbaIndexError(msg)
                 str_field = list(record.fields)[idx]
                 expectedty = record.typeof(str_field)
                 if self.context.can_convert(value, expectedty) is not None:
@@ -608,8 +613,9 @@ def generic(self, args, kws):
         target, idx, value = args
         if isinstance(target, types.Record) and isinstance(idx, types.StringLiteral):
             if idx.literal_value not in target.fields:
-                raise KeyError(f"Field '{idx.literal_value}' was not found in record with "
-                               f"fields {tuple(target.fields.keys())}")
+                msg = (f"Field '{idx.literal_value}' was not found in record "
+                       f"with fields {tuple(target.fields.keys())}")
+                raise NumbaKeyError(msg)
             expectedty = target.typeof(idx.literal_value)
             if self.context.can_convert(value, expectedty) is not None:
                 return signature(types.void, target, idx, value)
@@ -658,8 +664,11 @@ def _expand_integer(ty):
 
 
 def generic_homog(self, args, kws):
-    assert not args
-    assert not kws
+    if args:
+        raise NumbaAssertionError("args not supported")
+    if kws:
+        raise NumbaAssertionError("kws not supported")
+
     return signature(self.this.dtype, recvr=self.this)
 
 
@@ -739,8 +748,10 @@ def sum_stub(axis, dtype):
 
 
 def generic_expand_cumulative(self, args, kws):
-    assert not args
-    assert not kws
+    if args:
+        raise NumbaAssertionError("args unsupported")
+    if kws:
+        raise NumbaAssertionError("kwargs unsupported")
     assert isinstance(self.this, types.Array)
     return_type = types.Array(dtype=_expand_integer(self.this.dtype),
                               ndim=1, layout='C')
diff --git a/numba/core/typing/builtins.py b/numba/core/typing/builtins.py
index 649a5acf38a..fe7827ba496 100644
--- a/numba/core/typing/builtins.py
+++ b/numba/core/typing/builtins.py
@@ -608,7 +608,10 @@ def generic(self, args, kws):
         if not isinstance(tup, types.BaseTuple):
             return
         if isinstance(idx, int):
-            ret = tup.types[idx]
+            try:
+                ret = tup.types[idx]
+            except IndexError:
+                raise errors.NumbaIndexError("tuple index out of range")
         elif isinstance(idx, slice):
             ret = types.BaseTuple.from_types(tup.types[idx])
         if ret is not None:
@@ -770,12 +773,12 @@ def resolve_step(self, ty):
     def resolve_indices(self, ty, args, kws):
         assert not kws
         if len(args) != 1:
-            raise TypeError(
+            raise errors.NumbaTypeError(
                 "indices() takes exactly one argument (%d given)" % len(args)
             )
         typ, = args
         if not isinstance(typ, types.Integer):
-            raise TypeError(
+            raise errors.NumbaTypeError(
                 "'%s' object cannot be interpreted as an integer" % typ
             )
         return signature(types.UniTuple(types.intp, 3), types.intp)
@@ -945,7 +948,8 @@ def generic(self, args, kws):
 class Int(AbstractTemplate):
 
     def generic(self, args, kws):
-        assert not kws
+        if kws:
+            raise errors.NumbaAssertionError('kws not supported')
 
         [arg] = args
 
@@ -964,10 +968,10 @@ def generic(self, args, kws):
         [arg] = args
 
         if arg not in types.number_domain:
-            raise TypeError("float() only support for numbers")
+            raise errors.NumbaTypeError("float() only support for numbers")
 
         if arg in types.complex_domain:
-            raise TypeError("float() does not support complex")
+            raise errors.NumbaTypeError("float() does not support complex")
 
         if arg in types.integer_domain:
             return signature(types.float64, arg)
@@ -1011,8 +1015,8 @@ def generic(self, args, kws):
         assert not kws
         it = args[0]
         if len(args) > 1 and not isinstance(args[1], types.Integer):
-            raise TypeError("Only integers supported as start value in "
-                            "enumerate")
+            raise errors.NumbaTypeError("Only integers supported as start "
+                                        "value in enumerate")
         elif len(args) > 2:
             #let python raise its own error
             enumerate(*args)
diff --git a/numba/core/typing/context.py b/numba/core/typing/context.py
index 4c565b4faf2..7c0dcf5d260 100644
--- a/numba/core/typing/context.py
+++ b/numba/core/typing/context.py
@@ -63,7 +63,7 @@ def register(self, target, typeinfer, func_id, args):
         # guard compiling the same function with the same signature
         if self.match(func_id.func, args):
             msg = "compiler re-entrant to the same function signature"
-            raise RuntimeError(msg)
+            raise errors.NumbaRuntimeError(msg)
         self._lock.acquire()
         self._stack.append(CallFrame(target, typeinfer, func_id, args))
         try:
diff --git a/numba/core/typing/npydecl.py b/numba/core/typing/npydecl.py
index 6cdb16e0d91..f3b89de52ac 100644
--- a/numba/core/typing/npydecl.py
+++ b/numba/core/typing/npydecl.py
@@ -11,7 +11,8 @@
                              supported_ufunc_loop, as_dtype,
                              from_dtype, as_dtype, resolve_output_type,
                              carray, farray, _ufunc_loop_sig)
-from numba.core.errors import TypingError, NumbaPerformanceWarning
+from numba.core.errors import (TypingError, NumbaPerformanceWarning,
+                               NumbaTypeError, NumbaAssertionError)
 from numba import pndindex
 from numba.core.overload_glue import glue_typing
 
@@ -147,7 +148,7 @@ def generic(self, args, kws):
                         # raise TypeError here because
                         # NumpyRulesArrayOperator.generic is capturing
                         # TypingError
-                        raise TypeError(msg)
+                        raise NumbaTypeError(msg)
                     elif not issubclass(output_type, types.Array):
                         msg = (f"ufunc {ufunc} on {array_ufunc_type}"
                                f"cannot return non-array {output_type}")
@@ -786,9 +787,9 @@ def _homogeneous_dims(context, func_name, arrays):
     ndim = arrays[0].ndim
     for a in arrays:
         if a.ndim != ndim:
-            raise TypeError("%s(): all the input arrays "
-                            "must have same number of dimensions"
-                            % func_name)
+            msg = (f"{func_name}(): all the input arrays must have same number "
+                   "of dimensions")
+            raise NumbaTypeError(msg)
     return ndim
 
 def _sequence_of_arrays(context, func_name, arrays,
@@ -1224,8 +1225,10 @@ def typer(ref, k=0):
 class Take(AbstractTemplate):
 
     def generic(self, args, kws):
-        assert not kws
-        assert len(args) == 2
+        if kws:
+            raise NumbaAssertionError("kws not supported")
+        if len(args) != 2:
+            raise NumbaAssertionError("two arguments are required")
         arr, ind = args
         if isinstance(ind, types.Number):
             retty = arr.dtype
@@ -1256,27 +1259,27 @@ def typer(ptr, shape, dtype=types.none):
             elif isinstance(ptr, types.CPointer):
                 ptr_dtype = ptr.dtype
             else:
-                raise TypeError("%s(): pointer argument expected, got '%s'"
-                                % (func_name, ptr))
+                raise NumbaTypeError("%s(): pointer argument expected, got '%s'"
+                                     % (func_name, ptr))
 
             if dtype is types.none:
                 if ptr_dtype is None:
-                    raise TypeError("%s(): explicit dtype required for void* argument"
-                                    % (func_name,))
+                    raise NumbaTypeError("%s(): explicit dtype required for void* argument"
+                                         % (func_name,))
                 dtype = ptr_dtype
             elif isinstance(dtype, types.DTypeSpec):
                 dtype = dtype.dtype
                 if ptr_dtype is not None and dtype != ptr_dtype:
-                    raise TypeError("%s(): mismatching dtype '%s' for pointer type '%s'"
-                                    % (func_name, dtype, ptr))
+                    raise NumbaTypeError("%s(): mismatching dtype '%s' for pointer type '%s'"
+                                         % (func_name, dtype, ptr))
             else:
-                raise TypeError("%s(): invalid dtype spec '%s'"
-                                % (func_name, dtype))
+                raise NumbaTypeError("%s(): invalid dtype spec '%s'"
+                                     % (func_name, dtype))
 
             ndim = parse_shape(shape)
             if ndim is None:
-                raise TypeError("%s(): invalid shape '%s'"
-                                % (func_name, shape))
+                raise NumbaTypeError("%s(): invalid shape '%s'"
+                                     % (func_name, shape))
 
             return types.Array(dtype, ndim, self.layout)
 
diff --git a/numba/core/typing/templates.py b/numba/core/typing/templates.py
index 1e4e9841c07..c85edc1b475 100644
--- a/numba/core/typing/templates.py
+++ b/numba/core/typing/templates.py
@@ -402,6 +402,15 @@ class CallableTemplate(FunctionTemplate):
     def apply(self, args, kws):
         generic = getattr(self, "generic")
         typer = generic()
+        try:
+            match_sig = inspect.signature(typer)
+            match_sig.bind(*args, **kws)
+        except TypeError as e:
+            # ctor or bind failed, raise, if there's a
+            # ValueError then there's likely unrecoverable
+            # problems
+            raise TypingError(str(e)) from e
+
         sig = typer(*args, **kws)
 
         # Unpack optional type if no matching signature
@@ -772,7 +781,17 @@ def _build_impl(self, cache_key, args, kws):
         jitter = self._get_jit_decorator()
 
         # Get the overload implementation for the given types
-        ovf_result = self._overload_func(*args, **kws)
+        try:
+            ov_sig = inspect.signature(self._overload_func)
+            ov_sig.bind(*args, **kws)
+        except TypeError as e:
+            # ctor or bind failed, raise, if there's a
+            # ValueError then there's likely unrecoverable
+            # problems
+            raise TypingError(str(e)) from e
+        else:
+            ovf_result = self._overload_func(*args, **kws)
+
         if ovf_result is None:
             # No implementation => fail typing
             self._impl_cache[cache_key] = None, None
diff --git a/numba/core/typing/typeof.py b/numba/core/typing/typeof.py
index a8f1d133eaf..794d7613cdc 100644
--- a/numba/core/typing/typeof.py
+++ b/numba/core/typing/typeof.py
@@ -235,8 +235,8 @@ def _typeof_dtype(val, c):
 def _typeof_ndarray(val, c):
     try:
         dtype = numpy_support.from_dtype(val.dtype)
-    except NotImplementedError:
-        raise ValueError("Unsupported array dtype: %s" % (val.dtype,))
+    except errors.NumbaNotImplementedError:
+        raise errors.NumbaValueError(f"Unsupported array dtype: {val.dtype}")
     layout = numpy_support.map_layout(val)
     readonly = not val.flags.writeable
     return types.Array(dtype, val.ndim, layout, readonly=readonly)
diff --git a/numba/core/utils.py b/numba/core/utils.py
index ff5a7ab4c4b..bcbd2b3bd36 100644
--- a/numba/core/utils.py
+++ b/numba/core/utils.py
@@ -24,6 +24,7 @@
 
 from numba.core.config import (PYVERSION, MACHINE_BITS, # noqa: F401
                                DEVELOPER_MODE) # noqa: F401
+from numba.core import config
 from numba.core import types
 
 
@@ -180,6 +181,13 @@ def shutting_down(globals=globals):
 atexit.register(_at_shutdown)
 
 
+def use_new_style_errors():
+    """Returns True if new style errors are to be used, false otherwise"""
+    # This uses `config` so as to make sure it gets the current value from the
+    # module as e.g. some tests mutate the config with `override_config`.
+    return config.CAPTURED_ERRORS == 'new_style'
+
+
 class ConfigStack:
     """A stack for tracking target configurations in the compiler.
 
diff --git a/numba/core/withcontexts.py b/numba/core/withcontexts.py
index fc773b391d2..5051dd7c333 100644
--- a/numba/core/withcontexts.py
+++ b/numba/core/withcontexts.py
@@ -441,6 +441,8 @@ def _mutate_with_block_callee(blocks, blk_start, blk_end, inputs, outputs):
     outputs: sequence[str]
         Output variable names
     """
+    if not blocks:
+        raise errors.NumbaValueError("No blocks in with-context block")
     head_blk = min(blocks)
     temp_blk = blocks[head_blk]
     scope = temp_blk.scope
diff --git a/numba/cpython/builtins.py b/numba/cpython/builtins.py
index b10705515be..7703f4e3228 100644
--- a/numba/cpython/builtins.py
+++ b/numba/cpython/builtins.py
@@ -16,7 +16,7 @@
 from numba.core import typing, types, utils, cgutils
 from numba.core.extending import overload, intrinsic
 from numba.core.typeconv import Conversion
-from numba.core.errors import TypingError
+from numba.core.errors import TypingError, LoweringError
 from numba.misc.special import literal_unroll
 
 
@@ -47,7 +47,8 @@ def generic_is(context, builder, sig, args):
     if lhs_type == rhs_type:
             # mutable types
             if lhs_type.mutable:
-                raise NotImplementedError('no default `is` implementation')
+                msg = 'no default `is` implementation'
+                raise LoweringError(msg)
             # immutable types
             else:
                 # fallbacks to `==`
diff --git a/numba/cpython/randomimpl.py b/numba/cpython/randomimpl.py
index f46b8d1bd3a..4f6fc2bbaaa 100644
--- a/numba/cpython/randomimpl.py
+++ b/numba/cpython/randomimpl.py
@@ -19,6 +19,8 @@
 from numba.core import types, utils, cgutils
 from numba.np import arrayobj
 from numba.core.overload_glue import glue_lowering
+from numba.core.errors import NumbaTypeError
+
 
 POST_PY38 = utils.PYVERSION >= (3, 8)
 
@@ -1541,7 +1543,7 @@ def dirichlet_arr(alpha, out):
                 flat[i + k] /= norm
 
     if not isinstance(alpha, (types.Sequence, types.Array)):
-        raise TypeError(
+        raise NumbaTypeError(
             "np.random.dirichlet(): alpha should be an "
             "array or sequence, got %s" % (alpha,)
         )
@@ -1573,7 +1575,7 @@ def dirichlet_impl(alpha, size=None):
             return out
 
     else:
-        raise TypeError(
+        raise NumbaTypeError(
             "np.random.dirichlet(): size should be int or "
             "tuple of ints or None, got %s" % size
         )
diff --git a/numba/cpython/setobj.py b/numba/cpython/setobj.py
index 6caf30025e5..74d6c8ac8d9 100644
--- a/numba/cpython/setobj.py
+++ b/numba/cpython/setobj.py
@@ -18,6 +18,7 @@
 from numba.misc import quicksort
 from numba.cpython import slicing
 from numba.extending import intrinsic
+from numba.core.errors import NumbaValueError
 
 
 def get_payload_struct(context, builder, set_type, ptr):
@@ -205,7 +206,7 @@ def _lookup(self, item, h, for_insert=False):
             if context.data_model_manager[arg].contains_nrt_meminfo():
                 msg = ("Use of reference counted items in 'set()' is "
                        "unsupported, offending type is: '{}'.")
-                raise ValueError(msg.format(arg))
+                raise NumbaValueError(msg.format(arg))
         eqfn = context.get_function(fnty, sig)
 
         one = ir.Constant(intp_t, 1)
diff --git a/numba/cpython/unsafe/numbers.py b/numba/cpython/unsafe/numbers.py
index 8f945c93ccd..81a9e88c69e 100644
--- a/numba/cpython/unsafe/numbers.py
+++ b/numba/cpython/unsafe/numbers.py
@@ -1,6 +1,6 @@
 """ This module provides the unsafe things for targets/numbers.py
 """
-from numba.core import types
+from numba.core import types, errors
 from numba.core.extending import intrinsic
 
 from llvmlite import ir
@@ -29,10 +29,9 @@ def codegen(cgctx, builder, typ, args):
 def trailing_zeros(typeingctx, src):
     """Counts trailing zeros in the binary representation of an integer."""
     if not isinstance(src, types.Integer):
-        raise TypeError(
-            "trailing_zeros is only defined for integers, but passed value was"
-            " '{}'.".format(src)
-        )
+        msg = ("trailing_zeros is only defined for integers, but value passed "
+               f"was '{src}'.")
+        raise errors.NumbaTypeError(msg)
 
     def codegen(context, builder, signature, args):
         [src] = args
@@ -44,10 +43,9 @@ def codegen(context, builder, signature, args):
 def leading_zeros(typeingctx, src):
     """Counts leading zeros in the binary representation of an integer."""
     if not isinstance(src, types.Integer):
-        raise TypeError(
-            "leading_zeros is only defined for integers, but passed value was "
-            "'{}'.".format(src)
-        )
+        msg = ("leading_zeros is only defined for integers, but value passed "
+               f"was '{src}'.")
+        raise errors.NumbaTypeError(msg)
 
     def codegen(context, builder, signature, args):
         [src] = args
diff --git a/numba/cpython/unsafe/tuple.py b/numba/cpython/unsafe/tuple.py
index 7e30b07c1a5..ef243fec5c4 100644
--- a/numba/cpython/unsafe/tuple.py
+++ b/numba/cpython/unsafe/tuple.py
@@ -36,6 +36,9 @@ def codegen(context, builder, signature, args):
 @intrinsic
 def build_full_slice_tuple(tyctx, sz):
     """Creates a sz-tuple of full slices."""
+    if not isinstance(sz, types.IntegerLiteral):
+        raise errors.RequireLiteralValue(sz)
+
     size = int(sz.literal_value)
     tuple_type = types.UniTuple(dtype=types.slice2_type, count=size)
     sig = tuple_type(sz)
diff --git a/numba/cuda/cudadecl.py b/numba/cuda/cudadecl.py
index b8add52c39b..06f5f65f7c0 100644
--- a/numba/cuda/cudadecl.py
+++ b/numba/cuda/cudadecl.py
@@ -1,4 +1,4 @@
-from numba.core import types
+from numba.core import types, errors
 from numba.core.typing.npydecl import (parse_dtype, parse_shape,
                                        register_number_classes)
 from numba.core.typing.templates import (AttributeTemplate, ConcreteTemplate,
@@ -19,6 +19,8 @@
 class GridFunction(CallableTemplate):
     def generic(self):
         def typer(ndim):
+            if not isinstance(ndim, types.IntegerLiteral):
+                raise errors.RequireLiteralValue(ndim)
             val = ndim.literal_value
             if val == 1:
                 restype = types.int32
diff --git a/numba/experimental/jitclass/base.py b/numba/experimental/jitclass/base.py
index 37aadae74b5..0153edc5419 100644
--- a/numba/experimental/jitclass/base.py
+++ b/numba/experimental/jitclass/base.py
@@ -269,7 +269,7 @@ def generic(self, args, kws):
         sig = disp_type.get_call_type(self.context, boundargs, kws)
 
         if not isinstance(sig.return_type, types.NoneType):
-            raise TypeError(
+            raise errors.NumbaTypeError(
                 f"__init__() should return None, not '{sig.return_type}'")
 
         # Actual constructor returns an instance value (not None)
diff --git a/numba/misc/gdb_hook.py b/numba/misc/gdb_hook.py
index 620d819a2dc..e1d39c4f462 100644
--- a/numba/misc/gdb_hook.py
+++ b/numba/misc/gdb_hook.py
@@ -3,7 +3,7 @@
 
 from llvmlite import ir
 
-from numba.core import types, utils, config, cgutils
+from numba.core import types, utils, config, cgutils, errors
 from numba import gdb, gdb_init, gdb_breakpoint
 from numba.core.extending import overload, intrinsic
 
@@ -98,6 +98,8 @@ def init_gdb_codegen(cgctx, builder, signature, args,
     # issue command to continue execution from sleep function
     new_args.extend(['-ex', 'c'])
     # then run the user defined args if any
+    if any([not isinstance(x, types.StringLiteral) for x in const_args]):
+        raise errors.RequireLiteralValue(const_args)
     new_args.extend([x.literal_value for x in const_args])
     cmdlang = [cgctx.insert_const_string(mod, x) for x in new_args]
 
diff --git a/numba/np/arraymath.py b/numba/np/arraymath.py
index bfd81ca19fe..7362db35b8b 100644
--- a/numba/np/arraymath.py
+++ b/numba/np/arraymath.py
@@ -26,7 +26,9 @@
 from numba.np.linalg import ensure_blas
 
 from numba.core.extending import intrinsic
-from numba.core.errors import RequireLiteralValue, TypingError
+from numba.core.errors import (RequireLiteralValue, TypingError,
+                               NumbaValueError, NumbaNotImplementedError,
+                               NumbaTypeError)
 from numba.core.overload_glue import glue_lowering
 from numba.cpython.unsafe.tuple import tuple_setitem
 
@@ -328,7 +330,8 @@ def array_sum_axis(context, builder, sig, args):
         if const_axis_val < 0:
             const_axis_val = ty_array.ndim + const_axis_val
         if const_axis_val < 0 or const_axis_val > ty_array.ndim:
-            raise ValueError("'axis' entry is out of bounds")
+            msg = f"'axis' entry ({const_axis_val}) is out of bounds"
+            raise NumbaValueError(msg)
 
         ty_axis = context.typing_context.resolve_value_type(const_axis_val)
         axis_val = context.get_constant(ty_axis, const_axis_val)
@@ -1935,7 +1938,7 @@ def np_ediff1d(ary, to_end=None, to_begin=None):
 
     if isinstance(ary, types.Array):
         if isinstance(ary.dtype, types.Boolean):
-            raise TypeError("Boolean dtype is unsupported (as per NumPy)")
+            raise NumbaTypeError("Boolean dtype is unsupported (as per NumPy)")
             # Numpy tries to do this: return ary[1:] - ary[:-1] which
             # results in a TypeError exception being raised
 
@@ -1952,11 +1955,11 @@ def np_ediff1d(ary, to_end=None, to_begin=None):
 
         if to_begin_dt is not None and not np.can_cast(to_begin_dt, ary_dt):
             msg = "dtype of to_begin must be compatible with input ary"
-            raise TypeError(msg)
+            raise NumbaTypeError(msg)
 
         if to_end_dt is not None and not np.can_cast(to_end_dt, ary_dt):
             msg = "dtype of to_end must be compatible with input ary"
-            raise TypeError(msg)
+            raise NumbaTypeError(msg)
 
     def np_ediff1d_impl(ary, to_end=None, to_begin=None):
         # transform each input into an equivalent 1d array
@@ -3774,7 +3777,7 @@ def searchsorted(a, v, side='left'):
     elif side_val == 'right':
         loop_impl = _searchsorted_right
     else:
-        raise ValueError("Invalid value given for 'side': %s" % side_val)
+        raise NumbaValueError(f"Invalid value given for 'side': {side_val}")
 
     if isinstance(v, types.Array):
         # N-d array and output
@@ -4053,7 +4056,12 @@ def generate_xinfo(np_func, container, attr):
     @overload(np_func)
     def xinfo_impl(arg):
         nbty = getattr(arg, 'dtype', arg)
-        f = np_func(as_dtype(nbty))
+        np_dtype = as_dtype(nbty)
+        try:
+            f = np_func(np_dtype)
+        except ValueError: # This exception instance comes from NumPy
+            # The np function might not support the dtype
+            return None
         data = tuple([getattr(f, x) for x in attr])
 
         def impl(arg):
@@ -4364,36 +4372,36 @@ def np_select_arr_impl(condlist, choicelist, default=0):
 
     # first we check the types of the input parameters
     if not isinstance(condlist, (types.List, types.UniTuple)):
-        raise TypeError('condlist must be a List or a Tuple')
+        raise NumbaTypeError('condlist must be a List or a Tuple')
     if not isinstance(choicelist, (types.List, types.UniTuple)):
-        raise TypeError('choicelist must be a List or a Tuple')
+        raise NumbaTypeError('choicelist must be a List or a Tuple')
     if not isinstance(default, (int, types.Number, types.Boolean)):
-        raise TypeError('default must be a scalar (number or boolean)')
+        raise NumbaTypeError('default must be a scalar (number or boolean)')
     # the types of the parameters have been checked, now we test the types
     # of the content of the parameters
     # implementation note: if in the future numba's np.where accepts tuples
     # as elements of condlist, then the check below should be extended to
     # accept tuples
     if not isinstance(condlist[0], types.Array):
-        raise TypeError('items of condlist must be arrays')
+        raise NumbaTypeError('items of condlist must be arrays')
     if not isinstance(choicelist[0], types.Array):
-        raise TypeError('items of choicelist must be arrays')
+        raise NumbaTypeError('items of choicelist must be arrays')
     # the types of the parameters and their contents have been checked,
     # now we test the dtypes of the content of parameters
     if isinstance(condlist[0], types.Array):
         if not isinstance(condlist[0].dtype, types.Boolean):
-            raise TypeError('condlist arrays must contain booleans')
+            raise NumbaTypeError('condlist arrays must contain booleans')
     if isinstance(condlist[0], types.UniTuple):
         if not (isinstance(condlist[0], types.UniTuple)
                 and isinstance(condlist[0][0], types.Boolean)):
-            raise TypeError('condlist tuples must only contain booleans')
+            raise NumbaTypeError('condlist tuples must only contain booleans')
     # the input types are correct, now we perform checks on the dimensions
     if (isinstance(condlist[0], types.Array) and
             condlist[0].ndim != choicelist[0].ndim):
-        raise TypeError('condlist and choicelist elements must have the '
-                        'same number of dimensions')
+        raise NumbaTypeError('condlist and choicelist elements must have the '
+                             'same number of dimensions')
     if isinstance(condlist[0], types.Array) and condlist[0].ndim < 1:
-        raise TypeError('condlist arrays must be of at least dimension 1')
+        raise NumbaTypeError('condlist arrays must be of at least dimension 1')
 
     return np_select_arr_impl
 
@@ -4410,7 +4418,7 @@ def np_asarray_chkfinite(a, dtype=None):
     else:
         try:
             dt = as_dtype(dtype)
-        except NotImplementedError:
+        except NumbaNotImplementedError:
             raise TypingError('dtype must be a valid Numpy dtype')
 
     def impl(a, dtype=None):
diff --git a/numba/np/arrayobj.py b/numba/np/arrayobj.py
index bbd14894f36..c2c8f41b3c0 100644
--- a/numba/np/arrayobj.py
+++ b/numba/np/arrayobj.py
@@ -1667,7 +1667,7 @@ def numpy_rot90(arr, k=1):
         raise errors.TypingError('The first argument "arr" must be an array')
 
     if arr.ndim < 2:
-        raise ValueError('Input must be >= 2-d.')
+        raise errors.NumbaValueError('Input must be >= 2-d.')
 
     def impl(arr, k=1):
         k = k % 4
@@ -4074,7 +4074,10 @@ def _arange_dtype(*args):
         # were wrapped in NumPy int*() calls it's not possible to detect the
         # difference between `np.arange(10)` and `np.arange(np.int64(10)`.
         NPY_TY = getattr(types, "int%s" % (8 * np.dtype(int).itemsize))
-        dtype = max(bounds + [NPY_TY,])
+
+        # unliteral these types such that `max` works.
+        unliteral_bounds = [types.unliteral(x) for x in bounds]
+        dtype = max(unliteral_bounds + [NPY_TY,])
 
     return dtype
 
@@ -5170,6 +5173,8 @@ def impl(a):
 def _build_flip_slice_tuple(tyctx, sz):
     """ Creates a tuple of slices for np.flip indexing like
     `(slice(None, None, -1),) * sz` """
+    if not isinstance(sz, types.IntegerLiteral):
+        raise errors.RequireLiteralValue(sz)
     size = int(sz.literal_value)
     tuple_type = types.UniTuple(dtype=types.slice3_type, count=size)
     sig = tuple_type(sz)
@@ -5619,15 +5624,13 @@ def take_along_axis_impl(arr, indices, axis):
     else:
         check_is_integer(axis, "axis")
         if not isinstance(axis, types.IntegerLiteral):
-            raise ValueError(
-                "axis must be a literal value (i.e. not an argument) for now"
-            )
+            raise errors.NumbaValueError("axis must be a literal value")
         axis = axis.literal_value
         if axis < 0:
             axis = arr.ndim + axis
 
         if axis < 0 or axis >= arr.ndim:
-            raise ValueError("axis is out of bounds")
+            raise errors.NumbaValueError("axis is out of bounds")
 
         Ni = tuple(range(axis))
         Nk = tuple(range(axis + 1, arr.ndim))
diff --git a/numba/np/linalg.py b/numba/np/linalg.py
index cf618d7a783..3b224b83a4c 100644
--- a/numba/np/linalg.py
+++ b/numba/np/linalg.py
@@ -15,7 +15,7 @@
 from numba.core.typing import signature
 from numba.core.extending import overload, register_jitable
 from numba.core import types, cgutils
-from numba.core.errors import TypingError
+from numba.core.errors import TypingError, NumbaTypeError
 from .arrayobj import make_array, _empty_nd_impl, array_copy
 from numba.np import numpy_support as np_support
 from numba.core.overload_glue import glue_lowering
@@ -2493,7 +2493,7 @@ def matrix_power_impl(a, n):
 
     nt = getattr(n, 'dtype', n)
     if not isinstance(nt, types.Integer):
-        raise TypeError("Exponent must be an integer.")
+        raise NumbaTypeError("Exponent must be an integer.")
 
     def matrix_power_impl(a, n):
 
@@ -2572,7 +2572,7 @@ def matrix_trace_impl(a, offset=0):
     _check_linalg_matrix(a, "trace", la_prefix=False)
 
     if not isinstance(offset, (int, types.Integer)):
-        raise TypeError("integer argument expected, got %s" % offset)
+        raise NumbaTypeError("integer argument expected, got %s" % offset)
 
     def matrix_trace_impl(a, offset=0):
         rows, cols = a.shape
diff --git a/numba/np/npdatetime.py b/numba/np/npdatetime.py
index 68f0e2bdf64..05c4c440324 100644
--- a/numba/np/npdatetime.py
+++ b/numba/np/npdatetime.py
@@ -14,6 +14,7 @@
 from numba.np import npdatetime_helpers, numpy_support, npyfuncs
 from numba.extending import overload_method
 from numba.core.config import IS_32BITS
+from numba.core.errors import LoweringError
 
 # datetime64 and timedelta64 use the same internal representation
 DATETIME64 = TIMEDELTA64 = Type.int(64)
@@ -52,8 +53,8 @@ def scale_timedelta(context, builder, val, srcty, destty):
         srcty.unit, destty.unit)
     if factor is None:
         # This can happen when using explicit output in a ufunc.
-        raise NotImplementedError("cannot convert timedelta64 from %r to %r"
-                                  % (srcty.unit, destty.unit))
+        msg = f"cannot convert timedelta64 from {srcty.unit} to {destty.unit}"
+        raise LoweringError(msg)
     return scale_by_constant(builder, val, factor)
 
 
@@ -575,8 +576,8 @@ def convert_datetime_for_arith(builder, dt_val, src_unit, dest_unit):
     dt_factor = npdatetime_helpers.get_timedelta_conversion_factor(dt_unit, dest_unit)
     if dt_factor is None:
         # This can happen when using explicit output in a ufunc.
-        raise NotImplementedError("cannot convert datetime64 from %r to %r"
-                                  % (src_unit, dest_unit))
+        raise LoweringError("cannot convert datetime64 from %r to %r"
+                                       % (src_unit, dest_unit))
     return scale_by_constant(builder, dt_val, dt_factor)
 
 
diff --git a/numba/np/npyimpl.py b/numba/np/npyimpl.py
index 8ba2e5014cf..52fc504eafc 100644
--- a/numba/np/npyimpl.py
+++ b/numba/np/npyimpl.py
@@ -610,4 +610,4 @@ def imp(desc):
             return _make_dtype_object(desc)
         return imp
     else:
-        raise TypeError('unknown dtype descriptor: {}'.format(desc))
+        raise errors.NumbaTypeError('unknown dtype descriptor: {}'.format(desc))
diff --git a/numba/np/numpy_support.py b/numba/np/numpy_support.py
index a1267949407..7e516d68c75 100644
--- a/numba/np/numpy_support.py
+++ b/numba/np/numpy_support.py
@@ -110,7 +110,7 @@ def from_dtype(dtype):
             subtype = from_dtype(dtype.subdtype[0])
             return types.NestedArray(subtype, dtype.shape)
 
-    raise NotImplementedError(dtype)
+    raise errors.NumbaNotImplementedError(dtype)
 
 
 _as_dtype_letters = {
@@ -153,8 +153,9 @@ def as_dtype(nbtype):
         return np.dtype(spec)
     if isinstance(nbtype, types.PyObject):
         return np.dtype(object)
-    raise NotImplementedError("%r cannot be represented as a Numpy dtype"
-                              % (nbtype,))
+
+    msg = f"{nbtype} cannot be represented as a NumPy dtype"
+    raise errors.NumbaNotImplementedError(msg)
 
 
 def as_struct_dtype(rec):
@@ -370,11 +371,11 @@ def ufunc_find_matching_loop(ufunc, arg_types):
 
     try:
         np_input_types = [as_dtype(x) for x in input_types]
-    except NotImplementedError:
+    except errors.NumbaNotImplementedError:
         return None
     try:
         np_output_types = [as_dtype(x) for x in output_types]
-    except NotImplementedError:
+    except errors.NumbaNotImplementedError:
         return None
 
     # Whether the inputs are mixed integer / floating-point
@@ -492,7 +493,7 @@ def make_specific(outputs, unit):
                 if ufunc_inputs[0] == 'm':
                     outputs = set_output_dt_units(inputs, outputs, ufunc_inputs)
 
-            except NotImplementedError:
+            except errors.NumbaNotImplementedError:
                 # One of the selected dtypes isn't supported by Numba
                 # (e.g. float16), try other candidates
                 continue
diff --git a/numba/parfors/parfor.py b/numba/parfors/parfor.py
index a627d736379..391080ebd45 100644
--- a/numba/parfors/parfor.py
+++ b/numba/parfors/parfor.py
@@ -4662,7 +4662,8 @@ def dummy_return_in_loop_body(loop_body):
 class ReduceInfer(AbstractTemplate):
     def generic(self, args, kws):
         assert not kws
-        assert len(args) == 3
+        if len(args) != 3:
+            raise errors.NumbaAssertionError("len(args) != 3")
         assert isinstance(args[1], types.Array)
         return signature(args[1].dtype, *args)
 
diff --git a/numba/stencils/stencil.py b/numba/stencils/stencil.py
index d8e98928588..4c30bd2dd03 100644
--- a/numba/stencils/stencil.py
+++ b/numba/stencils/stencil.py
@@ -13,6 +13,7 @@
                                          infer_global, AbstractTemplate)
 from numba.core.imputils import lower_builtin
 from numba.core.extending import register_jitable
+from numba.core.errors import NumbaValueError
 from numba.misc.special import literal_unroll
 import numba
 
@@ -200,7 +201,7 @@ def add_indices_to_kernel(self, kernel, index_names, ndim,
                         elif stmt_index_var.name in const_dict:
                             kernel_consts += [const_dict[stmt_index_var.name]]
                         else:
-                            raise ValueError("stencil kernel index is not "
+                            raise NumbaValueError("stencil kernel index is not "
                                 "constant, 'neighborhood' option required")
 
                     if ndim == 1:
@@ -303,8 +304,8 @@ def add_indices_to_kernel(self, kernel, index_names, ndim,
             # index used in the kernel specification.
             neighborhood = [[0,0] for _ in range(ndim)]
             if len(kernel_consts) == 0:
-                raise ValueError("Stencil kernel with no accesses to "
-                                 "relatively indexed arrays.")
+                raise NumbaValueError("Stencil kernel with no accesses to "
+                                      "relatively indexed arrays.")
 
             for index in kernel_consts:
                 if isinstance(index, tuple) or isinstance(index, list):
@@ -316,7 +317,7 @@ def add_indices_to_kernel(self, kernel, index_names, ndim,
                             neighborhood[i][0] = min(neighborhood[i][0], te)
                             neighborhood[i][1] = max(neighborhood[i][1], te)
                         else:
-                            raise ValueError(
+                            raise NumbaValueError(
                                 "stencil kernel index is not constant,"
                                 "'neighborhood' option required")
                     index_len = len(index)
@@ -325,10 +326,10 @@ def add_indices_to_kernel(self, kernel, index_names, ndim,
                     neighborhood[0][1] = max(neighborhood[0][1], index)
                     index_len = 1
                 else:
-                    raise ValueError(
+                    raise NumbaValueError(
                         "Non-tuple or non-integer used as stencil index.")
                 if index_len != ndim:
-                    raise ValueError(
+                    raise NumbaValueError(
                         "Stencil index does not match array dimensionality.")
 
         return (neighborhood, relatively_indexed)
@@ -340,8 +341,8 @@ def get_return_type(self, argtys):
             ir_utils.dump_blocks(self.kernel_ir.blocks)
 
         if not isinstance(argtys[0], types.npytypes.Array):
-            raise ValueError("The first argument to a stencil kernel must "
-                             "be the primary input array.")
+            raise NumbaValueError("The first argument to a stencil kernel must "
+                                  "be the primary input array.")
 
         from numba.core import typed_passes
         typemap, return_type, calltypes, _ = typed_passes.type_inference_stage(
@@ -352,7 +353,7 @@ def get_return_type(self, argtys):
                 None,
                 {})
         if isinstance(return_type, types.npytypes.Array):
-            raise ValueError(
+            raise NumbaValueError(
                 "Stencil kernel must return a scalar and not a numpy array.")
 
         real_ret = types.npytypes.Array(return_type, argtys[0].ndim,
@@ -384,9 +385,9 @@ def _type_me(self, argtys, kwtys):
         """
         if (self.neighborhood is not None and
             len(self.neighborhood) != argtys[0].ndim):
-            raise ValueError("%d dimensional neighborhood specified "
-                             "for %d dimensional input array" %
-                             (len(self.neighborhood), argtys[0].ndim))
+            raise NumbaValueError("%d dimensional neighborhood specified "
+                                  "for %d dimensional input array" %
+                                  (len(self.neighborhood), argtys[0].ndim))
 
         argtys_extra = argtys
         sig_extra = ""
@@ -478,7 +479,7 @@ def _stencil_wrapper(self, result, sigret, return_type, typemap, calltypes, *arg
             copy_calltypes)
 
         if "out" in name_var_table:
-            raise ValueError("Cannot use the reserved word 'out' in stencil kernels.")
+            raise NumbaValueError("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:
@@ -520,12 +521,12 @@ def _stencil_wrapper(self, result, sigret, return_type, typemap, calltypes, *arg
         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.")
+            raise NumbaValueError("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.")
+            raise NumbaValueError("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
@@ -604,8 +605,8 @@ def cval_as_str(cval):
             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.")
+                    msg = "cval type does not match stencil return type."
+                    raise NumbaValueError(msg)
                 out_init ="{} = np.full({}, {}, dtype=np.{})\n".format(
                             out_name, shape_name, cval_as_str(cval),
                             return_type_name)
@@ -619,7 +620,7 @@ def cval_as_str(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)
+                    raise NumbaValueError(msg)
                 out_init = "{}[:] = {}\n".format(out_name, cval_as_str(cval))
                 func_text += "    " + out_init
 
diff --git a/numba/stencils/stencilparfor.py b/numba/stencils/stencilparfor.py
index f1f2c5b0184..d5d045f087b 100644
--- a/numba/stencils/stencilparfor.py
+++ b/numba/stencils/stencilparfor.py
@@ -19,6 +19,7 @@
 from numba.core.ir_utils import (get_call_table, mk_unique_var,
                             compile_to_numba_ir, replace_arg_nodes, guard,
                             find_callname, require, find_const, GuardException)
+from numba.core.errors import NumbaValueError
 from numba.core.utils import OPERATORS_TO_BUILTINS
 
 
@@ -278,7 +279,7 @@ def _mk_stencil_parfor(self, label, in_args, out_arr, stencil_ir,
                 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)
+                    raise NumbaValueError(msg)
 
                 # get slice ref
                 slice_var = ir.Var(scope, mk_unique_var("$py_g_var"), loc)
diff --git a/numba/tests/test_array_methods.py b/numba/tests/test_array_methods.py
index 4fde879ffb1..c60dee224a7 100644
--- a/numba/tests/test_array_methods.py
+++ b/numba/tests/test_array_methods.py
@@ -8,7 +8,7 @@
 from numba import jit, typeof
 from numba.core import types
 from numba.core.compiler import compile_isolated
-from numba.core.errors import TypingError, LoweringError
+from numba.core.errors import TypingError, LoweringError, NumbaValueError
 from numba.np.numpy_support import as_dtype, numpy_version
 from numba.tests.support import (TestCase, CompilationCache, MemoryLeak,
                                  MemoryLeakMixin, tag, needs_blas)
@@ -1223,9 +1223,9 @@ def foo(arr):
         self.assertPreciseEqual(foo(a), foo.py_func(a))
         # ndim == 2, axis == -3, BAD
         a = np.ones((1, 2))
-        with self.assertRaises(LoweringError) as raises:
+        with self.assertRaises(NumbaValueError) as raises:
             foo(a)
-        errmsg = "'axis' entry is out of bounds"
+        errmsg = "'axis' entry (-1) is out of bounds"
         self.assertIn(errmsg, str(raises.exception))
         with self.assertRaises(ValueError) as raises:
             foo.py_func(a)
diff --git a/numba/tests/test_errorhandling.py b/numba/tests/test_errorhandling.py
index cc12042ef7b..21b2543b4d9 100644
--- a/numba/tests/test_errorhandling.py
+++ b/numba/tests/test_errorhandling.py
@@ -20,7 +20,7 @@
 from numba.core.types.functions import _err_reasons as error_reasons
 
 from numba.tests.support import (skip_parfors_unsupported, override_config,
-                                 SerialMixin)
+                                 SerialMixin, skip_unless_scipy)
 import unittest
 
 # used in TestMiscErrorHandling::test_handling_of_write_to_*_global
@@ -214,7 +214,7 @@ def foo():
 
         @overload(foo)
         def ol_foo():
-            raise ValueError(given_reason)
+            raise errors.NumbaValueError(given_reason)
 
         @njit
         def call_foo():
@@ -246,6 +246,7 @@ def call_foo():
         excstr = str(raises.exception)
         self.assertIn("No match", excstr)
 
+    @skip_unless_scipy
     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
@@ -342,7 +343,7 @@ def myintrin(typingctx, x, arr):
                 raise errors.RequireLiteralValue(given_reason1)
 
             if arr.ndim != 1:
-                raise ValueError(given_reason2)
+                raise errors.NumbaValueError(given_reason2)
 
             sig = types.intp(x, arr)
 
@@ -444,7 +445,7 @@ def __new__(cls, pos, mass):
             Particle(pos=1, mass=2)
 
         excstr = str(raises.exception)
-        self.assertIn("required positional argument: 'mass'", excstr)
+        self.assertIn("missing a required argument: 'mass'", excstr)
 
 
 class TestDeveloperSpecificErrorMessages(SerialMixin, unittest.TestCase):
@@ -459,9 +460,36 @@ def foo(x):
                 njit("void(int64[:,:])")(foo)
 
         excstr = str(raises.exception)
-        self.assertIn("AssertionError()", excstr)
-        self.assertIn("BoundFunction(array.max for array(int64, 2d, A))",
-                      excstr)
+        self.assertIn("args not supported", excstr)
+
+
+class TestCapturedErrorHandling(SerialMixin, unittest.TestCase):
+    """Checks that the way errors are captured changes depending on the env
+    var "NUMBA_CAPTURED_ERRORS".
+    """
+
+    def test_error_in_overload(self):
+
+        def bar(x):
+            pass
+
+        @overload(bar)
+        def ol_bar(x):
+            x.some_invalid_attr # doesn't exist!
+
+            def impl(x):
+                pass
+            return impl
+
+        for style, err_class in (('new_style', AttributeError),
+                                 ('old_style', errors.TypingError)):
+            with override_config('CAPTURED_ERRORS', style):
+                with self.assertRaises(err_class) as raises:
+                    @njit('void(int64)')
+                    def foo(x):
+                        bar(x)
+                expected = "object has no attribute 'some_invalid_attr'"
+                self.assertIn(expected, str(raises.exception))
 
 
 if __name__ == '__main__':
diff --git a/numba/tests/test_extending_types.py b/numba/tests/test_extending_types.py
index 81c3b32d773..540472e37a0 100644
--- a/numba/tests/test_extending_types.py
+++ b/numba/tests/test_extending_types.py
@@ -5,7 +5,7 @@
 
 from numba import njit, literally
 from numba.core import types, cgutils
-from numba.core.errors import TypingError
+from numba.core.errors import TypingError, NumbaTypeError
 from numba.core.extending import lower_builtin
 from numba.core.extending import models, register_model
 from numba.core.extending import make_attribute_wrapper
@@ -16,6 +16,14 @@
 import unittest
 
 
+def gen_mock_float():
+    # Stub to overload, pretending to be `float`. The real `float` function is
+    # not used as multiple registrations can collide.
+    def mock_float(x):
+        pass
+    return mock_float
+
+
 class TestExtTypDummy(unittest.TestCase):
 
     def setUp(self):
@@ -61,39 +69,39 @@ def typeof_dummy(val, c):
         self.Dummy = Dummy
         self.DummyType = DummyType
 
-    def _add_float_overload(self):
-        @overload(float)
+    def _add_float_overload(self, mock_float_inst):
+        @overload(mock_float_inst)
         def dummy_to_float(x):
             if isinstance(x, self.DummyType):
                 def codegen(x):
                     return float(x.value)
                 return codegen
             else:
-                raise TypeError('cannot type float({})'.format(x))
+                raise NumbaTypeError('cannot type float({})'.format(x))
 
     def test_overload_float(self):
-        self._add_float_overload()
+        mock_float = gen_mock_float()
+        self._add_float_overload(mock_float)
         Dummy = self.Dummy
 
         @njit
         def foo(x):
-            return float(Dummy(x))
+            return mock_float(Dummy(x))
 
         self.assertEqual(foo(123), float(123))
 
     def test_overload_float_error_msg(self):
-        self._add_float_overload()
+        mock_float = gen_mock_float()
+        self._add_float_overload(mock_float)
 
         @njit
         def foo(x):
-            return float(x)
+            return mock_float(x)
 
         with self.assertRaises(TypingError) as raises:
             foo(1j)
 
-        self.assertIn("TypeError: float() does not support complex",
-                      str(raises.exception))
-        self.assertIn("TypeError: cannot type float(complex128)",
+        self.assertIn("cannot type float(complex128)",
                       str(raises.exception))
 
     def test_unboxing(self):
diff --git a/numba/tests/test_function_type.py b/numba/tests/test_function_type.py
index 34e9a85b671..d4b74d2eece 100644
--- a/numba/tests/test_function_type.py
+++ b/numba/tests/test_function_type.py
@@ -1068,7 +1068,9 @@ def bar_good(foos):
 
         self.assertEqual(bar_good((foo, )), 2)
 
-        with self.assertRaises(errors.TypingError) as cm:
+        # new/old style error handling
+        with self.assertRaises((errors.UnsupportedError,
+                                errors.TypingError)) as cm:
             bar_bad((foo, ))
 
         self.assertRegex(
diff --git a/numba/tests/test_literal_dispatch.py b/numba/tests/test_literal_dispatch.py
index 6bc32a22218..6d759f06e70 100644
--- a/numba/tests/test_literal_dispatch.py
+++ b/numba/tests/test_literal_dispatch.py
@@ -311,7 +311,7 @@ def ov_do_this(x, y):
             if isinstance(y, types.IntegerLiteral):
                 # This error is however suppressed because a non-literal
                 # version is valid.
-                raise ValueError("oops")
+                raise errors.NumbaValueError("oops")
             else:
                 def impl(x, y):
                     return hidden(x, y)
diff --git a/numba/tests/test_ndarray_subclasses.py b/numba/tests/test_ndarray_subclasses.py
index dce75b18f86..7644bef9c26 100644
--- a/numba/tests/test_ndarray_subclasses.py
+++ b/numba/tests/test_ndarray_subclasses.py
@@ -292,10 +292,11 @@ def foo(buf):
         buf = np.arange(4, dtype=np.float32)
         with self.assertRaises(TypingError) as raises:
             foo(buf)
-        self.assertIn(
-            "unsupported use of ufunc <ufunc 'add'> on MyArray(1, float32, C)",
-            str(raises.exception),
-        )
+
+        msg = ("No implementation of function",
+               "add(MyArray(1, float32, C), MyArray(1, float32, C))")
+        for m in msg:
+            self.assertIn(m, str(raises.exception))
 
     @use_logger
     def test_myarray_allocator_override(self):
diff --git a/numba/tests/test_numpy_support.py b/numba/tests/test_numpy_support.py
index 5fa6e24850f..b0adf9434c5 100644
--- a/numba/tests/test_numpy_support.py
+++ b/numba/tests/test_numpy_support.py
@@ -10,6 +10,7 @@
 
 import unittest
 from numba.core import types
+from numba.core.errors import NumbaNotImplementedError
 from numba.tests.support import TestCase
 from numba.tests.enum_usecases import Shake, RequestError
 from numba.np import numpy_support
@@ -67,7 +68,7 @@ def check(typechar, numba_type):
         # Non-native alignments are unsupported (except for 1-byte types)
         foreign_align = '>' if sys.byteorder == 'little' else '<'
         for letter in 'hHiIlLqQfdFD':
-            self.assertRaises(NotImplementedError, f,
+            self.assertRaises(NumbaNotImplementedError, f,
                               np.dtype(foreign_align + letter))
 
     def test_string_types(self):
diff --git a/numba/tests/test_parfors.py b/numba/tests/test_parfors.py
index d9585173034..8df8f4ed2dd 100644
--- a/numba/tests/test_parfors.py
+++ b/numba/tests/test_parfors.py
@@ -2392,6 +2392,8 @@ def test_no_state_change_in_gufunc_lowering_on_error(self):
         # sequential_parfor_lowering global variable should remain as False on
         # stack unwind.
 
+        BROKEN_MSG = 'BROKEN_MSG'
+
         @register_pass(mutates_CFG=True, analysis_only=False)
         class BreakParfors(AnalysisPass):
             _name = "break_parfors"
@@ -2409,7 +2411,12 @@ def run_pass(self, state):
                             # point it needs to be a set so e.g. set.difference
                             # can be computed, this therefore creates an error
                             # in the right location.
-                            stmt.races = []
+                            class Broken(list):
+
+                                def difference(self, other):
+                                    raise errors.LoweringError(BROKEN_MSG)
+
+                            stmt.races = Broken()
                     return True
 
 
@@ -2435,8 +2442,7 @@ def foo():
         with self.assertRaises(errors.LoweringError) as raises:
             foo()
 
-        self.assertIn("'list' object has no attribute 'difference'",
-                      str(raises.exception))
+        self.assertIn(BROKEN_MSG, str(raises.exception))
 
         # assert state has not changed
         self.assertFalse(numba.parfors.parfor.sequential_parfor_lowering)
diff --git a/numba/tests/test_return_values.py b/numba/tests/test_return_values.py
index a754dafa3df..d8d32fdaff0 100644
--- a/numba/tests/test_return_values.py
+++ b/numba/tests/test_return_values.py
@@ -8,7 +8,7 @@
 import unittest
 from numba.core.compiler import compile_isolated, Flags
 from numba.core import types
-from numba.core.errors import TypingError
+from numba.core.errors import TypingError, NumbaTypeError
 
 
 enable_pyobj_flags = Flags()
@@ -40,7 +40,7 @@ def test_nopython_func(self, flags=enable_pyobj_flags):
             result = cfunc()
 
     def test_nopython_func_npm(self):
-        with self.assertRaises(TypeError):
+        with self.assertRaises(NumbaTypeError):
             self.test_nopython_func(flags=no_pyobj_flags)
 
     def test_pyobj_func(self, flags=enable_pyobj_flags):
@@ -70,7 +70,7 @@ def test_module_func(self, flags=enable_pyobj_flags):
             result = cfunc()
 
     def test_module_func_npm(self):
-        with self.assertRaises(TypeError):
+        with self.assertRaises(NumbaTypeError):
             self.test_module_func(flags=no_pyobj_flags)
 
 
diff --git a/numba/tests/test_sets.py b/numba/tests/test_sets.py
index f1c20517082..5084ca62715 100644
--- a/numba/tests/test_sets.py
+++ b/numba/tests/test_sets.py
@@ -432,7 +432,7 @@ def test_refcounted_types_forbidden(self):
 
         pyfunc = constructor_usecase
         cfunc = jit(nopython=True)(pyfunc)
-        with self.assertRaises(errors.LoweringError) as raises:
+        with self.assertRaises(errors.NumbaValueError) as raises:
             cfunc("abc")
 
         excstr = str(raises.exception)
diff --git a/numba/tests/test_stencils.py b/numba/tests/test_stencils.py
index 77d8f6be604..8ed0520d4ef 100644
--- a/numba/tests/test_stencils.py
+++ b/numba/tests/test_stencils.py
@@ -20,7 +20,7 @@
 from numba.core.compiler import compile_extra, Flags
 from numba.core.cpu import ParallelOptions
 from numba.tests.support import tag, skip_parfors_unsupported, _32bit
-from numba.core.errors import LoweringError, TypingError
+from numba.core.errors import LoweringError, TypingError, NumbaValueError
 import unittest
 
 
@@ -509,14 +509,14 @@ def test_impl_seq(n):
         np.testing.assert_almost_equal(parfor_output4, expected, decimal=3)
 
         # check error in regular Python path
-        with self.assertRaises(ValueError) as e:
+        with self.assertRaises(NumbaValueError) as e:
             test_impl4(4)
 
         self.assertIn("stencil kernel index is not constant, "
                       "'neighborhood' option required", str(e.exception))
         # check error in njit path
         # TODO: ValueError should be thrown instead of LoweringError
-        with self.assertRaises(LoweringError) as e:
+        with self.assertRaises((LoweringError, NumbaValueError)) as e:
             njit(test_impl4)(4)
 
         self.assertIn("stencil kernel index is not constant, "
@@ -598,7 +598,7 @@ def wrapped():
 
         A = np.arange(12).reshape((3, 4))
         ret = np.ones_like(A)
-        with self.assertRaises(ValueError) as e:
+        with self.assertRaises(NumbaValueError) as e:
             stencil_fn(A, out=ret)
         msg = "cval type does not match stencil return type."
         self.assertIn(msg, str(e.exception))
@@ -606,7 +606,7 @@ def wrapped():
         for compiler in [self.compile_njit, self.compile_parallel]:
             try:
                 compiler(wrapped,())
-            except(ValueError, LoweringError) as e:
+            except(NumbaValueError, LoweringError) as e:
                 self.assertIn(msg, str(e))
             else:
                 raise AssertionError("Expected error was not raised")
@@ -1968,7 +1968,7 @@ def test_basic25(self):
 
         def kernel(a):
             return 1.
-        self.check(kernel, a, expected_exception=[ValueError, LoweringError])
+        self.check(kernel, a, expected_exception=[ValueError, NumbaValueError])
 
     def test_basic26(self):
         """3d arr"""
@@ -2000,7 +2000,8 @@ def test_basic29(self):
 
         def kernel(a):
             return a[0, int(np.cos(0))]
-        self.check(kernel, a, expected_exception=[ValueError, LoweringError])
+        self.check(kernel, a, expected_exception=[ValueError, NumbaValueError,
+                                                  LoweringError])
 
     def test_basic30(self):
         """signed zeros"""
@@ -2045,7 +2046,8 @@ def test_basic32(self):
 
         def kernel(a):
             return a[np.int8(1), 0]
-        self.check(kernel, a, expected_exception=[ValueError, LoweringError])
+        self.check(kernel, a, expected_exception=[ValueError, NumbaValueError,
+                                                  LoweringError])
 
     def test_basic33(self):
         """add 0d array"""
@@ -2069,9 +2071,9 @@ def kernel(a):
             return a[0, 1]
         a = np.arange(12.).reshape(3, 4)
         ex = self.exception_dict(
-            stencil=ValueError,
+            stencil=NumbaValueError,
             parfor=ValueError,
-            njit=LoweringError)
+            njit=NumbaValueError)
         self.check(kernel, a, options={'cval': 5}, expected_exception=ex)
 
     def test_basic36(self):
@@ -2094,9 +2096,9 @@ def kernel(a):
             return a[0, 1] + a[0, -1] + a[1, -1] + a[1, -1]
         a = np.arange(12.).reshape(3, 4)
         ex = self.exception_dict(
-            stencil=ValueError,
+            stencil=NumbaValueError,
             parfor=ValueError,
-            njit=LoweringError)
+            njit=NumbaValueError)
         self.check(kernel, a, options={'cval': 1.j}, expected_exception=ex)
 
     def test_basic39(self):
@@ -2228,7 +2230,7 @@ def kernel(a, b):
             kernel, a, b, options={
                 'standard_indexing': [
                     'a', 'b']}, expected_exception=[
-                ValueError, LoweringError])
+                ValueError, NumbaValueError])
 
     def test_basic52(self):
         """3 args, standard_indexing on middle arg """
@@ -2369,9 +2371,9 @@ def kernel(a, b):
         b = np.arange(12).reshape(3, 4)
         ex = self.exception_dict(
             pyStencil=ValueError,
-            stencil=ValueError,
+            stencil=NumbaValueError,
             parfor=ValueError,
-            njit=LoweringError)
+            njit=NumbaValueError)
         self.check(
             kernel,
             a,
@@ -2393,7 +2395,7 @@ def kernel(a):
                 'standard_indexing': 'a'},
             expected_exception=[
                 ValueError,
-                LoweringError])
+                NumbaValueError])
 
     def test_basic65(self):
         """basic induced neighborhood test"""
@@ -2722,9 +2724,9 @@ def kernel(a, out):
         a = np.arange(12.).reshape(3, 4)
         ex = self.exception_dict(
             pyStencil=ValueError,
-            stencil=ValueError,
+            stencil=NumbaValueError,
             parfor=ValueError,
-            njit=LoweringError)
+            njit=NumbaValueError)
         self.check(
             kernel,
             a,
diff --git a/numba/tests/test_target_extension.py b/numba/tests/test_target_extension.py
index 1c0cd2a3feb..a1b5c7ecd43 100644
--- a/numba/tests/test_target_extension.py
+++ b/numba/tests/test_target_extension.py
@@ -529,7 +529,10 @@ def ol_my_func_cuda(x):
         def dpu_foo():
             my_func(1)
 
-        with self.assertRaises(errors.TypingError) as raises:
+        # new style errors raise UnsupportedError, old style ends up as
+        # TypingError
+        accept = (errors.UnsupportedError, errors.TypingError)
+        with self.assertRaises(accept) as raises:
             dpu_foo()
 
         msgs = ["Function resolution cannot find any matches for function",
@@ -542,7 +545,7 @@ def dpu_foo():
 
     def test_invalid_target_jit(self):
 
-        with self.assertRaises(ValueError) as raises:
+        with self.assertRaises(errors.NumbaValueError) as raises:
             @njit(_target='invalid_silicon')
             def foo():
                 pass
@@ -626,7 +629,8 @@ def cpu_foo_generic():
         def cpu_foo_dpu():
             return intrin_math_dpu(3, 4)
 
-        with self.assertRaises(errors.TypingError) as raises:
+        accept = (errors.UnsupportedError, errors.TypingError)
+        with self.assertRaises(accept) as raises:
             cpu_foo_dpu()
 
         msgs = ["Function resolution cannot find any matches for function",
@@ -654,7 +658,8 @@ def dpu_foo_generic():
         def dpu_foo_cpu():
             return intrin_math_cpu(3, 4)
 
-        with self.assertRaises(errors.TypingError) as raises:
+        accept = (errors.UnsupportedError, errors.TypingError)
+        with self.assertRaises(accept) as raises:
             dpu_foo_cpu()
 
         msgs = ["Function resolution cannot find any matches for function",
diff --git a/numba/tests/test_target_overloadselector.py b/numba/tests/test_target_overloadselector.py
index 76acf30d79a..8fa54bccc65 100644
--- a/numba/tests/test_target_overloadselector.py
+++ b/numba/tests/test_target_overloadselector.py
@@ -6,6 +6,7 @@
 from numba.core.registry import cpu_target
 from numba.core.imputils import builtin_registry, RegistryLoader
 from numba.core import types
+from numba.core.errors import NumbaNotImplementedError, NumbaTypeError
 
 
 class TestOverloadSelector(unittest.TestCase):
@@ -64,7 +65,7 @@ def test_ambiguous_detection(self):
         os.append(2, (types.Integer, types.Boolean))
         self.assertEqual(os.find((types.boolean, types.boolean)), 1)
         # not implemented
-        with self.assertRaises(NotImplementedError) as raises:
+        with self.assertRaises(NumbaNotImplementedError) as raises:
             os.find((types.boolean, types.int32))
         # generic
         os.append(3, (types.Any, types.Any))
@@ -72,7 +73,7 @@ def test_ambiguous_detection(self):
         self.assertEqual(os.find((types.boolean, types.boolean)), 1)
         # add ambiguous signature; can match (bool, any) and (any, bool)
         os.append(4, (types.Boolean, types.Any))
-        with self.assertRaises(TypeError) as raises:
+        with self.assertRaises(NumbaTypeError) as raises:
             os.find((types.boolean, types.boolean))
         self.assertIn('2 ambiguous signatures', str(raises.exception))
         # disambiguous
@@ -122,7 +123,7 @@ def test_ambiguous_casts(self):
         for sig in permutations(all_types, r=2):
             try:
                 os.find(sig)
-            except NotImplementedError:
+            except NumbaNotImplementedError:
                 pass   # ignore not implemented cast
 
     def test_ambiguous_functions(self):
@@ -138,7 +139,7 @@ def test_ambiguous_functions(self):
             for sig in product(all_types, all_types):
                 try:
                     os.find(sig)
-                except NotImplementedError:
+                except NumbaNotImplementedError:
                     pass   # ignore not implemented cast
 
 
diff --git a/numba/tests/test_typeof.py b/numba/tests/test_typeof.py
index 5b2624308c1..eb9b8e04d5b 100644
--- a/numba/tests/test_typeof.py
+++ b/numba/tests/test_typeof.py
@@ -12,6 +12,7 @@
 import unittest
 import numba.core.typing.cffi_utils as cffi_support
 from numba.core import types
+from numba.core.errors import NumbaValueError
 from numba.misc.special import typeof
 from numba.core.dispatcher import OmittedArg
 from numba._dispatcher import compute_fingerprint
@@ -106,7 +107,7 @@ def check(arr, ndim, layout, mutable, aligned):
 
         # Unsupported dtype
         a5 = a1.astype(a1.dtype.newbyteorder())
-        with self.assertRaises(ValueError) as raises:
+        with self.assertRaises(NumbaValueError) as raises:
             typeof(a5)
         self.assertIn("Unsupported array dtype: %s" % (a5.dtype,),
                       str(raises.exception))
diff --git a/numba/tests/test_typingerror.py b/numba/tests/test_typingerror.py
index e215a1da46d..157beeb24f0 100644
--- a/numba/tests/test_typingerror.py
+++ b/numba/tests/test_typingerror.py
@@ -197,11 +197,7 @@ def test_unsupported_array_dtype(self):
         a = a.astype(a.dtype.newbyteorder())
         with self.assertRaises(TypingError) as raises:
             cfunc(1, a, a)
-        expected = textwrap.dedent("""\
-            This error may have been caused by the following argument(s):
-            - argument 1: Unsupported array dtype: {0}
-            - argument 2: Unsupported array dtype: {0}"""
-            ).format(a.dtype)
+        expected = f"Unsupported array dtype: {a.dtype}"
         self.assertIn(expected, str(raises.exception))
 
     def test_unsupported_type(self):
diff --git a/numba/tests/test_ufuncs.py b/numba/tests/test_ufuncs.py
index 16243eb0b90..c1f4b1bbce1 100644
--- a/numba/tests/test_ufuncs.py
+++ b/numba/tests/test_ufuncs.py
@@ -14,7 +14,7 @@
 from numba.core.compiler import compile_isolated, Flags, DEFAULT_FLAGS
 from numba.np.numpy_support import from_dtype
 from numba import jit, vectorize
-from numba.core.errors import LoweringError, TypingError
+from numba.core.errors import LoweringError, TypingError, NumbaTypeError
 from numba.tests.support import TestCase, CompilationCache, MemoryLeakMixin, tag
 from numba.core.typing.npydecl import supported_ufuncs, all_ufuncs
 from numba.np import numpy_support
@@ -751,7 +751,7 @@ def test_broadcasting(self):
             self.assertPreciseEqual(result, expected)
 
     def test_implicit_output_npm(self):
-        with self.assertRaises(TypeError):
+        with self.assertRaises(NumbaTypeError):
             def myadd(a0, a1):
                 return np.add(a0, a1)
             arr_ty = types.Array(types.uint64, 1, 'C')
diff --git a/numba/tests/test_unsafe_intrinsics.py b/numba/tests/test_unsafe_intrinsics.py
index 14387acd366..d53e0a6528a 100644
--- a/numba/tests/test_unsafe_intrinsics.py
+++ b/numba/tests/test_unsafe_intrinsics.py
@@ -206,14 +206,14 @@ def check_error_msg(self, func):
             with self.assertRaises(TypingError) as e:
                 cfunc(typ(2))
             self.assertIn(
-                "{} is only defined for integers, but passed value was '{}'."
+                "{} is only defined for integers, but value passed was '{}'."
                 .format(func_name, typ),
                 str(e.exception),
             )
 
         # Testing w/ too many/few arguments
         def check(args, string):
-            with self.assertRaises(TypingError) as e:
+            with self.assertRaises((TypingError, TypeError)) as e:
                 cfunc(*args)
             self.assertIn(
                 "{}() ".format(func_name),
diff --git a/numba/tests/test_withlifting.py b/numba/tests/test_withlifting.py
index df504084e62..4f7ad613f5c 100644
--- a/numba/tests/test_withlifting.py
+++ b/numba/tests/test_withlifting.py
@@ -257,7 +257,9 @@ def test_liftcall3(self):
         self.check_same_semantic(liftcall3)
 
     def test_liftcall4(self):
-        with self.assertRaises(errors.TypingError) as raises:
+        accept = (errors.TypingError, errors.NumbaRuntimeError,
+                  errors.NumbaValueError)
+        with self.assertRaises(accept) as raises:
             njit(liftcall4)()
         # Known error.  We only support one context manager per function
         # for body that are lifted.
@@ -521,7 +523,7 @@ def foo(x):
         # Check that an error occurred in with-lifting in objmode
         pat = ("During: resolving callee type: "
                "type\(ObjModeLiftedWith\(<.*>\)\)")
-        self.assertRegexpMatches(str(raises.exception), pat)
+        self.assertRegex(str(raises.exception), pat)
 
     def test_case07_mystery_key_error(self):
         # this raises a key error


Code review comments:
diff_hunk: @@ -783,9 +821,17 @@ def new_error_context(fmt_, *args, **kwargs):
         # Let assertion error pass through for shorter traceback in debugging
         raise
     except Exception as e:
-        newerr = errcls(e).add_context(_format_msg(fmt_, args, kwargs))
-        tb = sys.exc_info()[2] if numba.core.config.FULL_TRACEBACKS else None
-        raise newerr.with_traceback(tb)
+        if numba.core.config.CAPTURED_ERRORS == 'old_style':
+            newerr = errcls(e).add_context(_format_msg(fmt_, args, kwargs))
+            if numba.core.config.FULL_TRACEBACKS:
+                tb = sys.exc_info()[2]
+            else:
+                tb = None
+            raise newerr.with_traceback(tb)
+        elif numba.core.config.CAPTURED_ERRORS == 'new_style':
path: numba/core/errors.py
text: @user1:
replace with `use_new_style_errors()`

@author:
124f86f

diff_hunk: @@ -162,15 +162,20 @@ def propagate(self, typeinfer):
                     )
                     errors.append(utils.chain_exception(new_exc, e))
                 except Exception as e:
-                    _logger.debug("captured error", exc_info=e)
-                    msg = ("Internal error at {con}.\n"
-                           "{err}\nEnable logging at debug level for details.")
-                    new_exc = TypingError(
-                        msg.format(con=constraint, err=str(e)),
-                        loc=constraint.loc,
-                        highlighting=False,
-                    )
-                    errors.append(utils.chain_exception(new_exc, e))
+                    if config.CAPTURED_ERRORS == 'old_style':
path: numba/core/typeinfer.py
text: @user1:
replace with `use_old_style_errors()`

@author:
124f86f

diff_hunk: @@ -783,9 +821,17 @@ def new_error_context(fmt_, *args, **kwargs):
         # Let assertion error pass through for shorter traceback in debugging
         raise
     except Exception as e:
-        newerr = errcls(e).add_context(_format_msg(fmt_, args, kwargs))
-        tb = sys.exc_info()[2] if numba.core.config.FULL_TRACEBACKS else None
-        raise newerr.with_traceback(tb)
+        if numba.core.config.CAPTURED_ERRORS == 'old_style':
path: numba/core/errors.py
text: @user1:
needs a `use_old_style_errors()`

@author:
124f86f

diff_hunk: @@ -402,6 +402,15 @@ class CallableTemplate(FunctionTemplate):
     def apply(self, args, kws):
         generic = getattr(self, "generic")
         typer = generic()
+        try:
+            match_sig = inspect.signature(typer)
path: numba/core/typing/templates.py
text: @user1:
i don't think it should be catching `TypeError` from `.signature`

```suggestion
        match_sig = inspect.signature(typer)
        try:       
```

@author:
I guess this can only come from the arg to `signature` being non-callable, which would indicate a more fundamental problem, will make the change, thanks!

@author:
d63dab5

diff_hunk: @@ -162,15 +162,20 @@ def propagate(self, typeinfer):
                     )
                     errors.append(utils.chain_exception(new_exc, e))
                 except Exception as e:
-                    _logger.debug("captured error", exc_info=e)
-                    msg = ("Internal error at {con}.\n"
-                           "{err}\nEnable logging at debug level for details.")
-                    new_exc = TypingError(
-                        msg.format(con=constraint, err=str(e)),
-                        loc=constraint.loc,
-                        highlighting=False,
-                    )
-                    errors.append(utils.chain_exception(new_exc, e))
+                    if config.CAPTURED_ERRORS == 'old_style':
+                        _logger.debug("captured error", exc_info=e)
+                        msg = ("Internal error at {con}.\n{err}\n"
+                               "Enable logging at debug level for details.")
+                        new_exc = TypingError(
+                            msg.format(con=constraint, err=str(e)),
+                            loc=constraint.loc,
+                            highlighting=False,
+                        )
+                        errors.append(utils.chain_exception(new_exc, e))
+                    elif config.CAPTURED_ERRORS == 'new_style':
path: numba/core/typeinfer.py
text: @user1:
replace with `use_new_style_errors()`

@author:
124f86f