Repository: numba/numba

Issue:
Regression with literally and overloads with inline='always'
- [x] I am using the latest released version of Numba (most recent is visible in
 the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included below a minimal working reproducer (if you are unsure how
 to write one see http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports).

Hi, 

While trying to migrate to Numba 0.50 we encountered the following problem with literally that may now fail if called from overloads compiled with inline='always'. The error itself looks like this:
```
DEBUG: dtype= unicode_type
Traceback (most recent call last):
  File "test_inline_literally.py", line 36, in <module>
    res = test_impl(data, dtype)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\numba\core\dispatcher.py", line 401, in _compile_for_args
    error_rewrite(e, 'typing')
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\numba\core\dispatcher.py", line 344, in error_rewrite
    reraise(type(e), e, None)
  File "C:\Users\akozlov\AppData\Local\Continuum\anaconda3\numba_master\numba\numba\core\utils.py", line 79, in reraise
    raise value.with_traceback(tb)
numba.core.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Internal error at <numba.core.typeinfer.CallConstraint object at 0x0000023CF5AA24F0>.
'NoneType' object is not callable
[1] During: resolving callee type: Function(<function foo at 0x0000023CF24E5280>)
[2] During: typing of call at test_inline_literally.py (32)

Enable logging at debug level for details.

File "test_inline_literally.py", line 32:
def test_impl(arr, dtype):
    return foo(arr, dtype)
    ^
```

And below is the reproducer:
```
import numba
import numpy as np
from numba.extending import overload
from numba import types
from numba import literally


def foo(val, dtype):
    pass

@overload(foo, inline='always')    # seen with inline='always' only
def foo_ovld(arr, dtype):

    print("DEBUG: dtype=", dtype)
    if not isinstance(dtype, types.StringLiteral):
        def foo_noop(arr, dtype):
            return literally(dtype)
        return foo_noop

    if dtype.literal_value == 'str':
        def foo_as_str_impl(arr, dtype):
            return [str(x) for x in arr]
        return foo_as_str_impl

    if dtype.literal_value in ('int64', 'float64'):
        def foo_as_num_impl(arr, dtype):
            return list(arr.astype(np.dtype(dtype)))
        return foo_as_num_impl

@numba.njit
def test_impl(arr, dtype):
    return foo(arr, dtype)

data = np.asarray([1, 2, 3, 2, 4], dtype=np.int32)
dtype = 'int64'
print(test_impl(data, dtype))
```

The issue was found on 0.50.0rc1 but still remains in 0.51.0.dev0+56.g92df8df57
It seems it is somehow related to #5673 as it's not observed before it, but please note that even though test from our code base always passes on 0.49.1 the above script which is just simplified version might occasionally fail with 'Repeated literal typing request' as below:
```
During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test_inline_literally_2.py", line 20, in <module>
    res = test_impl(data, dtype)
  File "c:\users\akozlov\appdata\local\continuum\anaconda3\numba_master\numba\numba\core\dispatcher.py", line 376, in _compile_for_args
    return self._compile_for_args(*args)
  File "c:\users\akozlov\appdata\local\continuum\anaconda3\numba_master\numba\numba\core\dispatcher.py", line 369, in _compile_for_args
    raise errors.CompilerError(m.format(info))
numba.core.errors.CompilerError: Repeated literal typing request.
Arg #1 is Literal[str](int64).
This is likely caused by an error in typing. Please see nested and suppressed exceptions.
```
Not sure if this latter is a known issue and wasn't fixed already (please confirm if it's so).
But the main issue is of course new one _'NoneType' object is not callable_ problem.

Best Regards,
Alexey.



Discussion:
Thanks for the report. Is this the same problem as discussed, where `literally` wasn't forcing re-dispatch or is this just the error seen in use with cause unknown?

Resolving patch:
diff --git a/numba/core/typing/templates.py b/numba/core/typing/templates.py
--- a/numba/core/typing/templates.py
+++ b/numba/core/typing/templates.py
@@ -517,9 +517,17 @@ def generic(self, args, kws):
             inline_worker = InlineWorker(tyctx, tgctx, fcomp.locals,
                                          compiler_inst, flags, None,)
 
-            ir = inline_worker.run_untyped_passes(disp_type.dispatcher.py_func)
+            # If the inlinee contains something to trigger literal arg dispatch
+            # then the pipeline call will unconditionally fail due to a raised
+            # ForceLiteralArg exception. Therefore `resolve` is run first, as
+            # type resolution must occur at some point, this will hit any
+            # `literally` calls and because it's going via the dispatcher will
+            # handle them correctly i.e. ForceLiteralArg propagates. This having
+            # the desired effect of ensuring the pipeline call is only made in
+            # situations that will succeed. For context see #5887.
             resolve = disp_type.dispatcher.get_call_template
             template, pysig, folded_args, kws = resolve(new_args, kws)
+            ir = inline_worker.run_untyped_passes(disp_type.dispatcher.py_func)
 
             typemap, return_type, calltypes = typed_passes.type_inference_stage(
                 self.context, ir, folded_args, None)


Test patch:
diff --git a/numba/tests/test_ir_inlining.py b/numba/tests/test_ir_inlining.py
--- a/numba/tests/test_ir_inlining.py
+++ b/numba/tests/test_ir_inlining.py
@@ -6,7 +6,7 @@
 from itertools import product
 import numpy as np
 
-from numba import njit, typeof
+from numba import njit, typeof, literally
 from numba.core import types, ir, ir_utils, cgutils
 from numba.core.extending import (
     overload,
@@ -816,6 +816,48 @@ def impl():
         for val in consts:
             self.assertNotEqual(val.value, 1234)
 
+    def test_overload_inline_always_with_literally_in_inlinee(self):
+        # See issue #5887
+
+        def foo_ovld(dtype):
+
+            if not isinstance(dtype, types.StringLiteral):
+                def foo_noop(dtype):
+                    return literally(dtype)
+                return foo_noop
+
+            if dtype.literal_value == 'str':
+                def foo_as_str_impl(dtype):
+                    return 10
+                return foo_as_str_impl
+
+            if dtype.literal_value in ('int64', 'float64'):
+                def foo_as_num_impl(dtype):
+                    return 20
+                return foo_as_num_impl
+
+        # define foo for literal str 'str'
+        def foo(dtype):
+            return 10
+
+        overload(foo, inline='always')(foo_ovld)
+
+        def test_impl(dtype):
+            return foo(dtype)
+
+        # check literal dispatch on 'str'
+        dtype = 'str'
+        self.check(test_impl, dtype, inline_expect={'foo': True})
+
+        # redefine foo to be correct for literal str 'int64'
+        def foo(dtype):
+            return 20
+        overload(foo, inline='always')(foo_ovld)
+
+        # check literal dispatch on 'int64'
+        dtype = 'int64'
+        self.check(test_impl, dtype, inline_expect={'foo': True})
+
 
 class TestOverloadMethsAttrsInlining(InliningBase):
     def setUp(self):