Repository: numba/numba

Issue:
Update changelog script to account for missing author info on github.
Remaining task from https://github.com/numba/numba/issues/9372

Fix the changelog script to account for missing author info on github. probably should use git info to fill in the blanks.

Discussion:

Patch:
diff --git a/docs/source/extending/high-level.rst b/docs/source/extending/high-level.rst
index e07ede140..4e4877336 100644
--- a/docs/source/extending/high-level.rst
+++ b/docs/source/extending/high-level.rst
@@ -6,6 +6,15 @@ 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/deprecation.rst b/docs/source/reference/deprecation.rst
index d3a375a7c..6f7026ca0 100644
--- a/docs/source/reference/deprecation.rst
+++ b/docs/source/reference/deprecation.rst
@@ -499,8 +499,8 @@ Schedule
 Deprecation of old-style ``NUMBA_CAPTURED_ERRORS``
 ==================================================
 
-The use of the ``NUMBA_CAPTURED_ERRORS`` environment variable is deprecated and
-removed.
+The use of ``NUMBA_CAPTURED_ERRORS=old_style`` environment variable is being 
+deprecated in Numba.
 
 Reason for deprecation
 ----------------------
@@ -514,7 +514,9 @@ The new "new_style" option treats non-``NumbaError`` exceptions as hard errors,
 propagating them without capturing. This differentiates compilation errors from 
 unintended exceptions during compilation.
 
-The old style was removed in favor of the new behavior.
+The old style will eventually be removed in favor of the new behavior. Users 
+should migrate to setting ``NUMBA_CAPTURED_ERRORS='new_style'`` to opt-in to the 
+new exception handling. This will become the default in the future.
 
 Impact
 ------
@@ -525,6 +527,9 @@ functionality.
 Recommendations
 ---------------
 
+- Projects that extends Numba should set 
+  ``NUMBA_CAPTURED_ERRORS='new_style'`` for testing to find all places where 
+  non-``NumbaError`` exceptions are raised during compilation.
 - Modify any code that raises a non-``NumbaError`` to indicate a compilation
   error to raise a subclass of ``NumbaError`` instead. For example, instead of
   raising a ``TypeError``, raise a ``numba.core.errors.NumbaTypeError``.
@@ -533,9 +538,11 @@ Recommendations
 Schedule
 --------
 
-- In Numba 0.58: ``NUMBA_CAPTURED_ERRORS=old_style`` was deprecated. Warnings
+- In Numba 0.58: ``NUMBA_CAPTURED_ERRORS=old_style`` is deprecated. Warnings 
   will be raised when `old_style` error capturing is used.
 - In Numba 0.59: explicitly setting ``NUMBA_CAPTURED_ERRORS=old_style`` will 
   raise deprecation warnings.
-- In Numba 0.60: ``NUMBA_CAPTURED_ERRORS=new_style`` became the default.
-- In Numba 0.61: support for ``NUMBA_CAPTURED_ERRORS=old_style`` was removed.
+- In Numba 0.60: ``NUMBA_CAPTURED_ERRORS=new_style`` becomes the default.
+- In Numba 0.61: support for ``NUMBA_CAPTURED_ERRORS=old_style`` will be 
+  removed.
+
diff --git a/docs/source/reference/envvars.rst b/docs/source/reference/envvars.rst
index 25104dd66..f05a6b8ad 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 occurring 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/docs/upcoming_changes/9773.deprecation.rst b/docs/upcoming_changes/9773.deprecation.rst
deleted file mode 100644
index cc29b922a..000000000
--- a/docs/upcoming_changes/9773.deprecation.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Removal of ``NUMBA_CAPTURED_ERRORS``
-------------------------------------
-
-The ``NUMBA_CAPTURED_ERRORS`` environment variable and ``CAPTURED_ERRORS``
-configuration variable have been removed, as per the deprecation schedule.
diff --git a/maint/gitlog2changelog.py b/maint/gitlog2changelog.py
index 3930aebce..80668582a 100644
--- a/maint/gitlog2changelog.py
+++ b/maint/gitlog2changelog.py
@@ -57,21 +57,33 @@ if __name__ == '__main__':
             issue_id = match.groups()[0]
             ordered[issue_id] = "%s" % (x.message.splitlines()[2])
     print("Pull-Requests:\n")
+    missing_authors = set()
     for k in sorted(ordered.keys()):
         pull = get_pr(int(k))
         hyperlink = "`#%s <%s>`_" % (k, pull.html_url)
         # get all users for all commits
         pr_authors = set()
         for c in pull.get_commits():
-            if c.author:
+            author = c.author
+            if author:
                 pr_authors.add(c.author)
+            else:
+                missing_authors.add((pull, c))
             if c.committer and c.committer.login != "web-flow":
                 pr_authors.add(c.committer)
+            elif not author:
+                missing_authors.add((pull, c))
         print("* PR %s: %s (%s)" % (hyperlink, ordered[k],
                                     " ".join([hyperlink_user(u) for u in
                                               pr_authors])))
         for a in pr_authors:
             authors.add(a)
+    if missing_authors:
+        print("\n===========================WARNING=================================\n")
+        print("Following PR commits are missing authors and may need manual changes:\n")
+        for pull, commit in missing_authors:
+            print(f"* {commit} in PR #{pull.number}: {pull.title}")
+        print("\n===================================================================\n")
     if summary:
         print("\nTotal PRs: %s\n" % len(ordered))
     else:
diff --git a/numba/core/compiler.py b/numba/core/compiler.py
index 0422d273b..64617e4c7 100644
--- a/numba/core/compiler.py
+++ b/numba/core/compiler.py
@@ -3,7 +3,7 @@ import copy
 import warnings
 from numba.core.tracing import event
 
-from numba.core import (errors, interpreter, bytecode, postproc, config,
+from numba.core import (utils, errors, interpreter, bytecode, postproc, config,
                         callconv, cpu)
 from numba.parfors.parfor import ParforDiagnostics
 from numba.core.errors import CompilerError
@@ -477,7 +477,8 @@ class CompilerBase(object):
                     res = e.result
                     break
                 except Exception as e:
-                    if not isinstance(e, errors.NumbaError):
+                    if (utils.use_new_style_errors() and not
+                            isinstance(e, errors.NumbaError)):
                         raise e
 
                     self.state.status.fail_reason = e
diff --git a/numba/core/compiler_machinery.py b/numba/core/compiler_machinery.py
index 5851ea8b6..aa76553b6 100644
--- a/numba/core/compiler_machinery.py
+++ b/numba/core/compiler_machinery.py
@@ -358,7 +358,8 @@ class PassManager(object):
             except _EarlyPipelineCompletion as e:
                 raise e
             except Exception as e:
-                if not isinstance(e, errors.NumbaError):
+                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)
diff --git a/numba/core/config.py b/numba/core/config.py
index 1fa72a1e9..43263e347 100644
--- a/numba/core/config.py
+++ b/numba/core/config.py
@@ -70,6 +70,32 @@ def _os_supports_avx():
             return False
 
 
+_old_style_deprecation_msg = (
+    "NUMBA_CAPTURED_ERRORS=old_style is deprecated. "
+    "It will be removed in the next release. See details at "
+    "https://numba.readthedocs.io/en/latest/reference/deprecation.html#deprecation-of-old-style-numba-captured-errors" # noqa: E501
+)
+
+
+# Choose how to handle captured errors
+def _validate_captured_errors_style(style_str):
+    # to prevent circular import
+    from numba.core.errors import NumbaDeprecationWarning
+
+    rendered_style = str(style_str)
+    if rendered_style not in ('new_style', 'old_style', 'default'):
+        msg = ("Invalid style in NUMBA_CAPTURED_ERRORS: "
+               f"{rendered_style}")
+        raise ValueError(msg)
+    else:
+        if rendered_style == 'default':
+            rendered_style = 'new_style'
+        elif rendered_style == 'old_style':
+            warnings.warn(_old_style_deprecation_msg,
+                          NumbaDeprecationWarning)
+        return rendered_style
+
+
 class _OptLevel(int):
     """This class holds the "optimisation level" set in `NUMBA_OPT`. As this env
     var can be an int or a string, but is almost always interpreted as an int,
@@ -404,6 +430,10 @@ class _EnvReloader(object):
         )
         THREADING_LAYER = _readenv("NUMBA_THREADING_LAYER", str, 'default')
 
+        CAPTURED_ERRORS = _readenv("NUMBA_CAPTURED_ERRORS",
+                                   _validate_captured_errors_style,
+                                   'new_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 283d7344f..892b230bd 100644
--- a/numba/core/errors.py
+++ b/numba/core/errors.py
@@ -6,6 +6,7 @@ Numba-specific errors and warnings.
 import abc
 import contextlib
 import os
+import sys
 import warnings
 import numba.core.config
 import numpy as np
@@ -840,12 +841,23 @@ loc_info = {}
 def new_error_context(fmt_, *args, **kwargs):
     """
     A contextmanager that prepend contextual information to any exception
-    raised within.
+    raised within.  If the exception type is not an instance of NumbaError,
+    it will be wrapped into a InternalError.   The exception class can be
+    changed by providing a "errcls_" keyword argument with the exception
+    constructor.
 
     The first argument is a message that describes the context.  It can be a
     format string.  If there are additional arguments, it will be used as
     ``fmt_.format(*args, **kwargs)`` to produce the final message string.
     """
+    # Import here to avoid circular import.
+    from numba.core.utils import (
+        use_old_style_errors,
+        use_new_style_errors,
+    )
+
+    errcls = kwargs.pop('errcls_', InternalError)
+
     loc = kwargs.get('loc', None)
     if loc is not None and not loc.filename.startswith(_numba_path):
         loc_info.update(kwargs)
@@ -855,6 +867,23 @@ def new_error_context(fmt_, *args, **kwargs):
     except NumbaError as e:
         e.add_context(_format_msg(fmt_, args, kwargs))
         raise
+    except AssertionError:
+        # Let assertion error pass through for shorter traceback in debugging
+        raise
+    except Exception as e:
+        if use_old_style_errors():
+            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 use_new_style_errors():
+            raise e
+        else:
+            msg = ("Unknown CAPTURED_ERRORS style: "
+                   f"'{numba.core.config.CAPTURED_ERRORS}'.")
+            assert 0, msg
 
 
 __all__ += [name for (name, value) in globals().items()
diff --git a/numba/core/typeinfer.py b/numba/core/typeinfer.py
index e8a7d824c..0280dd9f5 100644
--- a/numba/core/typeinfer.py
+++ b/numba/core/typeinfer.py
@@ -167,6 +167,23 @@ class ConstraintNetwork(object):
                         highlighting=False,
                     )
                     errors.append(utils.chain_exception(new_exc, e))
+                except Exception as e:
+                    if utils.use_old_style_errors():
+                        _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 utils.use_new_style_errors():
+                        raise e
+                    else:
+                        msg = ("Unknown CAPTURED_ERRORS style: "
+                               f"'{config.CAPTURED_ERRORS}'.")
+                        assert 0, msg
 
         return errors
 
diff --git a/numba/core/types/functions.py b/numba/core/types/functions.py
index b7913923a..c3972af69 100644
--- a/numba/core/types/functions.py
+++ b/numba/core/types/functions.py
@@ -307,7 +307,8 @@ class BaseFunction(Callable):
                                     for k, v in kws.items()}
                         sig = temp.apply(nolitargs, nolitkws)
                 except Exception as e:
-                    if not isinstance(e, errors.NumbaError):
+                    if (utils.use_new_style_errors() and not
+                            isinstance(e, errors.NumbaError)):
                         raise e
                     else:
                         sig = None
@@ -398,7 +399,8 @@ class BoundFunction(Callable, Opaque):
                 try:
                     out = template.apply(args, kws)
                 except Exception as exc:
-                    if not isinstance(exc, errors.NumbaError):
+                    if (utils.use_new_style_errors() and not
+                            isinstance(exc, errors.NumbaError)):
                         raise exc
                     if isinstance(exc, errors.ForceLiteralArg):
                         raise exc
diff --git a/numba/core/utils.py b/numba/core/utils.py
index 3439982f9..e40f6a2fc 100644
--- a/numba/core/utils.py
+++ b/numba/core/utils.py
@@ -190,6 +190,48 @@ weakref.finalize(lambda: None, lambda: None)
 atexit.register(_at_shutdown)
 
 
+_old_style_deprecation_msg = (
+    "Code using Numba extension API maybe depending on 'old_style' "
+    "error-capturing, which is deprecated and will be replaced by 'new_style' "
+    "in the next release. See details at "
+    "https://numba.readthedocs.io/en/latest/reference/deprecation.html#deprecation-of-old-style-numba-captured-errors" # noqa: E501
+)
+
+
+def _warn_old_style():
+    from numba.core import errors  # to prevent circular import
+
+    exccls, _, tb = sys.exc_info()
+    # Warn only if the active exception is not a NumbaError
+    # and not a NumbaWarning which is raised if -Werror is set.
+    numba_errs = (errors.NumbaError, errors.NumbaWarning)
+    if exccls is not None and not issubclass(exccls, numba_errs):
+        tb_last = traceback.format_tb(tb)[-1]
+        msg = f"{_old_style_deprecation_msg}\nException origin:\n{tb_last}"
+        warnings.warn(msg,
+                      errors.NumbaDeprecationWarning)
+
+
+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`.
+    res = config.CAPTURED_ERRORS == 'new_style'
+    if not res:
+        _warn_old_style()
+    return res
+
+
+def use_old_style_errors():
+    """Returns True if old 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`.
+    res = config.CAPTURED_ERRORS == 'old_style'
+    if res:
+        _warn_old_style()
+    return res
+
+
 class ThreadLocalStack:
     """A TLS stack container.
 


Test patch:
diff --git a/numba/tests/test_config.py b/numba/tests/test_config.py
index d4efb07a9..a36857165 100644
--- a/numba/tests/test_config.py
+++ b/numba/tests/test_config.py
@@ -106,6 +106,37 @@ class TestConfig(TestCase):
         orig, curr = self.create_config_effect({})
         self.assertEqual(orig, curr)
 
+    def test_illegal_error_style_handling(self):
+        # ensure that illegal error styles are ignored
+        new_env = os.environ.copy()
+        new_env['NUMBA_CAPTURED_ERRORS'] = 'not_a_known_style'
+        source_compiled = "the source compiled"
+        code = ("from numba import njit\n@njit\ndef foo():\n\t"
+                f"print('{source_compiled}')\nfoo()")
+        out, err = run_in_subprocess(dedent(code), env=new_env)
+        expected = ("Environment variable \'NUMBA_CAPTURED_ERRORS\' is defined "
+                    "but its associated value \'not_a_known_style\' could not "
+                    "be parsed.")
+        err_msg = err.decode('utf-8')
+        self.assertIn(expected, err_msg)
+        ex_expected = ("Invalid style in NUMBA_CAPTURED_ERRORS: "
+                       "not_a_known_style")
+        self.assertIn(ex_expected, err_msg)
+        self.assertIn(source_compiled, out.decode('utf-8'))
+
+    def test_default_error_style_handling(self):
+        # ensure that the default is new_style
+        new_env = os.environ.copy()
+        new_env['NUMBA_CAPTURED_ERRORS'] = 'default'
+        code = ("from numba.core import config\n"
+                "print('---->', config.CAPTURED_ERRORS)\n"
+                "assert config.CAPTURED_ERRORS == 'new_style'")
+        out, err = run_in_subprocess(dedent(code), env=new_env)
+        err_msg = err.decode('utf-8')
+        out_msg = out.decode('utf-8')
+        ex_expected = "----> new_style"
+        self.assertIn(ex_expected, out_msg, msg=err_msg)
+
     @unittest.skipUnless(config.ENABLE_AVX,
                          "test expects NUMBA_ENABLE_AVX==True")
     def test_nocona_disables_avx(self):
diff --git a/numba/tests/test_errorhandling.py b/numba/tests/test_errorhandling.py
index 3d8f0091c..8bdb30adb 100644
--- a/numba/tests/test_errorhandling.py
+++ b/numba/tests/test_errorhandling.py
@@ -2,6 +2,8 @@
 Unspecified error handling tests
 """
 
+import sys
+import subprocess
 import numpy as np
 import os
 import warnings
@@ -436,7 +438,8 @@ class TestDeveloperSpecificErrorMessages(SerialMixin, unittest.TestCase):
 
 
 class TestCapturedErrorHandling(SerialMixin, TestCase):
-    """Checks that the way errors are captured.
+    """Checks that the way errors are captured changes depending on the env
+    var "NUMBA_CAPTURED_ERRORS".
     """
 
     def test_error_in_overload(self):
@@ -456,13 +459,145 @@ class TestCapturedErrorHandling(SerialMixin, TestCase):
             # Suppress error going into stdout
             warnings.simplefilter("ignore",
                                   errors.NumbaPendingDeprecationWarning)
+            # Check both new_style and old_style
+            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))
+
+    def _run_in_separate_process(self, runcode, env):
+        # Run code in separate process with -Wall and specific env-vars
+        code = f"""if 1:
+            {runcode}\n
+            """
+        # On windows, missing the base environment variable can cause
+        # Fatal Python error: _Py_HashRandomization_Init: failed to get random
+        #                     numbers to initialize Python
+        proc_env = os.environ.copy()
+        proc_env.update(env)
+        popen = subprocess.Popen([sys.executable, "-Wall", "-c", code],
+                                 stdout=subprocess.PIPE,
+                                 stderr=subprocess.PIPE,
+                                 env=proc_env)
+
+        out, err = popen.communicate()
+        if popen.returncode != 0:
+            raise AssertionError("process failed with code %s: stderr follows"
+                                 "\n%s\n" % (popen.returncode, err.decode()))
+        return out, err
+
+    def test_old_style_deprecation_on_import(self):
+        from numba.core.config import _old_style_deprecation_msg
+
+        code = """
+        import numba
+        """
+        # Check that the deprecated message is shown
+        # if NUMBA_CAPTURED_ERRORS=old_style
+        env = {"NUMBA_CAPTURED_ERRORS": "old_style"}
+        _out, err = self._run_in_separate_process(code, env)
+        self.assertIn(_old_style_deprecation_msg, err.decode())
+
+        # Check that the deprecated message is NOT shown
+        # if NUMBA_CAPTURED_ERRORS is unset
+        env = {"NUMBA_CAPTURED_ERRORS": ""}
+        _out, err = self._run_in_separate_process(code, env)
+        # Check that the deprecated message is not shown
+        self.assertNotIn("NumbaPendingDeprecationWarning", err.decode())
+
+        # Check that the deprecated message is NOT shown
+        # if NUMBA_CAPTURED_ERRORS=new_style
+        env = {"NUMBA_CAPTURED_ERRORS": "new_style"}
+        _out, err = self._run_in_separate_process(code, env)
+        # Check that the deprecated message is not shown
+        self.assertNotIn("NumbaPendingDeprecationWarning", err.decode())
+
+    def _test_old_style_deprecation(self):
+        # Verify that old_style error raise the correct deprecation warning
+        warnings.simplefilter("always", errors.NumbaDeprecationWarning)
 
-            with self.assertRaises(AttributeError) as raises:
+        def bar(x):
+            pass
+
+        @overload(bar)
+        def ol_bar(x):
+            raise AttributeError("Invalid attribute")
+
+        with self.assertWarns(errors.NumbaDeprecationWarning) as warns:
+            with self.assertRaises(errors.TypingError):
                 @njit('void(int64)')
                 def foo(x):
                     bar(x)
-            expected = "object has no attribute 'some_invalid_attr'"
-            self.assertIn(expected, str(raises.exception))
+
+            self.assertIn(
+                "Code using Numba extension API maybe depending on 'old_style' "
+                "error-capturing",
+                str(warns.warnings[0].message),
+            )
+
+    # Check deprecation warning when NUMBA_CAPTURED_ERRORS=old_style
+    test_old_style_deprecation = TestCase.run_test_in_subprocess(
+        envvars={"NUMBA_CAPTURED_ERRORS": "old_style"},
+    )(_test_old_style_deprecation)
+
+    @TestCase.run_test_in_subprocess(
+        envvars={"NUMBA_CAPTURED_ERRORS": "old_style"},
+    )
+    def test_old_style_no_deprecation(self):
+        # Verify that old_style error with NumbaError does not raise warnings
+        warnings.simplefilter("always", errors.NumbaDeprecationWarning)
+
+        def bar(x):
+            pass
+
+        @overload(bar)
+        def ol_bar(x):
+            raise errors.TypingError("Invalid attribute")
+
+        with warnings.catch_warnings(record=True) as warns:
+            with self.assertRaises(errors.TypingError):
+                @njit('void(int64)')
+                def foo(x):
+                    bar(x)
+
+            self.assertEqual(len(warns), 0,
+                             msg="There should not be any warnings")
+
+    def _test_new_style_no_warnings(self):
+        # Verify that new_style error raise no warnings
+        warnings.simplefilter("always", errors.NumbaDeprecationWarning)
+
+        def bar(x):
+            pass
+
+        @overload(bar)
+        def ol_bar(x):
+            raise AttributeError("Invalid attribute")
+
+        with warnings.catch_warnings(record=True) as warns:
+            with self.assertRaises(AttributeError):
+                @njit('void(int64)')
+                def foo(x):
+                    bar(x)
+            # There should not be any warnings
+            self.assertEqual(len(warns), 0,
+                             msg="There should not be any warnings")
+
+    test_new_style_no_warnings = TestCase.run_test_in_subprocess(
+        envvars={"NUMBA_CAPTURED_ERRORS": "new_style"},
+    )(_test_new_style_no_warnings)
+
+    # Check deprecation warning when NUMBA_CAPTURED_ERRORS=default
+    # ("default" means "old_style")
+    test_default_new_style_no_deprecation = TestCase.run_test_in_subprocess(
+        envvars={"NUMBA_CAPTURED_ERRORS": "default"},
+    )(_test_new_style_no_warnings)
 
 
 if __name__ == '__main__':