Repository: numba/numba

Issue:
`List.__getitem__` needs to handle unsigned int
<!--

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).

For more general "how do I do X?" type questions, please speak to us in real
time on https://gitter.im/numba/numba or post to the Numba mailing list
https://groups.google.com/a/continuum.io/forum/#!forum/numba-users.

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [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).
- [ ] 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).

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->
https://github.com/numba/numba/blob/60d2bddd0a9ddf84cf900a70e4b7e17bcbe5dc87/numba/listobject.py#L565

It's breaking prange usecases.


Discussion:

Resolving patch:
diff --git a/numba/listobject.py b/numba/listobject.py
--- a/numba/listobject.py
+++ b/numba/listobject.py
@@ -51,6 +51,8 @@
 
 INDEXTY = types.intp
 
+index_types = types.integer_domain
+
 
 @register_model(ListType)
 class ListModel(models.StructModel):
@@ -460,7 +462,9 @@ def handle_index(l, index):
     """
     # convert negative indices to positive ones
     if index < 0:
-        index = len(l) + index
+        # len(l) has always type 'int64'
+        # while index can be an signed/unsigned integer
+        index = type(index)(len(l) + index)
     # check that the index is in range
     if not (0 <= index < len(l)):
         raise IndexError("list index out of range")
@@ -562,7 +566,7 @@ def impl_getitem(l, index):
     indexty = INDEXTY
     itemty = l.item_type
 
-    if index in types.signed_domain:
+    if index in index_types:
         def integer_impl(l, index):
             index = handle_index(l, index)
             castedindex = _cast(index, indexty)
@@ -584,7 +588,7 @@ def slice_impl(l, index):
         return slice_impl
 
     else:
-        raise TypingError("list indices must be signed integers or slices")
+        raise TypingError("list indices must be integers or slices")
 
 
 @intrinsic
@@ -630,7 +634,7 @@ def impl_setitem(l, index, item):
     indexty = INDEXTY
     itemty = l.item_type
 
-    if index in types.signed_domain:
+    if index in index_types:
         def impl_integer(l, index, item):
             index = handle_index(l, index)
             castedindex = _cast(index, indexty)
@@ -692,7 +696,7 @@ def impl_slice(l, index, item):
         return impl_slice
 
     else:
-        raise TypingError("list indices must be signed integers or slices")
+        raise TypingError("list indices must be integers or slices")
 
 
 @overload_method(types.ListType, 'pop')
@@ -704,7 +708,7 @@ def impl_pop(l, index=-1):
 
     # FIXME: this type check works, but it isn't clear why and if it optimal
     if (isinstance(index, int)
-            or index in types.signed_domain
+            or index in index_types
             or isinstance(index, types.Omitted)):
         def impl(l, index=-1):
             if len(l) == 0:
@@ -719,7 +723,7 @@ def impl(l, index=-1):
         return impl
 
     else:
-        raise TypingError("argument for pop must be a signed integer")
+        raise TypingError("argument for pop must be an integer")
 
 
 @intrinsic
@@ -759,7 +763,7 @@ def impl_delitem(l, index):
     if not isinstance(l, types.ListType):
         return
 
-    if index in types.signed_domain:
+    if index in index_types:
         def integer_impl(l, index):
             l.pop(index)
 
@@ -775,7 +779,7 @@ def slice_impl(l, index):
         return slice_impl
 
     else:
-        raise TypingError("list indices must be signed integers or slices")
+        raise TypingError("list indices must be integers or slices")
 
 
 @overload(operator.contains)
@@ -861,7 +865,7 @@ def impl_insert(l, index, item):
     if not isinstance(l, types.ListType):
         return
 
-    if index in types.signed_domain:
+    if index in index_types:
         def impl(l, index, item):
             # If the index is larger than the size of the list or if the list is
             # empty, just append.
@@ -895,7 +899,7 @@ def impl(l, index, item):
             sig = typing.signature(types.void, l, INDEXTY, itemty)
             return sig, impl
     else:
-        raise TypingError("list insert indices must be signed integers")
+        raise TypingError("list insert indices must be integers")
 
 
 @overload_method(types.ListType, 'remove')
@@ -962,9 +966,9 @@ def impl_index(l, item, start=None, end=None):
 
     def check_arg(arg, name):
         if not (arg is None
-                or arg in types.signed_domain
+                or arg in index_types
                 or isinstance(arg, (types.Omitted, types.NoneType))):
-            raise TypingError("{} argument for index must be a signed integer"
+            raise TypingError("{} argument for index must be an integer"
                               .format(name))
     check_arg(start, "start")
     check_arg(end, "end")


Test patch:
diff --git a/numba/tests/test_listobject.py b/numba/tests/test_listobject.py
--- a/numba/tests/test_listobject.py
+++ b/numba/tests/test_listobject.py
@@ -144,7 +144,7 @@ def foo(i):
             with self.assertRaises(TypingError) as raises:
                 foo(i)
             self.assertIn(
-                "list indices must be signed integers or slices",
+                "list indices must be integers or slices",
                 str(raises.exception),
             )
 
@@ -487,7 +487,7 @@ def foo(i):
             with self.assertRaises(TypingError) as raises:
                 foo(i)
             self.assertIn(
-                "list indices must be signed integers or slices",
+                "list indices must be integers or slices",
                 str(raises.exception),
             )
 
@@ -663,7 +663,7 @@ def foo(i):
             with self.assertRaises(TypingError) as raises:
                 foo(i)
             self.assertIn(
-                "argument for pop must be a signed integer",
+                "argument for pop must be an integer",
                 str(raises.exception),
             )
 
@@ -957,7 +957,7 @@ def foo():
         with self.assertRaises(TypingError) as raises:
             foo()
         self.assertIn(
-            "list insert indices must be signed integers",
+            "list insert indices must be integers",
             str(raises.exception),
         )
 
@@ -1242,7 +1242,7 @@ def foo():
         with self.assertRaises(TypingError) as raises:
             foo()
         self.assertIn(
-            "start argument for index must be a signed integer",
+            "start argument for index must be an integer",
             str(raises.exception),
         )
 
@@ -1257,7 +1257,7 @@ def foo():
         with self.assertRaises(TypingError) as raises:
             foo()
         self.assertIn(
-            "end argument for index must be a signed integer",
+            "end argument for index must be an integer",
             str(raises.exception),
         )
 
diff --git a/numba/tests/test_typedlist.py b/numba/tests/test_typedlist.py
--- a/numba/tests/test_typedlist.py
+++ b/numba/tests/test_typedlist.py
@@ -5,7 +5,7 @@
 import numpy as np
 
 from numba import njit
-from numba import int32, float32, types
+from numba import int32, float32, types, prange
 from numba import jitclass, typeof
 from numba.typed import List, Dict
 from numba.utils import IS_PY3
@@ -14,6 +14,8 @@
 
 from numba.unsafe.refcount import get_refcount
 
+from .test_parfors import skip_unsupported as parfors_skip_unsupported
+
 skip_py2 = unittest.skipUnless(IS_PY3, reason='not supported in py2')
 
 
@@ -101,6 +103,71 @@ def test_basic(self):
         # index
         self.assertEqual(l.index(15), 4)
 
+    def test_unsigned_access(self):
+        L = List.empty_list(int32)
+        ui32_0 = types.uint32(0)
+        ui32_1 = types.uint32(1)
+        ui32_2 = types.uint32(2)
+
+        # insert
+        L.append(types.uint32(10))
+        L.append(types.uint32(11))
+        L.append(types.uint32(12))
+        self.assertEqual(len(L), 3)
+
+        # getitem
+        self.assertEqual(L[ui32_0], 10)
+        self.assertEqual(L[ui32_1], 11)
+        self.assertEqual(L[ui32_2], 12)
+
+        # setitem
+        L[ui32_0] = 123
+        L[ui32_1] = 456
+        L[ui32_2] = 789
+        self.assertEqual(L[ui32_0], 123)
+        self.assertEqual(L[ui32_1], 456)
+        self.assertEqual(L[ui32_2], 789)
+
+        # index
+        ui32_123 = types.uint32(123)
+        ui32_456 = types.uint32(456)
+        ui32_789 = types.uint32(789)
+        self.assertEqual(L.index(ui32_123), 0)
+        self.assertEqual(L.index(ui32_456), 1)
+        self.assertEqual(L.index(ui32_789), 2)
+
+        # delitem
+        L.__delitem__(ui32_2)
+        del L[ui32_1]
+        self.assertEqual(len(L), 1)
+        self.assertEqual(L[ui32_0], 123)
+
+        # pop
+        L.append(2)
+        L.append(3)
+        L.append(4)
+        self.assertEqual(len(L), 4)
+        self.assertEqual(L.pop(), 4)
+        self.assertEqual(L.pop(ui32_2), 3)
+        self.assertEqual(L.pop(ui32_1), 2)
+        self.assertEqual(L.pop(ui32_0), 123)
+
+    @parfors_skip_unsupported
+    def test_unsigned_prange(self):
+        @njit(parallel=True)
+        def foo(a):
+            r = types.uint64(3)
+            s = types.uint64(0)
+            for i in prange(r):
+                s = s + a[i]
+            return s
+
+        a = List.empty_list(types.uint64)
+        a.append(types.uint64(12))
+        a.append(types.uint64(1))
+        a.append(types.uint64(7))
+        self.assertEqual(foo(a), 20)
+
     def test_compiled(self):
         @njit
         def producer():