Multi-dimensional indices are incompatible with `end` index

Julia allows to access the last index in the axis of an array using the end keyword by lowering it to lastindex in a rather predictable way:

A = rand(5, 5, 2)
A[1:end - 1, end ÷ 2, :] # lowers to A[1:lastindex(A, 1) - 1, lastindex(A, 2) ÷ 2, :]

This silently assumes that all indices are one-dimensional, which leads to extremely sneaky errors

mask = rand(5, 5) .> 0.5
A[mask, end] # BoundsError, because `end` lowers to `lastindex(A, 2)` == 5

Now, this is likely a known issue, it is even raised in the docs, though only for CartesianIndex. However, since these types of errors are silent (so if A had shape (5, 5, 10) by sheer bad luck, I would have gotten wrong results without any error), so I believe we can’t just leave it this way.

Probably a good idea is update the docs to make more emphasis on this issue: make the warning message brighter and move it to the part where the end keyword is introduced. Another idea is changing how code like this is lowered (and probably implement it in JuliaLowering.jl), I can see how it can be done in two ways:

  • Make this behavior throw an error. For example, we can wrap A into a struct like Require1DIndexing and override its getindex/setindex to check if all indices are one-dimensional.
  • Check the dimensionality of each index on the lowering stage: create a local variable index_dim::Int = 1, then take the first index and replace end with lastindex(A, index_dim), then increase index_dim by the dimensionality of the first index, rinse, repeat.

Which is the better way to solve it here?

2 Likes

Yeah, this is a well known issue. Good catch that the warning box should also include boolean masks (and other custom index types like EllipsisNotation.jl), too. Some other ideas on how to solve this are in this issue:

4 Likes