Repository: numba/numba

Issue:
0.57.0rc1 NumbaDeprecationWarning: `@guvectorize` even with `nopython` keyword argument
The following code (note the `nopython=True` argument)

```py
from numba import guvectorize, int64

@guvectorize([(int64[:], int64, int64[:])], '(n),()->(n)', nopython=True)
def g(x, y, res):
    for i in range(x.shape[0]):
        res[i] = x[i] + y
```

raises `NumbaDeprecationWarning`:

`/Applications/anaconda3/envs/py311/lib/python3.11/site-packages/numba/np/ufunc/ufuncbuilder.py:328: NumbaDeprecationWarning: The 'nopython' keyword argument was not supplied to the 'numba.jit' decorator. The implicit default value for this argument is currently False, but it will be changed to True in Numba 0.59.0. See https://numba.readthedocs.io/en/stable/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit for details.
  self.nb_func = jit(_target='npyufunc', cache=cache)(py_func)`

This does not occur with `@vectorize`:

```py
from numba import vectorize, float64

@vectorize([float64(float64, float64)], nopython=True)
def f(x, y):
    return x + y
```

```
>>> numba.__version__
'0.57.0rc1'
```
0.57.0rc1 NumbaDeprecationWarning: `@guvectorize` even with `nopython` keyword argument
The following code (note the `nopython=True` argument)

```py
from numba import guvectorize, int64

@guvectorize([(int64[:], int64, int64[:])], '(n),()->(n)', nopython=True)
def g(x, y, res):
    for i in range(x.shape[0]):
        res[i] = x[i] + y
```

raises `NumbaDeprecationWarning`:

`/Applications/anaconda3/envs/py311/lib/python3.11/site-packages/numba/np/ufunc/ufuncbuilder.py:328: NumbaDeprecationWarning: The 'nopython' keyword argument was not supplied to the 'numba.jit' decorator. The implicit default value for this argument is currently False, but it will be changed to True in Numba 0.59.0. See https://numba.readthedocs.io/en/stable/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit for details.
  self.nb_func = jit(_target='npyufunc', cache=cache)(py_func)`

This does not occur with `@vectorize`:

```py
from numba import vectorize, float64

@vectorize([float64(float64, float64)], nopython=True)
def f(x, y):
    return x + y
```

```
>>> numba.__version__
'0.57.0rc1'
```


Discussion:
Hi @oyamad, thanks for the report. I can confirm this "bug" in Numba. 

In the snippet of code below, the call to `jit` is missing `**targetoptions` expansion.

https://github.com/numba/numba/blob/be990a818c377408a1f41c37c7720bc2ba0fb89a/numba/np/ufunc/ufuncbuilder.py#L328

Numba doesn't report the warning on vectorize because of the `**targetoptions` expansion, which contains `nopython=True`

https://github.com/numba/numba/blob/be990a818c377408a1f41c37c7720bc2ba0fb89a/numba/np/ufunc/ufuncbuilder.py#L263-L265

Let me know if you'd like to send a patch fixing this issue.
Hi @oyamad, really appreciate you putting some effort towards fixing this issue. 

We discuss this topic at today's Numba meeting and we have decided that it would be best to suppress the `NumbaDeprecationWarning`. Altering the call to `jit` could potentially result in unexpected behavior, especially if different flags are passed to the (gu)vectorize decorator.

@guilhermeleobas Sure, so, in the description in https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#numba.guvectorize, the keyword arguments `nopython`, `forceobj`, and `locals` for `guvectorize` (have been and) continue to be kept ignored? I guess that should be documented?
Hi @oyamad, thanks for the report. I can confirm this "bug" in Numba. 

In the snippet of code below, the call to `jit` is missing `**targetoptions` expansion.

https://github.com/numba/numba/blob/be990a818c377408a1f41c37c7720bc2ba0fb89a/numba/np/ufunc/ufuncbuilder.py#L328

Numba doesn't report the warning on vectorize because of the `**targetoptions` expansion, which contains `nopython=True`

https://github.com/numba/numba/blob/be990a818c377408a1f41c37c7720bc2ba0fb89a/numba/np/ufunc/ufuncbuilder.py#L263-L265

Let me know if you'd like to send a patch fixing this issue.
Hi @oyamad, really appreciate you putting some effort towards fixing this issue. 

We discuss this topic at today's Numba meeting and we have decided that it would be best to suppress the `NumbaDeprecationWarning`. Altering the call to `jit` could potentially result in unexpected behavior, especially if different flags are passed to the (gu)vectorize decorator.

@guilhermeleobas Sure, so, in the description in https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#numba.guvectorize, the keyword arguments `nopython`, `forceobj`, and `locals` for `guvectorize` (have been and) continue to be kept ignored? I guess that should be documented?

Resolving patch:
diff --git a/numba/np/ufunc/dufunc.py b/numba/np/ufunc/dufunc.py
--- a/numba/np/ufunc/dufunc.py
+++ b/numba/np/ufunc/dufunc.py
@@ -81,9 +81,10 @@ class DUFunc(serialize.ReduceMixin, _internal._DUFunc):
     def __init__(self, py_func, identity=None, cache=False, targetoptions={}):
         if is_jitted(py_func):
             py_func = py_func.py_func
-        dispatcher = jit(_target='npyufunc',
-                         cache=cache,
-                         **targetoptions)(py_func)
+        with ufuncbuilder._suppress_deprecation_warning_nopython_not_supplied():
+            dispatcher = jit(_target='npyufunc',
+                             cache=cache,
+                             **targetoptions)(py_func)
         self._initialize(dispatcher, identity)
         functools.update_wrapper(self, py_func)
 
diff --git a/numba/np/ufunc/ufuncbuilder.py b/numba/np/ufunc/ufuncbuilder.py
--- a/numba/np/ufunc/ufuncbuilder.py
+++ b/numba/np/ufunc/ufuncbuilder.py
@@ -1,12 +1,14 @@
 # -*- coding: utf-8 -*-
 
 import inspect
+import warnings
 from contextlib import contextmanager
 
 from numba.core import config, targetconfig
 from numba.core.decorators import jit
 from numba.core.descriptors import TargetDescriptor
 from numba.core.extending import is_jitted
+from numba.core.errors import NumbaDeprecationWarning
 from numba.core.options import TargetOptions, include_default_options
 from numba.core.registry import cpu_target
 from numba.core.target_extension import dispatcher_registry, target_registry
@@ -230,6 +232,20 @@ def parse_identity(identity):
     return identity
 
 
+@contextmanager
+def _suppress_deprecation_warning_nopython_not_supplied():
+    """This suppresses the NumbaDeprecationWarning that occurs through the use
+    of `jit` without the `nopython` kwarg. This use of `jit` occurs in a few
+    places in the `{g,}ufunc` mechanism in Numba, predominantly to wrap the
+    "kernel" function."""
+    with warnings.catch_warnings():
+        warnings.filterwarnings('ignore',
+                                category=NumbaDeprecationWarning,
+                                message=(".*The 'nopython' keyword argument "
+                                         "was not supplied*"),)
+        yield
+
+
 # Class definitions
 
 class _BaseUFuncBuilder(object):
@@ -260,9 +276,10 @@ def __init__(self, py_func, identity=None, cache=False, targetoptions={}):
             py_func = py_func.py_func
         self.py_func = py_func
         self.identity = parse_identity(identity)
-        self.nb_func = jit(_target='npyufunc',
-                           cache=cache,
-                           **targetoptions)(py_func)
+        with _suppress_deprecation_warning_nopython_not_supplied():
+            self.nb_func = jit(_target='npyufunc',
+                               cache=cache,
+                               **targetoptions)(py_func)
         self._sigs = []
         self._cres = {}
 
@@ -325,7 +342,8 @@ def __init__(self, py_func, signature, identity=None, cache=False,
                  targetoptions={}, writable_args=()):
         self.py_func = py_func
         self.identity = parse_identity(identity)
-        self.nb_func = jit(_target='npyufunc', cache=cache)(py_func)
+        with _suppress_deprecation_warning_nopython_not_supplied():
+            self.nb_func = jit(_target='npyufunc', cache=cache)(py_func)
         self.signature = signature
         self.sin, self.sout = parse_signature(signature)
         self.targetoptions = targetoptions


Test patch:
diff --git a/numba/tests/npyufunc/test_ufunc.py b/numba/tests/npyufunc/test_ufunc.py
--- a/numba/tests/npyufunc/test_ufunc.py
+++ b/numba/tests/npyufunc/test_ufunc.py
@@ -104,7 +104,7 @@ def test_ufunc_exception_on_write_to_readonly(self):
         tests = []
         expect = "ufunc 'sin' called with an explicit output that is read-only"
         tests.append((jit(nopython=True), TypingError, expect))
-        tests.append((jit(nopython=False), ValueError,
+        tests.append((jit(forceobj=True), ValueError,
                       "output array is read-only"))
 
         for dec, exc, msg in tests:
diff --git a/numba/tests/npyufunc/test_vectorize_decor.py b/numba/tests/npyufunc/test_vectorize_decor.py
--- a/numba/tests/npyufunc/test_vectorize_decor.py
+++ b/numba/tests/npyufunc/test_vectorize_decor.py
@@ -93,7 +93,7 @@ class TestParallelVectorizeDecor(unittest.TestCase, BaseVectorizeDecor):
 
 class TestCPUVectorizeJitted(unittest.TestCase, BaseVectorizeDecor):
     target = 'cpu'
-    wrapper = jit
+    wrapper = jit(nopython=True)
 
 
 class BaseVectorizeNopythonArg(unittest.TestCase, CheckWarningsMixin):
diff --git a/numba/tests/test_deprecations.py b/numba/tests/test_deprecations.py
--- a/numba/tests/test_deprecations.py
+++ b/numba/tests/test_deprecations.py
@@ -1,9 +1,19 @@
 import warnings
-from numba import jit, generated_jit
+import unittest
+from contextlib import contextmanager
+
+from numba import jit, generated_jit, vectorize, guvectorize
 from numba.core.errors import (NumbaDeprecationWarning,
                                NumbaPendingDeprecationWarning, NumbaWarning)
-from numba.tests.support import TestCase
-import unittest
+from numba.tests.support import TestCase, needs_setuptools
+
+
+@contextmanager
+def _catch_numba_deprecation_warnings():
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter("ignore", category=NumbaWarning)
+        warnings.simplefilter("always", category=NumbaDeprecationWarning)
+        yield w
 
 
 class TestDeprecation(TestCase):
@@ -18,9 +28,7 @@ def check_warning(self, warnings, expected_str, category):
     def test_jitfallback(self):
         # tests that @jit falling back to object mode raises a
         # NumbaDeprecationWarning
-        with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter("ignore", category=NumbaWarning)
-            warnings.simplefilter("always", category=NumbaDeprecationWarning)
+        with _catch_numba_deprecation_warnings() as w:
             # ignore the warning about the nopython kwarg not being supplied
             warnings.filterwarnings("ignore",
                                     message=(r".*The 'nopython' keyword "
@@ -40,9 +48,7 @@ def foo():
     def test_default_missing_nopython_kwarg(self):
         # test that not supplying `nopython` kwarg to @jit raises a warning
         # about the default changing.
-        with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter("ignore", category=NumbaWarning)
-            warnings.simplefilter("always", category=NumbaDeprecationWarning)
+        with _catch_numba_deprecation_warnings() as w:
 
             @jit
             def foo():
@@ -58,9 +64,7 @@ def test_explicit_false_nopython_kwarg(self):
         # tests that explicitly setting `nopython=False` in @jit raises a
         # warning about the default changing and it being an error in the
         # future.
-        with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter("ignore", category=NumbaWarning)
-            warnings.simplefilter("always", category=NumbaDeprecationWarning)
+        with _catch_numba_deprecation_warnings() as w:
 
             @jit(nopython=False)
             def foo():
@@ -76,9 +80,7 @@ def test_default_missing_nopython_kwarg_silent_if_forceobj(self):
         # Checks that if forceobj is set and the nopython kwarg is also not
         # present then no warning is raised. The user intentially wants objmode.
 
-        with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter("ignore", category=NumbaWarning)
-            warnings.simplefilter("always", category=NumbaDeprecationWarning)
+        with _catch_numba_deprecation_warnings() as w:
 
             @jit(forceobj=True)
             def foo():
@@ -89,6 +91,224 @@ def foo():
         # no warnings should be raised.
         self.assertFalse(w)
 
+    @TestCase.run_test_in_subprocess
+    def test_vectorize_missing_nopython_kwarg_not_reported(self):
+        # Checks that use of @vectorize without a nopython kwarg doesn't raise
+        # a warning about lack of said kwarg.
+
+        with _catch_numba_deprecation_warnings() as w:
+            # This compiles via nopython mode directly
+            @vectorize('float64(float64)')
+            def foo(a):
+                return a + 1
+
+        self.assertFalse(w)
+
+    @TestCase.run_test_in_subprocess
+    def test_vectorize_nopython_false_is_reported(self):
+        # Checks that use of @vectorize with nopython=False raises a warning
+        # about supplying it.
+
+        with _catch_numba_deprecation_warnings() as w:
+            # This compiles via nopython mode directly
+            @vectorize('float64(float64)', nopython=False)
+            def foo(a):
+                return a + 1
+
+        msg = "The keyword argument 'nopython=False' was supplied"
+        self.check_warning(w, msg, NumbaDeprecationWarning)
+
+    @TestCase.run_test_in_subprocess
+    def test_vectorize_objmode_missing_nopython_kwarg_not_reported(self):
+        # Checks that use of @vectorize without a nopython kwarg doesn't raise
+        # a warning about lack of the nopython kwarg, but does raise a warning
+        # about falling back to obj mode to compile.
+
+        with _catch_numba_deprecation_warnings() as w:
+            # This compiles via objmode fallback
+            @vectorize('float64(float64)')
+            def foo(a):
+                object()
+                return a + 1
+
+        msg = ("Fall-back from the nopython compilation path to the object "
+               "mode compilation path")
+        self.check_warning(w, msg, NumbaDeprecationWarning)
+
+    @TestCase.run_test_in_subprocess
+    def test_vectorize_objmode_nopython_false_is_reported(self):
+        # Checks that use of @vectorize with a nopython kwarg set to False
+        # doesn't raise a warning about lack of the nopython kwarg, but does
+        # raise a warning about it being False and then a warning about falling
+        # back to obj mode to compile.
+
+        with _catch_numba_deprecation_warnings() as w:
+            # This compiles via objmode fallback with a warning about
+            # nopython=False supplied.
+            @vectorize('float64(float64)', nopython=False)
+            def foo(a):
+                object()
+                return a + 1
+
+        # 2 warnings: 1. use of nopython=False, then, 2. objmode fallback
+        self.assertEqual(len(w), 2)
+
+        msg = "The keyword argument 'nopython=False' was supplied"
+        self.check_warning([w[0]], msg, NumbaDeprecationWarning)
+
+        msg = ("Fall-back from the nopython compilation path to the object "
+               "mode compilation path")
+        self.check_warning([w[1]], msg, NumbaDeprecationWarning)
+
+    @TestCase.run_test_in_subprocess
+    def test_vectorize_objmode_direct_compilation_no_warnings(self):
+        # Checks that use of @vectorize with forceobj=True raises no warnings.
+
+        with _catch_numba_deprecation_warnings() as w:
+            # Compiles via objmode directly with no warnings raised
+            @vectorize('float64(float64)', forceobj=True)
+            def foo(a):
+                object()
+                return a + 1
+
+        self.assertFalse(w)
+
+    @TestCase.run_test_in_subprocess
+    def test_vectorize_objmode_compilation_nopython_false_no_warnings(self):
+        # Checks that use of @vectorize with forceobj set and nopython set as
+        # False raises no warnings.
+
+        with _catch_numba_deprecation_warnings() as w:
+            # Compiles via objmode directly with no warnings raised
+            @vectorize('float64(float64)', forceobj=True, nopython=False)
+            def foo(a):
+                object()
+                return a + 1
+
+        self.assertFalse(w)
+
+    @TestCase.run_test_in_subprocess
+    def test_vectorize_parallel_true_no_warnings(self):
+        # Checks that use of @vectorize with the parallel target doesn't
+        # raise warnings about nopython kwarg, the parallel target doesn't
+        # support objmode so nopython=True is implicit.
+        with _catch_numba_deprecation_warnings() as w:
+            @vectorize('float64(float64)', target='parallel')
+            def foo(x):
+                return x + 1
+
+        self.assertFalse(w)
+
+    @TestCase.run_test_in_subprocess
+    def test_vectorize_parallel_true_nopython_true_no_warnings(self):
+        # Checks that use of @vectorize with the parallel target and
+        # nopython=True doesn't raise warnings about nopython kwarg.
+        with _catch_numba_deprecation_warnings() as w:
+            @vectorize('float64(float64)', target='parallel', nopython=True)
+            def foo(x):
+                return x + 1
+
+        self.assertFalse(w)
+
+    @TestCase.run_test_in_subprocess
+    def test_vectorize_parallel_true_nopython_false_warns(self):
+        # Checks that use of @vectorize with the parallel target and
+        # nopython=False raises a warning about the nopython kwarg being False.
+        with _catch_numba_deprecation_warnings() as w:
+            @vectorize('float64(float64)', target='parallel', nopython=False)
+            def foo(x):
+                return x + 1
+
+        msg = "The keyword argument 'nopython=False' was supplied"
+        self.check_warning(w, msg, NumbaDeprecationWarning)
+
+    @TestCase.run_test_in_subprocess
+    def test_vectorize_calling_jit_with_nopython_false_warns_from_jit(self):
+        # Checks the scope of the suppression of deprecation warnings that are
+        # present in e.g. vectorize. The function `bar` should raise a
+        # deprecation warning, the `@vectorize`d `foo` function should not,
+        # even though both don't have a nopython kwarg.
+
+        # First check that the @vectorize call doesn't raise anything
+        with _catch_numba_deprecation_warnings() as w:
+            @vectorize('float64(float64)', forceobj=True)
+            def foo(x):
+                return bar(x + 1)
+
+            def bar(*args):
+                pass
+
+        self.assertFalse(w)
+
+        # Now check the same compilation but this time compiling through to a
+        # @jit call.
+        with _catch_numba_deprecation_warnings() as w:
+            @vectorize('float64(float64)', forceobj=True)
+            def foo(x): # noqa : F811
+                return bar(x + 1)
+
+            @jit
+            def bar(x): # noqa : F811
+                return x
+
+        msg = "The 'nopython' keyword argument was not supplied"
+        self.check_warning(w, msg, NumbaDeprecationWarning)
+
+    @TestCase.run_test_in_subprocess
+    def test_guvectorize_implicit_nopython_no_warnings(self):
+        # Checks that use of @guvectorize with implicit nopython compilation
+        # does not warn on compilation.
+        with _catch_numba_deprecation_warnings() as w:
+
+            @guvectorize('void(float64[::1], float64[::1])', '(n)->(n)')
+            def bar(a, b):
+                a += 1
+
+        self.assertFalse(w)
+
+    @TestCase.run_test_in_subprocess
+    def test_guvectorize_forceobj_no_warnings(self):
+        # Checks that use of @guvectorize with direct objmode compilation does
+        # not warn.
+        with _catch_numba_deprecation_warnings() as w:
+
+            @guvectorize('void(float64[::1], float64[::1])', '(n)->(n)',
+                         forceobj=True)
+            def bar(a, b):
+                object()
+                a += 1
+
+        self.assertFalse(w)
+
+    @TestCase.run_test_in_subprocess
+    def test_guvectorize_parallel_implicit_nopython_no_warnings(self):
+        # Checks that use of @guvectorize with parallel target and implicit
+        # nopython mode compilation does not warn.
+        with _catch_numba_deprecation_warnings() as w:
+
+            @guvectorize('void(float64[::1], float64[::1])', '(n)->(n)',
+                         target='parallel')
+            def bar(a, b):
+                a += 1
+
+        self.assertFalse(w)
+
+    @TestCase.run_test_in_subprocess
+    def test_guvectorize_parallel_forceobj_no_warnings(self):
+        # Checks that use of @guvectorize with parallel target and direct
+        # objmode compilation does not warn.
+        with _catch_numba_deprecation_warnings() as w:
+
+            # This compiles somewhat surprisingly for the parallel target using
+            # object mode?!
+            @guvectorize('void(float64[::1], float64[::1])', '(n)->(n)',
+                         target='parallel', forceobj=True)
+            def bar(a, b):
+                object()
+                a += 1
+
+        self.assertFalse(w)
+
     @TestCase.run_test_in_subprocess
     def test_reflection_of_mutable_container(self):
         # tests that reflection in list/set warns
@@ -119,9 +339,7 @@ def foo_set(a):
     @TestCase.run_test_in_subprocess
     def test_generated_jit(self):
 
-        with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter("ignore", category=NumbaWarning)
-            warnings.simplefilter("always", category=NumbaDeprecationWarning)
+        with _catch_numba_deprecation_warnings() as w:
 
             @generated_jit
             def bar():
@@ -136,6 +354,7 @@ def foo():
             self.check_warning(w, "numba.generated_jit is deprecated",
                                NumbaDeprecationWarning)
 
+    @needs_setuptools
     @TestCase.run_test_in_subprocess
     def test_pycc_module(self):
         # checks import of module warns
@@ -148,6 +367,7 @@ def test_pycc_module(self):
             expected_str = ("The 'pycc' module is pending deprecation.")
             self.check_warning(w, expected_str, NumbaPendingDeprecationWarning)
 
+    @needs_setuptools
     @TestCase.run_test_in_subprocess
     def test_pycc_CC(self):
         # check the most commonly used functionality (CC) warns