Why is the NTuple{N} constructor not type stable when constructing a Tuple from a Vector?

julia> @code_warntype NTuple{3}([1,2,3])
Variables
  #self#::Type{Tuple{T, T, T} where T}
  itr::Vector{Int64}

Body::Tuple{Int64, Int64, Int64, Vararg{Any}}
1 ─ %1 = Base._totuple($(Expr(:static_parameter, 1)), itr)::Tuple{Int64, Int64, Int64, Vararg{Any}}
└──      return %1

The inferred type is Tuple{Int64, Int64, Int64, Vararg{Any}}, but shouldn’t the number of elements be known? Strangely the following is type stable:

julia> @code_warntype NTuple{3,Int}([1,2,3])
Variables
  #self#::Core.Const(Tuple{Int64, Int64, Int64})
  itr::Vector{Int64}

Body::Tuple{Int64, Int64, Int64}
1 ─ %1 = Base._totuple($(Expr(:static_parameter, 1)), itr)::Tuple{Int64, Int64, Int64}
└──      return %1

It seems that the code that creates the tuple does not get specialized on the type:

https://github.com/JuliaLang/julia/blob/750f42aac2ad09b9ccd79408ee4e19941f668fa4/base/tuple.jl#L312-L317

Forcing specialization via:

@eval Base begin
    function _totuple(::Type{T}, itr, s...) where {T}
        @_inline_meta
        y = iterate(itr, s...)
        y === nothing && _totuple_err(T)
        return (convert(fieldtype(T, 1), y[1]), _totuple(tuple_type_tail(T), itr, y[2])...)
    end
end

seems like it makes it type stable. Maybe this is done to not generate an excessive amount of signatures for the method.

4 Likes