Repository: numba/numba

Issue:
0.58.0rc1 old-style error capturing warning issues

Issues:
- [x] Warnings about old-style error are raised regardless of whether the error is a `NumbaError`. (ref: https://matrix.to/#/!gFwSEpcuaweltInwDW:gitter.im/$5Mc1guc4peAryXvx3WjSWhD3TDxC_Nzb-mCQsl-s17k?via=gitter.im&via=matrix.org&via=matrix.kraut.space)
- [x] Reason of the warning is unclear (ref: https://numba.discourse.group/t/ann-numba-0-58-0rc1-and-llvmlite-0-41-0rc1/2078/2)
0.58.0rc1 old-style error capturing warning issues

Issues:
- [x] Warnings about old-style error are raised regardless of whether the error is a `NumbaError`. (ref: https://matrix.to/#/!gFwSEpcuaweltInwDW:gitter.im/$5Mc1guc4peAryXvx3WjSWhD3TDxC_Nzb-mCQsl-s17k?via=gitter.im&via=matrix.org&via=matrix.kraut.space)
- [x] Reason of the warning is unclear (ref: https://numba.discourse.group/t/ann-numba-0-58-0rc1-and-llvmlite-0-41-0rc1/2078/2)


Discussion:
I'm watching this now.
I'm watching this now.

Resolving patch:
diff --git a/numba/core/errors.py b/numba/core/errors.py
--- a/numba/core/errors.py
+++ b/numba/core/errors.py
@@ -457,6 +457,9 @@ class WarningsFixer(object):
     An object "fixing" warnings of a given category caught during
     certain phases.  The warnings can have their filename and lineno fixed,
     and they are deduplicated as well.
+
+    When used as a context manager, any warnings caught by `.catch_warnings()`
+    will be flushed at the exit of the context manager.
     """
 
     def __init__(self, category):
@@ -502,6 +505,12 @@ def key(arg):
                 warnings.warn_explicit(msg, category, filename, lineno)
         self._warnings.clear()
 
+    def __enter__(self):
+        return
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.flush()
+
 
 class NumbaError(Exception):
 
diff --git a/numba/core/typed_passes.py b/numba/core/typed_passes.py
--- a/numba/core/typed_passes.py
+++ b/numba/core/typed_passes.py
@@ -68,9 +68,12 @@ def type_inference_stage(typingctx, targetctx, interp, args, return_type,
     if len(args) != interp.arg_count:
         raise TypeError("Mismatch number of argument types")
     warnings = errors.WarningsFixer(errors.NumbaWarning)
+
     infer = typeinfer.TypeInferer(typingctx, interp, warnings)
-    with typingctx.callstack.register(targetctx.target, infer, interp.func_id,
-                                      args):
+    callstack_ctx = typingctx.callstack.register(targetctx.target, infer,
+                                                 interp.func_id, args)
+    # Setup two contexts: 1) callstack setup/teardown 2) flush warnings
+    with callstack_ctx, warnings:
         # Seed argument types
         for index, (name, ty) in enumerate(zip(interp.arg_names, args)):
             infer.seed_argument(name, index, ty)
@@ -88,9 +91,6 @@ def type_inference_stage(typingctx, targetctx, interp, args, return_type,
         errs = infer.propagate(raise_errors=raise_errors)
         typemap, restype, calltypes = infer.unify(raise_errors=raise_errors)
 
-    # Output all Numba warnings
-    warnings.flush()
-
     return _TypingResults(typemap, restype, calltypes, errs)
 
 
diff --git a/numba/core/utils.py b/numba/core/utils.py
--- a/numba/core/utils.py
+++ b/numba/core/utils.py
@@ -192,15 +192,26 @@ def shutting_down(globals=globals):
 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 a future 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
 
-    warnings.warn(
-        "The 'old_style' error capturing is deprecated "
-        "and will be replaced by `new_style` in a future release.",
-        errors.NumbaPendingDeprecationWarning,
-        stacklevel=3
-    )
+    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.NumbaPendingDeprecationWarning)
 
 
 def use_new_style_errors():


Test patch:
diff --git a/numba/tests/test_errorhandling.py b/numba/tests/test_errorhandling.py
--- a/numba/tests/test_errorhandling.py
+++ b/numba/tests/test_errorhandling.py
@@ -463,15 +463,21 @@ 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))
+        with warnings.catch_warnings():
+            # 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))
 
     @TestCase.run_test_in_subprocess(
         envvars={"NUMBA_CAPTURED_ERRORS": "old_style"},
@@ -493,8 +499,34 @@ def ol_bar(x):
                 def foo(x):
                     bar(x)
 
-            self.assertIn("The 'old_style' error capturing is deprecated",
-                          str(warns.warnings[0].message))
+            self.assertIn(
+                "Code using Numba extension API maybe depending on 'old_style' "
+                "error-capturing",
+                str(warns.warnings[0].message),
+            )
+
+    @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.NumbaPendingDeprecationWarning)
+
+        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")
 
     @TestCase.run_test_in_subprocess(
         envvars={"NUMBA_CAPTURED_ERRORS": "new_style"},
@@ -515,8 +547,9 @@ def ol_bar(x):
                 @njit('void(int64)')
                 def foo(x):
                     bar(x)
-
-            self.assertEqual(len(warns), 0)
+            # There should not be any warnings
+            self.assertEqual(len(warns), 0,
+                             msg="There should not be any warnings")
 
 
 if __name__ == '__main__':