Repository: numba/numba

Issue:
Numba heapq doesn't work with numba typed lists
- [x] I am using the latest released version of Numba
- [x] I have included below a minimal working reproducer

# Description
`heapq` doesn't seem to like anything other than lists, even `numba` typed lists don't work.

# How to reproduce
```
import heapq as hq
from numba.typed import List

l = List()
l.append(5)
l.append(3)

e = hq.heappop(l)
```
```
TypeError: heap argument must be a list
```



Discussion:
Thanks for the report. The issue is that CPython requires a CPython list:
https://github.com/python/cpython/blob/664fe3996f7e05ae351526f02b21504bb065bcf8/Modules/_heapqmodule.c#L139-L142 pretty sure there's nothing that can easily be done about this from Numba.

Numba has it's own [`heapq` implementation](https://github.com/numba/numba/blob/7b2b0a1966b1d503e709a84da8aa64e72f35c9dc/numba/targets/heapq.py) for use in `jit` decorated functions, we should fix that to accept `typed.List`. 
@stuartarchibald Thank you for your quick reply. How can I use Numba's `heapq` implementation?
@ma-sadeghi there's a short example here: https://nbviewer.jupyter.org/github/numba/numba-examples/blob/master/notebooks/Numba_043_release_demo.ipynb#Support-for-heapq- essentially `import heapq` then use it in a jit decorated function, it behaves pretty much the same as CPython `heapq` (IIRC the implementations are very similar).
Bumped into this attempting to help @truenicoco with an implementation of a priority queue on Gitter. With the following PQ implementation:

```python
import itertools

import numba as nb
from numba.experimental import jitclass
from typing import List, Tuple, Dict
from heapq import heappush, heappop


# @jitclass
class PurePythonPriorityQueue:
    def __init__(self):
        self.pq = []  # list of entries arranged in a heap
        self.entry_finder = {}  # mapping of indices to entries
        self.REMOVED = -1  # placeholder for a removed item
        self.counter = itertools.count()  # unique sequence count

    def put(self, item: Tuple[int, int], priority: float = 0.0):
        """Add a new item or update the priority of an existing item"""
        if item in self.entry_finder:
            self.remove_item(item)
        count = next(self.counter)
        entry = [priority, count, item]
        self.entry_finder[item] = entry
        heappush(self.pq, entry)

    def remove_item(self, item: Tuple[int, int]):
        """Mark an existing item as REMOVED.  Raise KeyError if not found."""
        entry = self.entry_finder.pop(item)
        entry[-1] = self.REMOVED

    def pop(self):
        """Remove and return the lowest priority item. Raise KeyError if empty."""
        while self.pq:
            priority, count, item = heappop(self.pq)
            if item is not self.REMOVED:
                del self.entry_finder[item]
                return item
        raise KeyError("pop from an empty priority queue")


# @jitclass
# class Item:
#     i: int
#     j: int
#
#     def __init__(self, i, j):
#         self.i = i
#         self.j = j
#
#     def __eq__(self, other):
#         return self.i == other.i and self.j == other.j


@jitclass
class Entry:
    priority: float
    count: int
    item: Tuple[int, int]
    removed: bool

    def __init__(self, p: float, c: int, i: Tuple[int, int]):
        self.priority = p
        self.count = c
        self.item = i
        self.removed = False

    def __lt__(self, other):
        return self.priority < other.priority


@jitclass
class PriorityQueue:
    pq: List[Entry]
    entry_finder: Dict[Tuple[int, int], Entry]
    counter: int

    def __init__(self):
        self.pq = nb.typed.List.empty_list(Entry(0.0, 0, (0, 0)))
        self.entry_finder = nb.typed.Dict.empty((0, 0), Entry(0, 0, (0, 0)))
        self.counter = 0

    def put(self, item: Tuple[int, int], priority: float = 0.0):
        """Add a new item or update the priority of an existing item"""
        if item in self.entry_finder:
            self.remove_item(item)
        self.counter += 1
        entry = Entry(priority, self.counter, item)
        self.entry_finder[item] = entry
        heappush(self.pq, entry)

    def remove_item(self, item: Tuple[int, int]):
        """Mark an existing item as REMOVED.  Raise KeyError if not found."""
        entry = self.entry_finder.pop(item)
        entry.removed = True

    def pop(self):
        """Remove and return the lowest priority item. Raise KeyError if empty."""
        while self.pq:
            priority, count, item = heappop(self.pq)
            entry = heappop(self.pq)
            if not entry.removed:
                del self.entry_finder[entry.item]
                return item
        raise KeyError("pop from an empty priority queue")


if __name__ == "__main__":
    queue1 = PurePythonPriorityQueue()
    queue1.put((4, 5), 5.4)
    queue1.put((5, 6), 1.0)
    print(queue1.pop())  # Yay this works!

    queue2 = PriorityQueue()  # Nope
    queue2.put((4, 5), 5.4)
    queue2.put((5, 6), 1.0)
    print(queue2.pop())

    # queue3 = OptimalQueue()
    # for i in range(5):
    #     for j in range(5):
    #         queue3.add_index(i, j)
    #         # [Index(i, j) for i in range(5) for j in range(5)]
    # print(queue3.indices)
    # queue3.put((4, 5), 5.4)
    # queue3.put((5, 6), 1.0)
    # print(queue3.pop())  # Yay this works!
```

If I remove the check for a reflected list in the heapq implementation:

```diff
diff --git a/numba/cpython/heapq.py b/numba/cpython/heapq.py
index 23a80e269..50b1ddce3 100644
--- a/numba/cpython/heapq.py
+++ b/numba/cpython/heapq.py
@@ -104,8 +104,8 @@ def _heapreplace_max(heap, item):
 
 
 def assert_heap_type(heap):
-    if not isinstance(heap, types.List):
-        raise TypingError('heap argument must be a list')
+    #if not isinstance(heap, types.List):
+    #    raise TypingError('heap argument must be a list')
 
     dt = heap.dtype
     if isinstance(dt, types.Complex):
```

Then I get the traceback:

```
$ python modified.py :q
(5, 6)
Traceback (most recent call last):
  File "/home/gmarkall/numbadev/issues/so-priority-queue/modified.py", line 114, in <module>
    queue2.put((4, 5), 5.4)
  File "/home/gmarkall/numbadev/numba/numba/experimental/jitclass/boxing.py", line 59, in wrapper
    return method(*args, **kwargs)
  File "/home/gmarkall/numbadev/numba/numba/core/dispatcher.py", line 482, in _compile_for_args
    error_rewrite(e, 'typing')
  File "/home/gmarkall/numbadev/numba/numba/core/dispatcher.py", line 423, in error_rewrite
    raise e.with_traceback(None)
numba.core.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
- Resolution failure for literal arguments:
Failed in nopython mode pipeline (step: nopython frontend)
No implementation of function Function(<built-in function heappush>) found for signature:

 >>> heappush(ListType[instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>], instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>)

There are 2 candidate implementations:
  - Of which 2 did not match due to:
  Overload in function 'heappush': File: numba/cpython/heapq.py: Line 150.
    With argument(s): '(ListType[instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>], instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>)':
   Rejected as the implementation raised a specific error:
     TypingError: Failed in nopython mode pipeline (step: nopython frontend)
   No implementation of function Function(<function _siftdown at 0x7f7c4294cee0>) found for signature:

    >>> _siftdown(ListType[instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>], Literal[int](0), int64)

   There are 2 candidate implementations:
         - Of which 2 did not match due to:
         Overload in function 'register_jitable.<locals>.wrap.<locals>.ov_wrap': File: numba/core/extending.py: Line 159.
           With argument(s): '(ListType[instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>], int64, int64)':
          Rejected as the implementation raised a specific error:
            TypingError: Failed in nopython mode pipeline (step: nopython frontend)
          No implementation of function Function(<built-in function lt>) found for signature:

           >>> lt(instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>, instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>)

          There are 20 candidate implementations:
                - Of which 18 did not match due to:
                Overload of function 'lt': File: <numerous>: Line N/A.
                  With argument(s): '(instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>, instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>)':
                 No match.
                - Of which 2 did not match due to:
                Operator Overload in function 'lt': File: unknown: Line unknown.
                  With argument(s): '(instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>, instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>)':
                 No match for registered cases:
                  * (bool, bool) -> bool
                  * (int8, int8) -> bool
                  * (int16, int16) -> bool
                  * (int32, int32) -> bool
                  * (int64, int64) -> bool
                  * (uint8, uint8) -> bool
                  * (uint16, uint16) -> bool
                  * (uint32, uint32) -> bool
                  * (uint64, uint64) -> bool
                  * (float32, float32) -> bool
                  * (float64, float64) -> bool
          
          During: typing of intrinsic-call at /home/gmarkall/numbadev/numba/numba/cpython/heapq.py (18)
          
          File "../../numba/numba/cpython/heapq.py", line 18:
          def _siftdown(heap, startpos, pos):
              <source elided>
                  parent = heap[parentpos]
                  if newitem < parent:
                  ^
   
     raised from /home/gmarkall/numbadev/numba/numba/core/typeinfer.py:1074
   
   During: resolving callee type: Function(<function _siftdown at 0x7f7c4294cee0>)
   During: typing of call at /home/gmarkall/numbadev/numba/numba/cpython/heapq.py (157)
   
   
   File "../../numba/numba/cpython/heapq.py", line 157:
       def hq_heappush_impl(heap, item):
           <source elided>
           heap.append(item)
           _siftdown(heap, 0, len(heap) - 1)
           ^

  raised from /home/gmarkall/numbadev/numba/numba/core/typeinfer.py:1074

During: resolving callee type: Function(<built-in function heappush>)
During: typing of call at /home/gmarkall/numbadev/issues/so-priority-queue/modified.py (89)


File "modified.py", line 89:
    def put(self, item: Tuple[int, int], priority: float = 0.0):
        <source elided>
        self.entry_finder[item] = entry
        heappush(self.pq, entry)
        ^

- Resolution failure for non-literal arguments:
None

During: resolving callee type: BoundFunction((<class 'numba.core.types.misc.ClassInstanceType'>, 'put') for instance.jitclass.PriorityQueue#7f7c42d484f0<pq:ListType[instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>],entry_finder:DictType[UniTuple(int64 x 2),instance.jitclass.Entry#7f7c42dbee50<priority:float64,count:int64,item:UniTuple(int64 x 2),removed:bool>]<iv=None>,counter:int64>)
During: typing of call at <string> (3)


File "<string>", line 3:
<source missing, REPL/exec in use?>
```

It seems like this comes from line 18 of heapq.py, `if newitem < parent:`:

```python
 11 @register_jitable
 12 def _siftdown(heap, startpos, pos):
 13     newitem = heap[pos]
 14 
 15     while pos > startpos:
 16         parentpos = (pos - 1) >> 1
 17         parent = heap[parentpos]
 18         if newitem < parent:
 19             heap[pos] = parent
 20             pos = parentpos
 21             continue
 22         break
 23 
 24     heap[pos] = newitem
```

Resolving patch:
diff --git a/numba/cpython/heapq.py b/numba/cpython/heapq.py
--- a/numba/cpython/heapq.py
+++ b/numba/cpython/heapq.py
@@ -104,7 +104,7 @@ def _heapreplace_max(heap, item):
 
 
 def assert_heap_type(heap):
-    if not isinstance(heap, types.List):
+    if not isinstance(heap, (types.List, types.ListType)):
         raise TypingError('heap argument must be a list')
 
     dt = heap.dtype
@@ -194,7 +194,7 @@ def check_input_types(n, iterable):
         # heapq also accepts 1.0 (but not 0.0, 2.0, 3.0...) but
         # this isn't replicated
 
-    if not isinstance(iterable, (types.Sequence, types.Array)):
+    if not isinstance(iterable, (types.Sequence, types.Array, types.ListType)):
         raise TypingError("Second argument 'iterable' must be iterable")
 
 


Test patch:
diff --git a/numba/tests/test_heapq.py b/numba/tests/test_heapq.py
--- a/numba/tests/test_heapq.py
+++ b/numba/tests/test_heapq.py
@@ -3,8 +3,9 @@
 
 import numpy as np
 
-from numba import jit
+from numba import jit, typed
 from numba.core.compiler import Flags
+from numba.core.config import IS_WIN32
 from numba.tests.support import TestCase, CompilationCache, MemoryLeakMixin
 
 no_pyobj_flags = Flags()
@@ -39,10 +40,10 @@ def nlargest(n, iterable):
     return hq.nlargest(n, iterable)
 
 
-class TestHeapq(MemoryLeakMixin, TestCase):
+class _TestHeapq(MemoryLeakMixin):
 
     def setUp(self):
-        super(TestHeapq, self).setUp()
+        super(_TestHeapq, self).setUp()
         self.ccache = CompilationCache()
         self.rnd = np.random.RandomState(42)
 
@@ -51,11 +52,11 @@ def test_heapify_basic_sanity(self):
         cfunc = jit(nopython=True)(pyfunc)
 
         a = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
-        b = a[:]
+        b = self.listimpl(a)
 
         pyfunc(a)
         cfunc(b)
-        self.assertPreciseEqual(a, b)
+        self.assertPreciseEqual(a, list(b))
 
         # includes non-finite elements
         element_pool = [3.142, -10.0, 5.5, np.nan, -np.inf, np.inf]
@@ -63,27 +64,27 @@ def test_heapify_basic_sanity(self):
         # list which may contain duplicate elements
         for x in itertools.combinations_with_replacement(element_pool, 6):
             a = list(x)
-            b = a[:]
+            b = self.listimpl(a)
 
             pyfunc(a)
             cfunc(b)
-            self.assertPreciseEqual(a, b)
+            self.assertPreciseEqual(a, list(b))
 
         # single element list
         for i in range(len(element_pool)):
             a = [element_pool[i]]
-            b = a[:]
+            b = self.listimpl(a)
 
             pyfunc(a)
             cfunc(b)
-            self.assertPreciseEqual(a, b)
+            self.assertPreciseEqual(a, list(b))
 
         # elements are tuples
         a = [(3, 33), (1, 11), (2, 22)]
-        b = a[:]
+        b = self.listimpl(a)
         pyfunc(a)
         cfunc(b)
-        self.assertPreciseEqual(a, b)
+        self.assertPreciseEqual(a, list(b))
 
     def check_invariant(self, heap):
         for pos, item in enumerate(heap):
@@ -100,8 +101,8 @@ def test_push_pop(self):
         pyfunc_heappop = heappop
         cfunc_heappop = jit(nopython=True)(pyfunc_heappop)
 
-        heap = [-1.0]
-        data = [-1.0]
+        heap = self.listimpl([-1.0])
+        data = self.listimpl([-1.0])
         self.check_invariant(heap)
         for i in range(256):
             item = self.rnd.randn(1).item(0)
@@ -115,7 +116,7 @@ def test_push_pop(self):
             results.append(item)
         data_sorted = data[:]
         data_sorted.sort()
-        self.assertPreciseEqual(data_sorted, results)
+        self.assertPreciseEqual(list(data_sorted), results)
         self.check_invariant(results)
 
     def test_heapify(self):
@@ -125,7 +126,7 @@ def test_heapify(self):
         cfunc = jit(nopython=True)(pyfunc)
 
         for size in list(range(1, 30)) + [20000]:
-            heap = self.rnd.random_sample(size).tolist()
+            heap = self.listimpl(self.rnd.random_sample(size))
             cfunc(heap)
             self.check_invariant(heap)
 
@@ -143,7 +144,7 @@ def test_heapify_exceptions(self):
         self.assertIn(msg, str(e.exception))
 
         with self.assertTypingError() as e:
-            cfunc([1 + 1j, 2 - 3j])
+            cfunc(self.listimpl([1 + 1j, 2 - 3j]))
 
         msg = ("'<' not supported between instances "
                "of 'complex' and 'complex'")
@@ -161,12 +162,12 @@ def a_variations():
 
         for a in a_variations():
             heapify(a)
-            b = a[:]
+            b = self.listimpl(a)
 
             for i in range(len(a)):
                 val_py = pyfunc(a)
                 val_c = cfunc(b)
-                self.assertPreciseEqual(a, b)
+                self.assertPreciseEqual(a, list(b))
                 self.assertPreciseEqual(val_py, val_c)
 
     def test_heappop_exceptions(self):
@@ -183,12 +184,12 @@ def test_heappop_exceptions(self):
         self.assertIn(msg, str(e.exception))
 
     def iterables(self):
-        yield [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
+        yield self.listimpl([1, 3, 5, 7, 9, 2, 4, 6, 8, 0])
         a = np.linspace(-10, 2, 23)
-        yield a.tolist()
-        yield a[::-1].tolist()
+        yield self.listimpl(a)
+        yield self.listimpl(a[::-1])
         self.rnd.shuffle(a)
-        yield a.tolist()
+        yield self.listimpl(a)
 
     def test_heappush_basic(self):
         pyfunc_push = heappush
@@ -199,7 +200,7 @@ def test_heappush_basic(self):
 
         for iterable in self.iterables():
             expected = sorted(iterable)
-            heap = [iterable.pop(0)]  # must initialise heap
+            heap = self.listimpl([iterable.pop(0)])  # must initialise heap
 
             for value in iterable:
                 cfunc_push(heap, value)
@@ -221,7 +222,7 @@ def test_heappush_exceptions(self):
         self.assertIn(msg, str(e.exception))
 
         with self.assertTypingError() as e:
-            cfunc([1, 5, 4], 6.0)
+            cfunc(self.listimpl([1, 5, 4]), 6.0)
 
         msg = 'heap type must be the same as item type'
         self.assertIn(msg, str(e.exception))
@@ -237,10 +238,10 @@ def test_nsmallest_basic(self):
                 self.assertPreciseEqual(expected, got)
 
         # n is boolean
-        out = cfunc(False, [3, 2, 1])
+        out = cfunc(False, self.listimpl([3, 2, 1]))
         self.assertPreciseEqual(out, [])
 
-        out = cfunc(True, [3, 2, 1])
+        out = cfunc(True, self.listimpl([3, 2, 1]))
         self.assertPreciseEqual(out, [1])
 
         # iterable is not a list
@@ -261,10 +262,10 @@ def test_nlargest_basic(self):
                 self.assertPreciseEqual(expected, got)
 
         # n is boolean
-        out = cfunc(False, [3, 2, 1])
+        out = cfunc(False, self.listimpl([3, 2, 1]))
         self.assertPreciseEqual(out, [])
 
-        out = cfunc(True, [3, 2, 1])
+        out = cfunc(True, self.listimpl([3, 2, 1]))
         self.assertPreciseEqual(out, [3])
 
         # iterable is not a list
@@ -280,7 +281,7 @@ def _assert_typing_error(self, cfunc):
         self.disable_leak_check()
 
         with self.assertTypingError() as e:
-            cfunc(2.2, [3, 2, 1])
+            cfunc(2.2, self.listimpl([3, 2, 1]))
 
         msg = "First argument 'n' must be an integer"
         self.assertIn(msg, str(e.exception))
@@ -308,12 +309,12 @@ def test_heapreplace_basic(self):
         a = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
 
         heapify(a)
-        b = a[:]
+        b = self.listimpl(a)
 
         for item in [-4, 4, 14]:
             pyfunc(a, item)
             cfunc(b, item)
-            self.assertPreciseEqual(a, b)
+            self.assertPreciseEqual(a, list(b))
 
         a = np.linspace(-3, 13, 20)
         a[4] = np.nan
@@ -321,12 +322,12 @@ def test_heapreplace_basic(self):
         a = a.tolist()
 
         heapify(a)
-        b = a[:]
+        b = self.listimpl(a)
 
         for item in [-4.0, 3.142, -np.inf, np.inf]:
             pyfunc(a, item)
             cfunc(b, item)
-            self.assertPreciseEqual(a, b)
+            self.assertPreciseEqual(a, list(b))
 
     def test_heapreplace_exceptions(self):
         pyfunc = heapreplace
@@ -342,7 +343,7 @@ def test_heapreplace_exceptions(self):
         self.assertIn(msg, str(e.exception))
 
         with self.assertTypingError() as e:
-            cfunc([1, 5, 4], -1.0)
+            cfunc(self.listimpl([1, 5, 4]), -1.0)
 
         msg = 'heap type must be the same as item type'
         self.assertIn(msg, str(e.exception))
@@ -361,14 +362,15 @@ def test_nbest(self):
         cfunc_heapreplace = jit(nopython=True)(heapreplace)
 
         data = self.rnd.choice(range(2000), 1000).tolist()
-        heap = data[:10]
+        heap = self.listimpl(data[:10])
         cfunc_heapify(heap)
 
         for item in data[10:]:
             if item > heap[0]:
                 cfunc_heapreplace(heap, item)
 
-        self.assertPreciseEqual(list(self.heapiter(heap)), sorted(data)[-10:])
+        self.assertPreciseEqual(list(self.heapiter(list(heap))),
+                                sorted(data)[-10:])
 
     def test_heapsort(self):
         # inspired by
@@ -377,13 +379,20 @@ def test_heapsort(self):
         cfunc_heappush = jit(nopython=True)(heappush)
         cfunc_heappop = jit(nopython=True)(heappop)
 
+        arange_kwargs = {}
+        if IS_WIN32:
+            # Ensure consistency of typing on Windows, where the default dtype
+            # of arange is int32 (vs. int64 on other 64-bit platforms)
+            arange_kwargs['dtype'] = np.int64
+
         for trial in range(100):
-            data = self.rnd.choice(range(5), 10).tolist()
+            values = np.arange(5, **arange_kwargs)
+            data = self.listimpl(self.rnd.choice(values, 10))
             if trial & 1:
                 heap = data[:]
                 cfunc_heapify(heap)
             else:
-                heap = [data[0]]
+                heap = self.listimpl([data[0]])
                 for item in data[1:]:
                     cfunc_heappush(heap, item)
             heap_sorted = [cfunc_heappop(heap) for _ in range(10)]
@@ -395,7 +404,7 @@ def test_nsmallest(self):
         pyfunc = nsmallest
         cfunc = jit(nopython=True)(pyfunc)
 
-        data = self.rnd.choice(range(2000), 1000).tolist()
+        data = self.listimpl(self.rnd.choice(range(2000), 1000))
 
         for n in (0, 1, 2, 10, 100, 400, 999, 1000, 1100):
             self.assertPreciseEqual(list(cfunc(n, data)), sorted(data)[:n])
@@ -406,7 +415,7 @@ def test_nlargest(self):
         pyfunc = nlargest
         cfunc = jit(nopython=True)(pyfunc)
 
-        data = self.rnd.choice(range(2000), 1000).tolist()
+        data = self.listimpl(self.rnd.choice(range(2000), 1000))
 
         for n in (0, 1, 2, 10, 100, 400, 999, 1000, 1100):
             self.assertPreciseEqual(list(cfunc(n, data)),
@@ -421,14 +430,22 @@ def test_nbest_with_pushpop(self):
         pyfunc_heapify = heapify
         cfunc_heapify = jit(nopython=True)(pyfunc_heapify)
 
-        data = self.rnd.choice(range(2000), 1000).tolist()
+        arange_kwargs = {}
+        if IS_WIN32:
+            # Ensure consistency of typing on Windows, where the default dtype
+            # of arange is int32 (vs. int64 on other 64-bit platforms)
+            arange_kwargs['dtype'] = np.int64
+
+        values = np.arange(2000, **arange_kwargs)
+        data = self.listimpl(self.rnd.choice(values, 1000))
         heap = data[:10]
         cfunc_heapify(heap)
 
         for item in data[10:]:
             cfunc_heappushpop(heap, item)
 
-        self.assertPreciseEqual(list(self.heapiter(heap)), sorted(data)[-10:])
+        self.assertPreciseEqual(list(self.heapiter(list(heap))),
+                                sorted(data)[-10:])
 
     def test_heappushpop(self):
         # inspired by
@@ -436,19 +453,19 @@ def test_heappushpop(self):
         pyfunc = heappushpop
         cfunc = jit(nopython=True)(pyfunc)
 
-        h = [1.0]
+        h = self.listimpl([1.0])
         x = cfunc(h, 10.0)
-        self.assertPreciseEqual((h, x), ([10.0], 1.0))
+        self.assertPreciseEqual((list(h), x), ([10.0], 1.0))
         self.assertPreciseEqual(type(h[0]), float)
         self.assertPreciseEqual(type(x), float)
 
-        h = [10]
+        h = self.listimpl([10])
         x = cfunc(h, 9)
-        self.assertPreciseEqual((h, x), ([10], 9))
+        self.assertPreciseEqual((list(h), x), ([10], 9))
 
-        h = [10]
+        h = self.listimpl([10])
         x = cfunc(h, 11)
-        self.assertPreciseEqual((h, x), ([11], 10))
+        self.assertPreciseEqual((list(h), x), ([11], 10))
 
     def test_heappushpop_exceptions(self):
         pyfunc = heappushpop
@@ -464,7 +481,19 @@ def test_heappushpop_exceptions(self):
         self.assertIn(msg, str(e.exception))
 
         with self.assertTypingError() as e:
-            cfunc([1, 5, 4], False)
+            cfunc(self.listimpl([1, 5, 4]), False)
 
         msg = 'heap type must be the same as item type'
         self.assertIn(msg, str(e.exception))
+
+
+class TestHeapqReflectedList(_TestHeapq, TestCase):
+    """Test heapq with reflected lists"""
+
+    listimpl = list
+
+
+class TestHeapqTypedList(_TestHeapq, TestCase):
+    """Test heapq with typed lists"""
+
+    listimpl = typed.List