Repository: numba/numba

Issue:
LiteralSlice.indices errors
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I have tried 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 a self contained code sample to reproduce the problem.
  i.e. it's possible to run as 'python bug.py'.

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->

```python
from numba import njit

@njit
def foo(x=slice(None)):
    return x.indices(5)

# Working, expected output
foo(slice(None))
# (0, 5, 1)

# When the default argument is used
foo()
# LoweringError: Failed in nopython mode pipeline (step: nopython mode backend)
```

<details>
<summary> Full traceback </summary>

```pytb
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
/usr/local/lib/python3.8/site-packages/numba/core/base.py in get_constant_generic(self, builder, ty, val)
    517         try:
--> 518             impl = self._get_constants.find((ty,))
    519             return impl(self, builder, ty, val)

/usr/local/lib/python3.8/site-packages/numba/core/base.py in find(self, sig)
     48         if out is None:
---> 49             out = self._find(sig)
     50             self._cache[sig] = out

/usr/local/lib/python3.8/site-packages/numba/core/base.py in _find(self, sig)
     57         else:
---> 58             raise NotImplementedError(self, sig)
     59 

NotImplementedError: (<numba.core.base.OverloadSelector object at 0x116df7ca0>, (Literal[slice](slice(None, None, None)),))

During handling of the above exception, another exception occurred:

NotImplementedError                       Traceback (most recent call last)
/usr/local/lib/python3.8/site-packages/numba/core/errors.py in new_error_context(fmt_, *args, **kwargs)
    743     try:
--> 744         yield
    745     except NumbaError as e:

/usr/local/lib/python3.8/site-packages/numba/core/lowering.py in lower_block(self, block)
    229                                    loc=self.loc, errcls_=defaulterrcls):
--> 230                 self.lower_inst(inst)
    231         self.post_block(block)

/usr/local/lib/python3.8/site-packages/numba/core/lowering.py in lower_inst(self, inst)
    326             ty = self.typeof(inst.target.name)
--> 327             val = self.lower_assign(ty, inst)
    328             self.storevar(val, inst.target.name)

/usr/local/lib/python3.8/site-packages/numba/core/lowering.py in lower_assign(self, ty, inst)
    519                 # use the type of the constant value
--> 520                 const = self.context.get_constant_generic(
    521                     self.builder, valty, pyval,

/usr/local/lib/python3.8/site-packages/numba/core/base.py in get_constant_generic(self, builder, ty, val)
    520         except NotImplementedError:
--> 521             raise NotImplementedError("Cannot lower constant of type '%s'" % (ty,))
    522 

NotImplementedError: Cannot lower constant of type 'Literal[slice](slice(None, None, None))'

During handling of the above exception, another exception occurred:

LoweringError                             Traceback (most recent call last)
<ipython-input-4-c19b6d9633cf> in <module>
----> 1 foo()

/usr/local/lib/python3.8/site-packages/numba/core/dispatcher.py in _compile_for_args(self, *args, **kws)
    431                     e.patch_message('\n'.join((str(e).rstrip(), help_msg)))
    432             # ignore the FULL_TRACEBACKS config, this needs reporting!
--> 433             raise e
    434 
    435     def inspect_llvm(self, signature=None):

/usr/local/lib/python3.8/site-packages/numba/core/dispatcher.py in _compile_for_args(self, *args, **kws)
    364                 argtypes.append(self.typeof_pyval(a))
    365         try:
--> 366             return self.compile(tuple(argtypes))
    367         except errors.ForceLiteralArg as e:
    368             # Received request for compiler re-entry with the list of arguments

/usr/local/lib/python3.8/site-packages/numba/core/compiler_lock.py in _acquire_compile_lock(*args, **kwargs)
     30         def _acquire_compile_lock(*args, **kwargs):
     31             with self:
---> 32                 return func(*args, **kwargs)
     33         return _acquire_compile_lock
     34 

/usr/local/lib/python3.8/site-packages/numba/core/dispatcher.py in compile(self, sig)
    855             self._cache_misses[sig] += 1
    856             try:
--> 857                 cres = self._compiler.compile(args, return_type)
    858             except errors.ForceLiteralArg as e:
    859                 def folded(args, kws):

/usr/local/lib/python3.8/site-packages/numba/core/dispatcher.py in compile(self, args, return_type)
     75 
     76     def compile(self, args, return_type):
---> 77         status, retval = self._compile_cached(args, return_type)
     78         if status:
     79             return retval

/usr/local/lib/python3.8/site-packages/numba/core/dispatcher.py in _compile_cached(self, args, return_type)
     89 
     90         try:
---> 91             retval = self._compile_core(args, return_type)
     92         except errors.TypingError as e:
     93             self._failed_cache[key] = e

/usr/local/lib/python3.8/site-packages/numba/core/dispatcher.py in _compile_core(self, args, return_type)
    102 
    103         impl = self._get_implementation(args, {})
--> 104         cres = compiler.compile_extra(self.targetdescr.typing_context,
    105                                       self.targetdescr.target_context,
    106                                       impl,

/usr/local/lib/python3.8/site-packages/numba/core/compiler.py in compile_extra(typingctx, targetctx, func, args, return_type, flags, locals, library, pipeline_class)
    600     pipeline = pipeline_class(typingctx, targetctx, library,
    601                               args, return_type, flags, locals)
--> 602     return pipeline.compile_extra(func)
    603 
    604 

/usr/local/lib/python3.8/site-packages/numba/core/compiler.py in compile_extra(self, func)
    350         self.state.lifted = ()
    351         self.state.lifted_from = None
--> 352         return self._compile_bytecode()
    353 
    354     def compile_ir(self, func_ir, lifted=(), lifted_from=None):

/usr/local/lib/python3.8/site-packages/numba/core/compiler.py in _compile_bytecode(self)
    412         """
    413         assert self.state.func_ir is None
--> 414         return self._compile_core()
    415 
    416     def _compile_ir(self):

/usr/local/lib/python3.8/site-packages/numba/core/compiler.py in _compile_core(self)
    392                 self.state.status.fail_reason = e
    393                 if is_final_pipeline:
--> 394                     raise e
    395         else:
    396             raise CompilerError("All available pipelines exhausted")

/usr/local/lib/python3.8/site-packages/numba/core/compiler.py in _compile_core(self)
    383             res = None
    384             try:
--> 385                 pm.run(self.state)
    386                 if self.state.cr is not None:
    387                     break

/usr/local/lib/python3.8/site-packages/numba/core/compiler_machinery.py in run(self, state)
    337                     (self.pipeline_name, pass_desc)
    338                 patched_exception = self._patch_error(msg, e)
--> 339                 raise patched_exception
    340 
    341     def dependency_analysis(self):

/usr/local/lib/python3.8/site-packages/numba/core/compiler_machinery.py in run(self, state)
    328                 pass_inst = _pass_registry.get(pss).pass_inst
    329                 if isinstance(pass_inst, CompilerPass):
--> 330                     self._runPass(idx, pass_inst, state)
    331                 else:
    332                     raise BaseException("Legacy pass in use")

/usr/local/lib/python3.8/site-packages/numba/core/compiler_lock.py in _acquire_compile_lock(*args, **kwargs)
     30         def _acquire_compile_lock(*args, **kwargs):
     31             with self:
---> 32                 return func(*args, **kwargs)
     33         return _acquire_compile_lock
     34 

/usr/local/lib/python3.8/site-packages/numba/core/compiler_machinery.py in _runPass(self, index, pss, internal_state)
    287             mutated |= check(pss.run_initialization, internal_state)
    288         with SimpleTimer() as pass_time:
--> 289             mutated |= check(pss.run_pass, internal_state)
    290         with SimpleTimer() as finalize_time:
    291             mutated |= check(pss.run_finalizer, internal_state)

/usr/local/lib/python3.8/site-packages/numba/core/compiler_machinery.py in check(func, compiler_state)
    260 
    261         def check(func, compiler_state):
--> 262             mangled = func(compiler_state)
    263             if mangled not in (True, False):
    264                 msg = ("CompilerPass implementations should return True/False. "

/usr/local/lib/python3.8/site-packages/numba/core/typed_passes.py in run_pass(self, state)
    447 
    448         # TODO: Pull this out into the pipeline
--> 449         NativeLowering().run_pass(state)
    450         lowered = state['cr']
    451         signature = typing.signature(state.return_type, *state.args)

/usr/local/lib/python3.8/site-packages/numba/core/typed_passes.py in run_pass(self, state)
    371                 lower = lowering.Lower(targetctx, library, fndesc, interp,
    372                                        metadata=metadata)
--> 373                 lower.lower()
    374                 if not flags.no_cpython_wrapper:
    375                     lower.create_cpython_wrapper(flags.release_gil)

/usr/local/lib/python3.8/site-packages/numba/core/lowering.py in lower(self)
    134         if self.generator_info is None:
    135             self.genlower = None
--> 136             self.lower_normal_function(self.fndesc)
    137         else:
    138             self.genlower = self.GeneratorLower(self)

/usr/local/lib/python3.8/site-packages/numba/core/lowering.py in lower_normal_function(self, fndesc)
    188         # Init argument values
    189         self.extract_function_arguments()
--> 190         entry_block_tail = self.lower_function_body()
    191 
    192         # Close tail of entry block

/usr/local/lib/python3.8/site-packages/numba/core/lowering.py in lower_function_body(self)
    214             bb = self.blkmap[offset]
    215             self.builder.position_at_end(bb)
--> 216             self.lower_block(block)
    217         self.post_lower()
    218         return entry_block_tail

/usr/local/lib/python3.8/site-packages/numba/core/lowering.py in lower_block(self, block)
    228             with new_error_context('lowering "{inst}" at {loc}', inst=inst,
    229                                    loc=self.loc, errcls_=defaulterrcls):
--> 230                 self.lower_inst(inst)
    231         self.post_block(block)
    232 

/usr/local/Cellar/python@3.8/3.8.6_2/Frameworks/Python.framework/Versions/3.8/lib/python3.8/contextlib.py in __exit__(self, type, value, traceback)
    129                 value = type()
    130             try:
--> 131                 self.gen.throw(type, value, traceback)
    132             except StopIteration as exc:
    133                 # Suppress StopIteration *unless* it's the same exception that

/usr/local/lib/python3.8/site-packages/numba/core/errors.py in new_error_context(fmt_, *args, **kwargs)
    749         newerr = errcls(e).add_context(_format_msg(fmt_, args, kwargs))
    750         tb = sys.exc_info()[2] if numba.core.config.FULL_TRACEBACKS else None
--> 751         raise newerr.with_traceback(tb)
    752 
    753 

LoweringError: Failed in nopython mode pipeline (step: nopython mode backend)
Cannot lower constant of type 'Literal[slice](slice(None, None, None))'

File "<ipython-input-2-5958ebc3482d>", line 3:
def foo(x=slice(None)):
    return x.indices(5)
    ^

During: lowering "x = arg(0, name=x)" at <ipython-input-2-5958ebc3482d> (3)
```

</details>


Discussion:
Bug confirmed.

Resolving patch:
diff --git a/numba/core/datamodel/models.py b/numba/core/datamodel/models.py
--- a/numba/core/datamodel/models.py
+++ b/numba/core/datamodel/models.py
@@ -1077,6 +1077,7 @@ def __init__(self, dmm, fe_type):
         super(UniTupleIter, self).__init__(dmm, fe_type, members)
 
 
+@register_default(types.misc.SliceLiteral)
 @register_default(types.SliceType)
 class SliceModel(StructModel):
     def __init__(self, dmm, fe_type):
diff --git a/numba/core/types/misc.py b/numba/core/types/misc.py
--- a/numba/core/types/misc.py
+++ b/numba/core/types/misc.py
@@ -334,6 +334,11 @@ def __init__(self, value):
         members = 2 if value.step is None else 3
         SliceType.__init__(self, name=name, members=members)
 
+    @property
+    def key(self):
+        sl = self.literal_value
+        return sl.start, sl.stop, sl.step
+
 
 Literal.ctor_map[slice] = SliceLiteral
 
diff --git a/numba/cpython/slicing.py b/numba/cpython/slicing.py
--- a/numba/cpython/slicing.py
+++ b/numba/cpython/slicing.py
@@ -5,11 +5,11 @@
 from itertools import zip_longest
 
 from llvmlite import ir
-
-from numba.core import types, typing, utils, cgutils
-from numba.core.imputils import (lower_builtin, lower_getattr,
-                                    iternext_impl, impl_ret_borrowed,
-                                    impl_ret_new_ref, impl_ret_untracked)
+from numba.core import cgutils, types, typing, utils
+from numba.core.imputils import (impl_ret_borrowed, impl_ret_new_ref,
+                                 impl_ret_untracked, iternext_impl,
+                                 lower_builtin, lower_cast, lower_constant,
+                                 lower_getattr)
 
 
 def fix_index(builder, idx, size):
@@ -150,8 +150,13 @@ def get_defaults(context):
 
 @lower_builtin(slice, types.VarArg(types.Any))
 def slice_constructor_impl(context, builder, sig, args):
-    default_start_pos, default_start_neg, default_stop_pos, default_stop_neg, default_step = \
-        [context.get_constant(types.intp, x) for x in get_defaults(context)]
+    (
+        default_start_pos,
+        default_start_neg,
+        default_stop_pos,
+        default_stop_neg,
+        default_step,
+    ) = [context.get_constant(types.intp, x) for x in get_defaults(context)]
 
     slice_args = [None] * 3
 
@@ -233,3 +238,65 @@ def slice_indices(context, builder, sig, args):
         sig.return_type,
         (sli.start, sli.stop, sli.step)
     )
+
+
+def make_slice_from_constant(context, builder, ty, pyval):
+    sli = context.make_helper(builder, ty)
+    lty = context.get_value_type(types.intp)
+
+    (
+        default_start_pos,
+        default_start_neg,
+        default_stop_pos,
+        default_stop_neg,
+        default_step,
+    ) = [context.get_constant(types.intp, x) for x in get_defaults(context)]
+
+    step = pyval.step
+    if step is None:
+        step_is_neg = False
+        step = default_step
+    else:
+        step_is_neg = step < 0
+        step = lty(step)
+
+    start = pyval.start
+    if start is None:
+        if step_is_neg:
+            start = default_start_neg
+        else:
+            start = default_start_pos
+    else:
+        start = lty(start)
+
+    stop = pyval.stop
+    if stop is None:
+        if step_is_neg:
+            stop = default_stop_neg
+        else:
+            stop = default_stop_pos
+    else:
+        stop = lty(stop)
+
+    sli.start = start
+    sli.stop = stop
+    sli.step = step
+
+    return sli._getvalue()
+
+
+@lower_constant(types.SliceType)
+def constant_slice(context, builder, ty, pyval):
+    if isinstance(ty, types.Literal):
+        typ = ty.literal_type
+    else:
+        typ = ty
+
+    return make_slice_from_constant(context, builder, typ, pyval)
+
+
+@lower_cast(types.misc.SliceLiteral, types.SliceType)
+def cast_from_literal(context, builder, fromty, toty, val):
+    return make_slice_from_constant(
+        context, builder, toty, fromty.literal_value,
+    )


Test patch:
diff --git a/numba/tests/test_parfors.py b/numba/tests/test_parfors.py
--- a/numba/tests/test_parfors.py
+++ b/numba/tests/test_parfors.py
@@ -3433,6 +3433,32 @@ def test_impl():
 
         self.check(test_impl)
 
+    @skip_parfors_unsupported
+    def test_parfor_array_access_lower_slice(self):
+        for ts in [slice(1, 3, None), slice(2, None, None), slice(None, 2, -1),
+                   slice(None, None, None), slice(None, None, -2)]:
+
+            def test_impl(n):
+                X = np.arange(n * 4).reshape((n, 4))
+                y = 0
+                for i in numba.prange(n):
+                    y += X[i, ts].sum()
+                return y
+
+            n = 10
+            self.check(test_impl, n)
+
+            X = np.arange(n * 4).reshape((n, 4))
+
+            def test_impl(X):
+                y = 0
+                for i in numba.prange(X.shape[0]):
+                    y += X[i, ts].sum()
+                return y
+
+            self.check(test_impl, X)
+
+
 
 class TestParforsOptions(TestParforsBase):
 
diff --git a/numba/tests/test_slices.py b/numba/tests/test_slices.py
--- a/numba/tests/test_slices.py
+++ b/numba/tests/test_slices.py
@@ -6,7 +6,7 @@
 import numpy as np
 
 from numba import jit, typeof, TypingError
-from numba.core import utils
+from numba.core import utils, types
 from numba.tests.support import TestCase, MemoryLeakMixin
 from numba.core.types.functions import _header_lead
 import unittest
@@ -185,6 +185,28 @@ def test_slice_indices_examples(self):
             str(e.exception)
         )
 
+    def test_slice_from_constant(self):
+        test_tuple = (1, 2, 3, 4)
+
+        for ts in itertools.product(
+            [None, 1, 2, 3], [None, 1, 2, 3], [None, 1, 2, -1, -2]
+        ):
+            ts = slice(*ts)
+
+            @jit(nopython=True)
+            def test_fn():
+                return test_tuple[ts]
+
+            self.assertEqual(test_fn(), test_fn.py_func())
+
+    def test_literal_slice_distinct(self):
+        sl1 = types.misc.SliceLiteral(slice(1, None, None))
+        sl2 = types.misc.SliceLiteral(slice(None, None, None))
+        sl3 = types.misc.SliceLiteral(slice(1, None, None))
+
+        self.assertNotEqual(sl1, sl2)
+        self.assertEqual(sl1, sl3)
+
 
 if __name__ == '__main__':
     unittest.main()