Repository: numba/numba

Issue:
slice constructor inconsistent with python
While working on #4707, I noticed the `slice` constructor behaves differently in python code vs numba code. This occurs on bdd8e312a24c5b76e and `v0.46.0`. Here's a couple examples:

```python
from numba import njit

def slice_no_args(): slice()
def slice_one_arg(): slice(10)

cslice_no_args = njit(slice_no_args)
cslice_one_arg = njit(slice_one_arg)

slice_no_args()
# TypeError: slice expected at least 1 arguments, got 0
cslice_no_args()  # Runs fine

slice_one_arg()  # Runs fine
cslice_one_arg()
# TypingError: Failed in nopython mode pipeline (step: nopython frontend)
# Invalid use of Function(<class 'slice'>) with argument(s) of type(s): (Literal[int](10))
```

I think I've figured out how to make the behavior more consistent and would be happy to open a PR. I wanted to get dev opinion on this first, since I'm not sure if changing `slice()` to an error would be breaking (update: it actually gets tested [here](https://github.com/numba/numba/blob/a01986f1a2d9082aa88e8898049e9d55969124cb/numba/tests/test_slices.py#L64)).

I think the total changes would be:

* Add a case for 1 argument [here](https://github.com/numba/numba/blob/a01986f1a2d9082aa88e8898049e9d55969124cb/numba/targets/slicing.py#L157-L163)
* Remove 0-arity signature and add a 1-arity signatures for `None` and `int` [here](https://github.com/numba/numba/blob/a01986f1a2d9082aa88e8898049e9d55969124cb/numba/typing/builtins.py#L46-L58)






Discussion:
Thanks for the report. Looking at the known signatures:
```
Known signatures:
 * () -> slice<a:b>
 * (none, none) -> slice<a:b>
 * (none, int64) -> slice<a:b>
 * (int64, none) -> slice<a:b>
 * (int64, int64) -> slice<a:b>
 * (int64, int64, int64) -> slice<a:b:c>
 * (none, int64, int64) -> slice<a:b:c>
 * (int64, none, int64) -> slice<a:b:c>
 * (none, none, int64) -> slice<a:b:c>
```
explains the issues reported above. I just went and checked Python 2 to see if it was some inherited difference and it is not. I think fixing this to be correct is probably a good idea else people's code won't work if they turn off the JIT.
Great! I think I've got this fixed in a branch on my machine. The branch uses some of the testing code from #4707 so I'm thinking I'll wait on that first.

Resolving patch:
diff --git a/numba/targets/slicing.py b/numba/targets/slicing.py
--- a/numba/targets/slicing.py
+++ b/numba/targets/slicing.py
@@ -154,13 +154,15 @@ 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)]
 
-    # Fetch non-None arguments
     slice_args = [None] * 3
-    for i, (ty, val) in enumerate(zip(sig.args, args)):
-        if ty is types.none:
-            slice_args[i] = None
-        else:
-            slice_args[i] = val
+
+    # Fetch non-None arguments
+    if len(args) == 1 and sig.args[0] is not types.none:
+        slice_args[1] = args[0]
+    else:
+        for i, (ty, val) in enumerate(zip(sig.args, args)):
+            if ty is not types.none:
+                slice_args[i] = val
 
     # Fill omitted arguments
     def get_arg_value(i, default):
diff --git a/numba/typing/builtins.py b/numba/typing/builtins.py
--- a/numba/typing/builtins.py
+++ b/numba/typing/builtins.py
@@ -46,7 +46,8 @@ class Abs(ConcreteTemplate):
 @infer_global(slice)
 class Slice(ConcreteTemplate):
     cases = [
-        signature(types.slice2_type),
+        signature(types.slice2_type, types.intp),
+        signature(types.slice2_type, types.none),
         signature(types.slice2_type, types.none, types.none),
         signature(types.slice2_type, types.none, types.intp),
         signature(types.slice2_type, types.intp, types.none),
@@ -54,7 +55,11 @@ class Slice(ConcreteTemplate):
         signature(types.slice3_type, types.intp, types.intp, types.intp),
         signature(types.slice3_type, types.none, types.intp, types.intp),
         signature(types.slice3_type, types.intp, types.none, types.intp),
+        signature(types.slice3_type, types.intp, types.intp, types.none),
+        signature(types.slice3_type, types.intp, types.none, types.none),
+        signature(types.slice3_type, types.none, types.intp, types.none),
         signature(types.slice3_type, types.none, types.none, types.intp),
+        signature(types.slice3_type, types.none, types.none, types.none),
     ]
 
 


Test patch:
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
@@ -1,9 +1,12 @@
 from __future__ import print_function, division, absolute_import
 
+from functools import partial
 import itertools
 from itertools import chain, product, starmap
 import sys
 
+import numpy as np
+
 from numba import unittest_support as unittest
 from numba import jit, typeof, utils, TypingError
 from .support import TestCase, MemoryLeakMixin
@@ -16,10 +19,13 @@ def slice_constructor(*args):
     sl = slice(*args)
     return sl.start, sl.stop, sl.step
 
+def slice_construct_and_use(args, l):
+    sl = slice(*args)
+    return l[sl]
+
 def slice_indices(s, *indargs):
     return s.indices(*indargs)
 
-
 class TestSlices(MemoryLeakMixin, TestCase):
 
     def test_slice_passing(self):
@@ -60,27 +66,79 @@ def check(a, b, c, d, e, f):
 
     def test_slice_constructor(self):
         """
-        Test the slice() constructor in nopython mode.
+        Test the 'happy path' for slice() constructor in nopython mode.
         """
         maxposint = sys.maxsize
         maxnegint = -maxposint - 1
+        a = np.arange(10)
         cfunc = jit(nopython=True)(slice_constructor)
-        for args, expected in [((), (0, maxposint, 1)),
-                               ((None, None), (0, maxposint, 1)),
-                               ((1, None), (1, maxposint, 1)),
-                               ((None, 2), (0, 2, 1)),
-                               ((1, 2), (1, 2, 1)),
-                               ((None, None, 3), (0, maxposint, 3)),
-                               ((None, 2, 3), (0, 2, 3)),
-                               ((1, None, 3), (1, maxposint, 3)),
-                               ((1, 2, 3), (1, 2, 3)),
-                               ((None, None, -1), (maxposint, maxnegint, -1)),
-                               ((10, None, -1), (10, maxnegint, -1)),
-                               ((None, 5, -1), (maxposint, 5, -1)),
-                               ((10, 5, -1), (10, 5, -1)),
-                               ]:
+        cfunc_use = jit(nopython=True)(slice_construct_and_use)
+        for args, expected in [
+            ((None,), (0, maxposint, 1)),
+            ((5,), (0, 5, 1)),
+            ((None, None), (0, maxposint, 1)),
+            ((1, None), (1, maxposint, 1)),
+            ((None, 2), (0, 2, 1)),
+            ((1, 2), (1, 2, 1)),
+            ((None, None, 3), (0, maxposint, 3)),
+            ((None, 2, 3), (0, 2, 3)),
+            ((1, None, 3), (1, maxposint, 3)),
+            ((1, 2, 3), (1, 2, 3)),
+            ((None, None, -1), (maxposint, maxnegint, -1)),
+            ((10, None, -1), (10, maxnegint, -1)),
+            ((None, 5, -1), (maxposint, 5, -1)),
+            ((10, 5, -1), (10, 5, -1)),
+        ]:
             got = cfunc(*args)
             self.assertPreciseEqual(got, expected)
+            usage = slice_construct_and_use(args, a)
+            cusage = cfunc_use(args, a)
+            self.assertPreciseEqual(usage, cusage)
+
+    def test_slice_constructor_cases(self):
+        """
+        Test that slice constructor behaves same in python and compiled code.
+        """
+        options = (None, -1, 0, 1)
+        arg_cases = chain.from_iterable(
+            product(options, repeat=n) for n in range(5)
+        )
+        array = np.arange(10)
+
+        cfunc = jit(nopython=True)(slice_construct_and_use)
+
+        self.disable_leak_check()
+        for args in arg_cases:
+            try:
+                expected = slice_construct_and_use(args, array)
+            except TypeError as py_type_e:
+                # Catch cases of 0, or more than 3 arguments.
+                # This becomes a typing error in numba
+                n_args = len(args)
+                self.assertRegexpMatches(
+                    str(py_type_e),
+                    r"slice expected at (most|least) (3|1) arguments, got {}"
+                    .format(n_args)
+                )
+                with self.assertRaises(TypingError) as numba_e:
+                    cfunc(args, array)
+                self.assertIn(
+                    "Invalid use of Function",
+                    str(numba_e.exception)
+                )
+                self.assertIn(
+                    ", ".join(str(typeof(arg)) for arg in args),
+                    str(numba_e.exception)
+                )
+            except Exception as py_e:
+                with self.assertRaises(type(py_e)) as numba_e:
+                    cfunc(args, array)
+                self.assertIn(
+                    str(py_e),
+                    str(numba_e.exception)
+                )
+            else:
+                self.assertPreciseEqual(expected, cfunc(args, array))
 
     def test_slice_indices(self):
         """Test that a numba slice returns same result for .indices as a python one."""