Repository: scikit-hep/awkward

Issue:
Slicing `dask-awkward` array in too many dimensions raises `StopIteration`
### Description of new feature

Trying to slice an Awkward Array in too many dimensions raises a helpful error:

```python
>>> import awkward as ak, dask_awkward as dak
>>> a = ak.Array([1])
>>> a[:, 0]
Traceback (most recent call last):
  File "/home/user/iris-hep/src/awkward-1.0/src/awkward/contents/numpyarray.py", line 346, in _getitem_next
    out = self._data[where]
IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/user/iris-hep/src/awkward-1.0/src/awkward/highlevel.py", line 951, in __getitem__
    out = self._layout[where]
  File "/home/user/iris-hep/src/awkward-1.0/src/awkward/contents/content.py", line 548, in __getitem__
    return self._getitem(where)
  File "/home/user/iris-hep/src/awkward-1.0/src/awkward/contents/content.py", line 589, in _getitem
    out = next._getitem_next(nextwhere[0], nextwhere[1:], None)
  File "/home/user/iris-hep/src/awkward-1.0/src/awkward/contents/regulararray.py", line 521, in _getitem_next
    nextcontent._getitem_next(nexthead, nexttail, advanced),
  File "/home/user/iris-hep/src/awkward-1.0/src/awkward/contents/numpyarray.py", line 348, in _getitem_next
    raise ak._errors.index_error(self, (head, *tail), str(err)) from err
IndexError: while attempting to slice

    <Array [1] type='1 * int64'>

with

    (:, 0)

at inner NumpyArray of length 1, using sub-slice (0).

Error details: too many indices for array: array is 1-dimensional, but 2 were indexed.
```

Whereas for `dask-awkward` arrays, you get this much less friendly uncaught `StopIteration`, coming from `typetracer`:

```python
>>> da = dak.from_awkward(a, 1)
>>> da[:, 0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/user/iris-hep/src/dask-awkward/src/dask_awkward/lib/core.py", line 920, in __getitem__
    return self._getitem_tuple(where)
  File "/home/user/iris-hep/src/dask-awkward/src/dask_awkward/lib/core.py", line 847, in _getitem_tuple
    return self._getitem_trivial_map_partitions(where)
  File "/home/user/iris-hep/src/dask-awkward/src/dask_awkward/lib/core.py", line 722, in _getitem_trivial_map_partitions
    meta = self._meta[metad]
  File "/home/user/iris-hep/src/awkward-1.0/src/awkward/highlevel.py", line 951, in __getitem__
    out = self._layout[where]
  File "/home/user/iris-hep/src/awkward-1.0/src/awkward/contents/content.py", line 548, in __getitem__
    return self._getitem(where)
  File "/home/user/iris-hep/src/awkward-1.0/src/awkward/contents/content.py", line 589, in _getitem
    out = next._getitem_next(nextwhere[0], nextwhere[1:], None)
  File "/home/user/iris-hep/src/awkward-1.0/src/awkward/contents/regulararray.py", line 521, in _getitem_next
    nextcontent._getitem_next(nexthead, nexttail, advanced),
  File "/home/user/iris-hep/src/awkward-1.0/src/awkward/contents/numpyarray.py", line 346, in _getitem_next
    out = self._data[where]
  File "/home/user/iris-hep/src/awkward-1.0/src/awkward/_nplikes/typetracer.py", line 456, in __getitem__
    dimension_length = next(iter_shape)
StopIteration
```

Ideally, this case should produce a message similar to the first example.


Discussion:
This issue might need to be on https://github.com/dask-contrib/dask-awkward. I think the fix is going to involve changes to that codebase, not this one.
Going by the stack-trace, we're raising a `StopIteration` exception if the indexing shape is out-of-bounds in typetracer. We should be raising an `IndexError` here, which would then be caught and re-thrown by 
https://github.com/scikit-hep/awkward/blob/7fbe6b856cb87cfbe5a4827b7586852c49ff2bbe/src/awkward/contents/numpyarray.py#L346-L348
Oh, okay. So it is something to do here.

Resolving patch:
diff --git a/src/awkward/_nplikes/typetracer.py b/src/awkward/_nplikes/typetracer.py
--- a/src/awkward/_nplikes/typetracer.py
+++ b/src/awkward/_nplikes/typetracer.py
@@ -427,11 +427,20 @@ def __getitem__(
                     )
                 )
 
+        n_dim_index = n_basic_non_ellipsis + n_advanced
+        if n_dim_index > self.ndim:
+            raise wrap_error(
+                IndexError(
+                    f"too many indices for array: array is {self.ndim}-dimensional, but {n_dim_index} were indexed"
+                )
+            )
+
         # 2. Normalise Ellipsis and boolean arrays
         key_parts = []
         for item in key:
             if item is Ellipsis:
-                n_missing_dims = self.ndim - n_advanced - n_basic_non_ellipsis
+                # How many more dimensions do we have than the index provides
+                n_missing_dims = self.ndim - n_dim_index
                 key_parts.extend((slice(None),) * n_missing_dims)
             elif is_unknown_array(item) and np.issubdtype(item, np.bool_):
                 key_parts.append(self.nplike.nonzero(item)[0])


Test patch:
diff --git a/tests/test_2304_index_typetracer.py b/tests/test_2304_index_typetracer.py
new file mode 100644
--- /dev/null
+++ b/tests/test_2304_index_typetracer.py
@@ -0,0 +1,21 @@
+# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE
+
+import pytest
+
+import awkward as ak
+
+
+def test():
+    array = ak.Array([[1, 2, 3], [4, 5, 6]], backend="typetracer")
+
+    with pytest.raises(IndexError):
+        array[0, 0, 0]
+
+    with pytest.raises(IndexError):
+        array[0, :, 0]
+
+    with pytest.raises(IndexError):
+        array[0, :, ..., 0]
+
+    with pytest.raises(IndexError):
+        array[0, [0], [0]]