Where is the lowering of end within [ ] documented

Where is the lowering of end within [] documented? Ie the fact that it is assigned a value from lastindex, as in

julia> Meta.@lower (1:4)[min(end, 5)]
:($(Expr(:thunk, CodeInfo(
1 ─ %1 = Main.:(:)
│   %2 =   dynamic (%1)(1, 4)
│   %3 = Main.min
│   %4 =   dynamic Base.lastindex(%2)
│   %5 =   dynamic (%3)(%4, 5)
│   %6 =   dynamic Base.getindex(%2, %5)
└──      return %6
))))

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.)

I think it’s hidden away in the compiler internals.

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.

Funnily enough, this is done in the parser, not in lowering.

julia> Meta.parse("[1][begin : end]")
:(([1])[var"begin":var"end"])

julia> Meta.parse("[begin : end]")
:([begin
          #= none:1 =#
          :
      end])

julia> [begin : end]
1-element Vector{Colon}:
 (::Colon) (generic function with 16 methods)

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.

It’s also documented on the end keyword itself!

help?> end
search: end tand read secd sind nand rand bind

  end

  end marks the conclusion of a block of expressions, for example module,
  struct, mutable struct, begin, let, for etc.

  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.

  Examples
  ≡≡≡≡≡≡≡≡

  julia> A = [1 2; 3 4]
  2×2 Matrix{Int64}:
   1  2
   3  4

  julia> A[end, :]
  2-element Vector{Int64}:
   3
   4

Yes, this is understood, but what this feature does precisely is not documented there.

Some languages have similar keywords or concepts, but do not implement them in such a general way.

So I don’t think it is a good way to index the first and last elements. Mathematica’s 1 and -1 are quite concise, as long as you give up offset arrays.

Does this behavior depend on the Julia version or so? If I try this in 1.12.6 (and 1.10.11), it looks like this:

julia> Meta.parse("[1][begin : end]")
:(([1])[begin:end])

var"begin" and var"end" don’t seem to exist for me.

PS: Or did you define them by hand to make a point? I’m not sure why anyone would do that though :sweat_smile: And even if I set var"begin" and var"end" to something, the above still parses to the same for me (no var"begin" just begin).