Repository: numba/numba

Issue:
Support slice objects
Supporting the slice() constructor and the slice object's method(s) could be useful.



Discussion:
Actually, the slice() constructor already seems supported. Only the `indices()` method would have to be supported.


Resolving patch:
diff --git a/numba/targets/slicing.py b/numba/targets/slicing.py
--- a/numba/targets/slicing.py
+++ b/numba/targets/slicing.py
@@ -7,7 +7,7 @@
 from llvmlite import ir
 
 from numba.six.moves import zip_longest
-from numba import cgutils, types, typing
+from numba import cgutils, types, typing, utils
 from .imputils import (lower_builtin, lower_getattr,
                        iternext_impl, impl_ret_borrowed,
                        impl_ret_new_ref, impl_ret_untracked)
@@ -207,3 +207,30 @@ def slice_step_impl(context, builder, typ, value):
         return sli.step
     else:
         return context.get_constant(types.intp, 1)
+
+
+@lower_builtin("slice.indices", types.SliceType, types.Integer)
+def slice_indices(context, builder, sig, args):
+    length = args[1]
+    sli = context.make_helper(builder, sig.args[0], args[0])
+
+    if utils.IS_PY3:
+        # Negative values allowed in python2.7, see changelog for python 3.4
+        with builder.if_then(cgutils.is_neg_int(builder, length), likely=False):
+            context.call_conv.return_user_exc(
+                builder, ValueError,
+                ("length should not be negative",)
+            )
+    with builder.if_then(cgutils.is_scalar_zero(builder, sli.step), likely=False):
+        context.call_conv.return_user_exc(
+            builder, ValueError,
+            ("slice step cannot be zero",)
+        )
+
+    fix_slice(builder, sli, length)
+
+    return context.make_tuple(
+        builder,
+        sig.return_type,
+        (sli.start, sli.stop, sli.step)
+    )
diff --git a/numba/typing/builtins.py b/numba/typing/builtins.py
--- a/numba/typing/builtins.py
+++ b/numba/typing/builtins.py
@@ -726,6 +726,20 @@ def resolve_stop(self, ty):
     def resolve_step(self, ty):
         return types.intp
 
+    @bound_function("slice.indices")
+    def resolve_indices(self, ty, args, kws):
+        assert not kws
+        if len(args) != 1:
+            raise TypeError(
+                "indices() takes exactly one argument (%d given)" % len(args)
+            )
+        typ, = args
+        if not isinstance(typ, types.Integer):
+            raise TypeError(
+                "'%s' object cannot be interpreted as an integer" % typ
+            )
+        return signature(types.UniTuple(types.intp, 3), types.intp)
+
 
 #-------------------------------------------------------------------------------
 


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,11 +1,12 @@
 from __future__ import print_function, division, absolute_import
 
 import itertools
+from itertools import chain, product, starmap
 import sys
 
 from numba import unittest_support as unittest
-from numba import jit, typeof
-from .support import TestCase
+from numba import jit, typeof, utils, TypingError
+from .support import TestCase, MemoryLeakMixin
 
 
 def slice_passing(sl):
@@ -15,8 +16,11 @@ def slice_constructor(*args):
     sl = slice(*args)
     return sl.start, sl.stop, sl.step
 
+def slice_indices(s, *indargs):
+    return s.indices(*indargs)
 
-class TestSlices(TestCase):
+
+class TestSlices(MemoryLeakMixin, TestCase):
 
     def test_slice_passing(self):
         """
@@ -78,6 +82,55 @@ def test_slice_constructor(self):
             got = cfunc(*args)
             self.assertPreciseEqual(got, expected)
 
+    def test_slice_indices(self):
+        """Test that a numba slice returns same result for .indices as a python one."""
+        slices = starmap(
+            slice,
+            product(
+                chain(range(-5, 5), (None,)),
+                chain(range(-5, 5), (None,)),
+                chain(range(-5, 5), (None,))
+            )
+        )
+        lengths = range(-2, 3)
+
+        cfunc = jit(nopython=True)(slice_indices)
+
+        for s, l in product(slices, lengths):
+            if l < 0 and not utils.IS_PY3:
+                # Passing a negative length to slice.indices in python2 is
+                # undefined. See https://bugs.python.org/issue14794#msg174678
+                continue
+            try:
+                expected = slice_indices(s, l)
+            except Exception as py_e:
+                with self.assertRaises(type(py_e)) as numba_e:
+                    cfunc(s, l)
+                self.assertIn(
+                    str(py_e),
+                    str(numba_e.exception)
+                )
+            else:
+                self.assertPreciseEqual(expected, cfunc(s, l))
+
+    def test_slice_indices_examples(self):
+        """Tests for specific error cases."""
+        cslice_indices = jit(nopython=True)(slice_indices)
+
+        with self.assertRaises(TypingError) as e:
+            cslice_indices(slice(None), 1, 2, 3)
+        self.assertIn(
+             "indices() takes exactly one argument (3 given)",
+             str(e.exception)
+        )
+
+        with self.assertRaises(TypingError) as e:
+            cslice_indices(slice(None, None, 0), 1.2)
+        self.assertIn(
+            "'%s' object cannot be interpreted as an integer" % typeof(1.2),
+            str(e.exception)
+        )
+
 
 if __name__ == '__main__':
     unittest.main()