Description
__setitem__
states:
https://data-apis.org/array-api/2024.12/API_specification/generated/array_api.array.__setitem__.html#array_api.array.__setitem__
Setting array values must not affect the data type of self.
When value is a Python scalar (i.e., int, float, complex, bool), behavior must follow specification guidance on mixing arrays with Python scalars (see Type Promotion Rules).
When value is an array of a different data type than self, how values are cast to the data type of self is implementation defined.
The last line is majorly problematic in my opinion and should be revised.
These use cases are left to the library's discretion:
- setting a float array value into an integer array, e.g.
>>> a = xp.asarray([1], dtype=xp.int64)
>>> a[0] = xp.asarray(1.0)
- setting a signed array value into an unsigned array, e.g.
>>> a = xp.asarray([1], dtype=xp.unt64)
>>> a[0] = xp.asarray(-2, dtype=xp.int8)
- setting an array value with a larger dtype into a smaller dtype of the same kind, e.g.
>>> a = xp.asarray([1], dtype=xp.int8)
>>> a[0] = xp.asarray(2, dtype=xp.int64)
All these use cases are quietly allowed by numpy.
Sanity would dictate for them to be all disallowed, exactly like in binops.
If the last use case is allowed, it also opens the issue of in-place binops (__iadd__
) etc.
Whereas out-of-place binops unambiguously must cast the smaller dtype to the larger one and then perform the operation, what is the correct process for in-place ones, when lhs has smaller dtype than rhs?
lhs[idx] = op(lhs[idx], xp.astype(rhs[idx], lhs.dtype))
or
lhs[idx] = xp.astype(op(lhs[idx], rhs[idx]), lhs.dtype)
which is the same as saying
t = xp.result_type(lhs, rhs)
lhs[idx] = xp.astype(op(xp.astype(lhs[idx], t), xp.astype(rhs[idx], t)), lhs.dtype)
? the output will subtly differ.