Repository: data-apis/array-api-strict

Issue:
array-api-strict creates an empty iterable rather than raising an error
For example:
```
In [47]: import array_api_strict as xp

In [48]: x = xp.ones((2,2))

In [49]: list(iter(x))
Out[49]: []
```
I think this is because `__getitem__` raises a `IndexError`
```
In [47]: import array_api_strict as xp

In [48]: x = xp.ones((2,2))

In [49]: list(iter(x))
Out[49]: []

In [50]: x.__getitem__(0)
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[50], line 1
----> 1 x.__getitem__(0)

File ~/miniconda3/envs/scipy-dev-pytorch/lib/python3.11/site-packages/array_api_strict/_array_object.py:588, in Array.__getitem__(self, key)
    583 """
    584 Performs the operation __getitem__.
    585 """
    586 # Note: Only indices required by the spec are allowed. See the
    587 # docstring of _validate_index
--> 588 self._validate_index(key)
    589 if isinstance(key, Array):
    590     # Indexing self._array with array_api_strict arrays can be erroneous
    591     key = key._array

File ~/miniconda3/envs/scipy-dev-pytorch/lib/python3.11/site-packages/array_api_strict/_array_object.py:374, in Array._validate_index(self, key)
    370 elif n_ellipsis == 0:
    371     # Note boolean masks must be the sole index, which we check for
    372     # later on.
    373     if not key_has_mask and n_single_axes < self.ndim:
--> 374         raise IndexError(
    375             f"{self.ndim=}, but the multi-axes index only specifies "
    376             f"{n_single_axes} dimensions. If this was intentional, "
    377             "add a trailing ellipsis (...) which expands into as many "
    378             "slices (:) as necessary - this is what np.ndarray arrays "
    379             "implicitly do, but such flat indexing behaviour is not "
    380             "specified in the Array API."
    381         )
    383 if n_ellipsis == 0:
    384     indexed_shape = self.shape

IndexError: self.ndim=2, but the multi-axes index only specifies 1 dimensions. If this was intentional, add a trailing ellipsis (...) which expands into as many slices (:) as necessary - this is what np.ndarray arrays implicitly do, but such flat indexing behaviour is not specified in the Array API.
```
which is meant for out of bounds, instead I think we should be raising a `TypeError`.
![image](https://github.com/data-apis/array-api-strict/assets/60778417/7619edae-e16f-4c2f-aa93-ff215a0899d9)

This can be reproduced also in the following example
```
In [51]: class A:
    ...:     def __getitem__(self, key):
    ...:         raise IndexError
    ...:

In [52]: list(iter(A()))
Out[52]: []
```

Discussion:

Patch:
diff --git a/array_api_strict/_array_object.py b/array_api_strict/_array_object.py
index 8849ce3..18ed327 100644
--- a/array_api_strict/_array_object.py
+++ b/array_api_strict/_array_object.py
@@ -647,6 +647,15 @@ class Array:
         res = self._array.__invert__()
         return self.__class__._new(res)
 
+    def __iter__(self: Array, /):
+        """
+        Performs the operation __iter__.
+        """
+        # Manually disable iteration, since __getitem__ raises IndexError on
+        # things like ones((3, 3))[0], which causes list(ones((3, 3))) to give
+        # [].
+        raise TypeError("array iteration is not allowed in array-api-strict")
+
     def __le__(self: Array, other: Union[int, float, Array], /) -> Array:
         """
         Performs the operation __le__.


Test patch:
diff --git a/array_api_strict/tests/test_array_object.py b/array_api_strict/tests/test_array_object.py
index e061a94..407bff2 100644
--- a/array_api_strict/tests/test_array_object.py
+++ b/array_api_strict/tests/test_array_object.py
@@ -416,3 +416,7 @@ def test_array_namespace():
 
     pytest.raises(ValueError, lambda: a.__array_namespace__(api_version="2021.11"))
     pytest.raises(ValueError, lambda: a.__array_namespace__(api_version="2023.12"))
+
+def test_no_iter():
+    pytest.raises(TypeError, lambda: iter(ones(3)))
+    pytest.raises(TypeError, lambda: iter(ones((3, 3))))