Type stability of indexing a tuple in local scope vs global scope

The behavior of @code_warntype makes a bit more sense if you look at what it’s actually doing:

julia> @macroexpand @code_warntype t[1]
:(InteractiveUtils.code_warntype(getindex, (Base.typesof)(t, 1)))

julia> Base.typesof(t, 1)
Tuple{Tuple{Int64, Float64}, Int64}

So @code_warntype is just calling the regular function code_warntype with the types of t and 1, which are Tuple{Int64, Float64} and Int64, respectively.

That means code_warntype has only access to the type of 1, not the constant value. There’s no way it can possibly return anything more specific than a Union because, just from that information, there’s no way to know if you asked for t[1] or t[2].

Once you’ve hard-coded the index 1 in a function, though, the compiler can be even smarter and do some constant propagation to figure out the result type. When you do: @code_warntype foo(t), there actually is enough information from just the type of t to infer the result of foo because of the literal t[1] in the definition of foo.

Being in a function doesn’t really matter–we can show that even in a function @code_warntype’s behavior doesn’t change at all:

julia> function bar(t)
         @code_warntype t[1]
         t[1]
       end
bar (generic function with 1 method)

julia> bar(t)
Variables
  #self#::Core.Const(getindex)
  t::Tuple{Int64, Float64}
  i::Int64

Body::Union{Float64, Int64}

There’s no difference because @code_warntype is just calling code_warntype(getindex, typesof(t, 1)) just as before.

7 Likes