Repository: numba/numba

Issue:
Consider deprecating `numba.generated_jit`
The decorator `@numba.generated_jit` provides similar functionality to the much more powerful `@numba.extending.overload` decorator. This issue discusses issues with the `@generated_jit` functionality and is to track decisions, if any, over deprecation in favour of just using `@overload`.

The `@generated_jit` decorator essentially provides functionality to allow type-based selection of an implementation at compile time. Example:

```python
from numba import njit, generated_jit, types

@generated_jit
def select(x):
    if isinstance(x, types.Float):
        def impl(x):
            return x + 1
        return impl
    elif isinstance(x, types.UnicodeType):
        def impl(x):
            return x + " the number one"
        return impl
    else:
        raise TypeError("Unsupported Type")

@njit
def foo(x):
    return select(x)

print(foo(1.))
print(foo("a string"))
```
A few issues exist with the concept/implementation:
1. `generated_jit` use tends to break "JIT transparency", i.e. if the `@jit` family decorators are removed or the environment variable `NUMBA_DISABLE_JIT` is set, then the source code does not execute in the same way as it would were the JIT compiler present (xref: #4759).
2. Various flags etc are not supported by `@generated_jit`, for example inlining (xref: #4692).
3. I've not proved this as yet, but would guess based on the implementation, that the target specific overload functionality would probably not work as expected.

Conceptually, `@generated_jit` is like `@overload` but the overloaded function is the decorated function, as a result, there already exists a replacement for `@generated_jit`, it's `@overload`. Taking the example above and turning it into an `@overload`:
```python
from numba import njit, types
from numba.extending import overload

def select(x):
    if isinstance(x, float):
        return x + 1
    elif isinstance(x, str):
        return x + " the number one"
    else:
        raise TypeError("Unsupported Type")

@overload(select)
def ol_select(x):
    if isinstance(x, types.Float):
        def impl(x):
            return x + 1
        return impl
    elif isinstance(x, types.UnicodeType):
        def impl(x):
            return x + " the number one"
        return impl
    else:
        raise TypeError("Unsupported Type")

@njit
def foo(x):
    return select(x)

print(foo(1.))
print(foo("a string"))
```

Further, since preliminary support for `isinstance` has been added, along with advances in branch pruning, the necessity for `@generated_jit` in basic cases like the above has decreased, for example, this is a shorter version of the above.

```python
from numba import njit, types
from numba.extending import overload

def select(x):
    if isinstance(x, float):
        return x + 1
    elif isinstance(x, str):
        return x + " the number one"
    else:
        raise TypeError("Unsupported Type")

overload(select)(lambda x: select)

@njit
def foo(x):
    return select(x)

print(foo(1.))
print(foo("a string"))
```

Final points...
* The CUDA target (and any other target implemented through the target specific functionality) now supports `@overload` and `@intrinsic` and will inherit all the effort that has been put into those decorators, trying to make `@generated_jit` work seems unnecessary given the replacement already exists.
* From a maintenance perspective, reducing the public facing API reduces maintenance burden, however, `@generated_jit` is not particularly involved in part because it is not as heavily developed as `@overload`.

Considerations:
1. Is there any functionality that `@generated_jit` provides that `@overload` does not?
2. Does `@overload` perhaps need an update to assist with 1. ?
3. How much of an impact would deprecation have against existing code bases?
    1. Need to find prevalence of use.
    2. Need to work out how hard the translation would be/should a wrapper through `@overload` be provided?
5. If deprecated, how long a cycle is required?


Discussion:
#8467 removes internal uses of `generated_jit`.
From discussion at the Numba public meeting today.
1. It was agreed that `@numba.extending.overload` does indeed provide the same functionality but is much better supported on a number of fronts.
2. An attempt is going to be made to try and implement `@generated_jit` using `@overload` internally so as to try and make it more stable.
3. Invariant of 2. the `generated_jit` function is going to be deprecated, probably with a long-ish cycle as there seems to be quite a bit of use but this is quite hard to quantify exactly.

Resolving patch:
diff --git a/numba/core/decorators.py b/numba/core/decorators.py
--- a/numba/core/decorators.py
+++ b/numba/core/decorators.py
@@ -231,6 +231,13 @@ def generated_jit(function=None, cache=False,
     function is called at compile-time with the *types* of the arguments
     and should return an implementation function for those types.
     """
+    url_s = "https://numba.readthedocs.io/en/stable/reference/deprecation.html"
+    url_anchor = "#deprecation-of-generated-jit"
+    url = f"{url_s}{url_anchor}"
+    msg = ("numba.generated_jit is deprecated. Please see the documentation "
+           f"at: {url} for more information and advice on a suitable "
+           "replacement.")
+    warnings.warn(msg, NumbaDeprecationWarning)
     dispatcher_args = {}
     if pipeline_class is not None:
         dispatcher_args['pipeline_class'] = pipeline_class


Test patch:
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,11 +1,12 @@
 import warnings
-from numba import jit
+from numba import jit, generated_jit
 from numba.core.errors import (NumbaDeprecationWarning,
                           NumbaPendingDeprecationWarning, NumbaWarning)
+from numba.tests.support import TestCase
 import unittest
 
 
-class TestDeprecation(unittest.TestCase):
+class TestDeprecation(TestCase):
 
     def check_warning(self, warnings, expected_str, category):
         self.assertEqual(len(warnings), 1)
@@ -13,6 +14,7 @@ def check_warning(self, warnings, expected_str, category):
         self.assertIn(expected_str, str(warnings[0].message))
         self.assertIn("https://numba.readthedocs.io", str(warnings[0].message))
 
+    @TestCase.run_test_in_subprocess
     def test_jitfallback(self):
         # tests that @jit falling back to object mode raises a
         # NumbaDeprecationWarning
@@ -28,6 +30,7 @@ def foo():
                    "mode compilation path")
             self.check_warning(w, msg, NumbaDeprecationWarning)
 
+    @TestCase.run_test_in_subprocess
     def test_reflection_of_mutable_container(self):
         # tests that reflection in list/set warns
         def foo_list(a):
@@ -54,6 +57,26 @@ def foo_set(a):
                 self.assertIn(msg, warn_msg)
                 self.assertIn("https://numba.readthedocs.io", warn_msg)
 
+    @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)
+
+            @generated_jit
+            def bar():
+                return lambda : None
+
+            @jit(nopython=True)
+            def foo():
+                bar()
+
+            foo()
+
+            self.check_warning(w, "numba.generated_jit is deprecated",
+                               NumbaDeprecationWarning)
+
 
 if __name__ == '__main__':
     unittest.main()