I just realized that I know this, but can’t find it in the docs (context: someone asked me about it and I wanted to share a docs section that explains it.)
It’s documented in the lastindex docstring, which says:
The syntaxes A[end] and A[end, end] lower to A[lastindex(A)] and A[lastindex(A, 1), lastindex(A, 2)], respectively.
The end docstring also says:
end may also be used when indexing to represent the last index of a collection or the last index of a dimension of an array.
but it should probably cross-reference lastindex specifically.
Probably it should also be referenced from the “Arrays” chapter of the manual, which currently only says:
As a special part of this syntax, the end keyword may be used to represent the last index of each dimension within the indexing brackets, as determined by the size of the innermost array being indexed.
If you think about it for a second, then it is clear why this has to work that way: Macros.
That is quite the footgun for code generation!
And the distinction is not “internals”, it is public API (macros). So I guess the docs should say var"begin" and var"end" get lowered to appropriate firstindex/lastindex called when within an indexing bracket; and begin/end sometimes get parsed into var"begin"/var"end" when within an indexing bracket.
…and the “sometimes” is really non-trivial – see begin : end, which plausibly could have been getindex(A, Colon()) or getindex(A, Colon()(firstindex(A), lastindex(A))). Luckily both give the same result for most (all?) Base types.