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.