From 2e32141ce58ecc5ce53ad9692bfc1893a6d78419 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Sat, 21 Jun 2025 05:33:27 +0200 Subject: [PATCH] decrease method count for `eltype` for `Tuple`, decrease `max_methods` The `eltype` function was one of the few functions in the sysimage with a `max_methods` value (the world-splitting threshold) greater than the default. This was a workaround for the unnecessarily large number of methods of `eltype(::Type{<:Tuple})`. The `max_methods` value was increased in PR #48322 to help effect inference. Reduce the number of `eltype(::Type{<:Tuple})` methods, which then also allows keeping the `max_methods` for `eltype` at the default value. The intent here is to guard against unnecessary invalidation of the sysimage or other precompiled code, which might require decreasing the `max_methods` value to the natural minimum for many interface functions, by which I mean "generic function meant to have methods added to it by package authors". I intend to approach this issue in following PRs. See also: PR #57884. Regarding future work: I consider the "natural minimum" value for interface functions taking types to be **two**, because the bottom type subtypes each type. If the interface function doesn't accept types, the natural minimum should be **one**. --- base/tuple.jl | 29 +++++++++++------------------ test/ambiguous.jl | 2 +- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/base/tuple.jl b/base/tuple.jl index 5255f8ba07539..640eca1416ac2 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -268,10 +268,18 @@ first(t::Tuple) = t[1] # eltype -eltype(::Type{Tuple{}}) = Bottom # the <: here makes the runtime a bit more complicated (needing to check isdefined), but really helps inference -eltype(t::Type{<:Tuple{Vararg{E}}}) where {E} = @isdefined(E) ? (E isa Type ? E : Union{}) : _compute_eltype(t) -eltype(t::Type{<:Tuple}) = _compute_eltype(t) +_eltype_ntuple(t::Type{<:Tuple{Vararg{E}}}) where {E} = @isdefined(E) ? (E isa Type ? E : Union{}) : _compute_eltype(t) +# We'd like to be able to infer eltype(::Tuple), so keep the number of eltype(::Type{<:Tuple}) methods at max_methods! +function eltype(t::Type{<:Tuple}) + if t <: Tuple{} + Bottom + elseif t <: NTuple + _eltype_ntuple(t) + else + _compute_eltype(t) + end +end function _compute_eltype(@nospecialize t) @_total_meta has_free_typevars(t) && return Any @@ -296,21 +304,6 @@ function _compute_eltype(@nospecialize t) return r end -# We'd like to be able to infer eltype(::Tuple), which needs to be able to -# look at these four methods: -# -# julia> methods(Base.eltype, Tuple{Type{<:Tuple}}) -# 4 methods for generic function "eltype" from Base: -# [1] eltype(::Type{Union{}}) -# @ abstractarray.jl:234 -# [2] eltype(::Type{Tuple{}}) -# @ tuple.jl:199 -# [3] eltype(t::Type{<:Tuple{Vararg{E}}}) where E -# @ tuple.jl:200 -# [4] eltype(t::Type{<:Tuple}) -# @ tuple.jl:209 -typeof(function eltype end).name.max_methods = UInt8(4) - # key/val types keytype(@nospecialize t::Tuple) = keytype(typeof(t)) keytype(@nospecialize T::Type{<:Tuple}) = Int diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 0f29817e74dd5..1ba06b3b13ffc 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -349,7 +349,7 @@ end let need_to_handle_undef_sparam = Set{Method}(detect_unbound_args(Base; recursive=true, allowed_undefineds)) pop!(need_to_handle_undef_sparam, which(Base._totuple, (Type{Tuple{Vararg{E}}} where E, Any, Any))) - pop!(need_to_handle_undef_sparam, which(Base.eltype, Tuple{Type{Tuple{Any}}})) + pop!(need_to_handle_undef_sparam, which(Base._eltype_ntuple, Tuple{Type{Tuple{Any}}})) pop!(need_to_handle_undef_sparam, first(methods(Base.same_names))) @test_broken isempty(need_to_handle_undef_sparam) pop!(need_to_handle_undef_sparam, which(Base._cat, Tuple{Any, AbstractArray}))