From 25c21c7b6d6fcf4f2b578fc1929be3415da5943f Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Sat, 13 Sep 2025 13:46:00 +0200 Subject: [PATCH 1/3] improve nongeneric methods of `Base.rest`, `Base.indexed_iterate` Allow non-`Int` in dispatch for the second argument (iteration state), but `typeassert` `Int` in the method body. This improves abstract inference because the compiler can know the second argument must be `Int` to avoid a throw, when previously such a call would have dispatched to the generic method. --- base/namedtuple.jl | 7 +++++-- base/pair.jl | 5 ++++- base/tuple.jl | 23 +++++++++++++++++++---- test/core.jl | 31 +++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 37f3a3ef8436b..3a8670327ab4b 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -159,7 +159,7 @@ end length(t::NamedTuple) = nfields(t) iterate(t::NamedTuple, iter=1) = iter > nfields(t) ? nothing : (getfield(t, iter), iter + 1) rest(t::NamedTuple) = t -@inline rest(t::NamedTuple{names}, i::Int) where {names} = NamedTuple{rest(names,i)}(t) +@inline rest(t::NamedTuple{names}, i) where {names} = NamedTuple{rest(names,i::Int)}(t) firstindex(t::NamedTuple) = 1 lastindex(t::NamedTuple) = nfields(t) getindex(t::NamedTuple, i::Int) = getfield(t, i) @@ -167,7 +167,10 @@ getindex(t::NamedTuple, i::Symbol) = getfield(t, i) getindex(t::NamedTuple, ::Colon) = t @inline getindex(t::NamedTuple, idxs::Tuple{Vararg{Symbol}}) = NamedTuple{idxs}(t) @inline getindex(t::NamedTuple, idxs::AbstractVector{Symbol}) = NamedTuple{Tuple(idxs)}(t) -indexed_iterate(t::NamedTuple, i::Int, state=1) = (getfield(t, i), i+1) +function indexed_iterate(t::NamedTuple, i, state=1) + i = i::Int + (getfield(t, i), i+1) +end isempty(::NamedTuple{()}) = true isempty(::NamedTuple) = false empty(::NamedTuple) = NamedTuple() diff --git a/base/pair.jl b/base/pair.jl index 1953dc2886053..26a99b841f93d 100644 --- a/base/pair.jl +++ b/base/pair.jl @@ -39,7 +39,10 @@ Pair, => eltype(p::Type{Pair{A, B}}) where {A, B} = Union{A, B} iterate(p::Pair, i=1) = i > 2 ? nothing : (getfield(p, i), i + 1) -indexed_iterate(p::Pair, i::Int, state=1) = (getfield(p, i), i + 1) +function indexed_iterate(p::Pair, i, state=1) + i = i::Int + (getfield(p, i), i + 1) +end hash(p::Pair, h::UInt) = hash(p.second, hash(p.first, h)) diff --git a/base/tuple.jl b/base/tuple.jl index ebc2cd1e53202..64c495710a674 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -160,8 +160,16 @@ end # this allows partial evaluation of bounded sequences of next() calls on tuples, # while reducing to plain next() for arbitrary iterables. -indexed_iterate(t::Tuple, i::Int, state=1) = (@inline; (getfield(t, i), i+1)) -indexed_iterate(a::Union{Array,Memory}, i::Int, state=1) = (@inline; (a[i], i+1)) +function indexed_iterate(t::Tuple, i, state=1) + @inline + i = i::Int + (getfield(t, i), i+1) +end +function indexed_iterate(a::Union{Array,Memory}, i, state=1) + @inline + i = i::Int + (a[i], i+1) +end function indexed_iterate(I, i) x = iterate(I) x === nothing && throw(BoundsError(I, i)) @@ -206,8 +214,15 @@ julia> first, Base.rest(a, state) """ function rest end rest(t::Tuple) = t -rest(t::Tuple, i::Int) = ntuple(x -> getfield(t, x+i-1), length(t)-i+1) -rest(a::Union{Array,Memory,Core.SimpleVector}, i::Int=1) = a[i:end] +function rest(t::Tuple, i) + let i = i::Int + ntuple(x -> getfield(t, x+i-1), length(t)-i+1) + end +end +function rest(a::Union{Array,Memory,Core.SimpleVector}, i = 1) + i = i::Int + a[i:end] +end rest(itr, state...) = Iterators.rest(itr, state...) """ diff --git a/test/core.jl b/test/core.jl index 84444ecb725d7..8544ee373619c 100644 --- a/test/core.jl +++ b/test/core.jl @@ -8193,6 +8193,37 @@ foo46503(a::Int, b::Nothing) = @invoke foo46503(a::Any, b) foo46503(@nospecialize(a), b::Union{Nothing, Float64}) = rand() + 10 @test 10 <= foo46503(1, nothing) <= 11 +@testset "inference for `rest` and `indexed_iterate` with unknown state argument types" begin + @testset "`Array`, `Memory`" begin + for typ in (Vector{Float32}, Matrix{Float32}, Memory{Float32}) + @test (isconcretetype ∘ Base.infer_return_type)(Base.rest, Tuple{typ, Any}) + @test (isconcretetype ∘ Base.infer_return_type)(Base.indexed_iterate, Tuple{typ, Any}) + @test (isconcretetype ∘ Base.infer_return_type)(Base.indexed_iterate, Tuple{typ, Any, Any}) + for e in (Base.Compiler.is_terminates, Base.Compiler.is_notaskstate, Base.Compiler.is_nonoverlayed) + @test (e ∘ Base.infer_effects)(Base.rest, Tuple{typ, Any}) + @test (e ∘ Base.infer_effects)(Base.indexed_iterate, Tuple{typ, Any}) + @test (e ∘ Base.infer_effects)(Base.indexed_iterate, Tuple{typ, Any, Any}) + end + end + end + @testset "`Tuple`" begin + typ = NTuple{5, Float32} + @test Base.infer_return_type(Base.rest, Tuple{typ, Any}) <: Tuple + end + @testset "`NamedTuple`" begin + typ = NamedTuple{(:a, :b, :c, :d, :e), NTuple{5, Float32}} + @test Base.infer_return_type(Base.rest, Tuple{typ, Any}) <: NamedTuple + end + @testset "`Pair`, `Tuple`, `NamedTuple`" begin + for typ in (Pair{Float32, Float32}, NTuple{5, Float32}, NamedTuple{(:a, :b, :c, :d, :e), NTuple{5, Float32}}) + @test (isconcretetype ∘ Base.infer_return_type)(Base.indexed_iterate, Tuple{typ, Any}) + @test (isconcretetype ∘ Base.infer_return_type)(Base.indexed_iterate, Tuple{typ, Any, Any}) + @test (Base.Compiler.is_foldable ∘ Base.infer_effects)(Base.indexed_iterate, Tuple{typ, Any}) + @test (Base.Compiler.is_foldable ∘ Base.infer_effects)(Base.indexed_iterate, Tuple{typ, Any, Any}) + end + end +end + @testset "effect override on Symbol(::String)" begin @test Core.Compiler.is_foldable(Base.infer_effects(Symbol, (String,))) end From c443d54cfe5f752804a76a40f9b898d18d2c0215 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Wed, 28 Jan 2026 06:17:50 +0100 Subject: [PATCH 2/3] revert `indexed_iterate`-related changes --- base/namedtuple.jl | 5 +---- base/pair.jl | 5 +---- base/tuple.jl | 12 ++---------- test/core.jl | 14 +------------- 4 files changed, 5 insertions(+), 31 deletions(-) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 3a8670327ab4b..57d72bb3ea569 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -167,10 +167,7 @@ getindex(t::NamedTuple, i::Symbol) = getfield(t, i) getindex(t::NamedTuple, ::Colon) = t @inline getindex(t::NamedTuple, idxs::Tuple{Vararg{Symbol}}) = NamedTuple{idxs}(t) @inline getindex(t::NamedTuple, idxs::AbstractVector{Symbol}) = NamedTuple{Tuple(idxs)}(t) -function indexed_iterate(t::NamedTuple, i, state=1) - i = i::Int - (getfield(t, i), i+1) -end +indexed_iterate(t::NamedTuple, i::Int, state=1) = (getfield(t, i), i+1) isempty(::NamedTuple{()}) = true isempty(::NamedTuple) = false empty(::NamedTuple) = NamedTuple() diff --git a/base/pair.jl b/base/pair.jl index 26a99b841f93d..1953dc2886053 100644 --- a/base/pair.jl +++ b/base/pair.jl @@ -39,10 +39,7 @@ Pair, => eltype(p::Type{Pair{A, B}}) where {A, B} = Union{A, B} iterate(p::Pair, i=1) = i > 2 ? nothing : (getfield(p, i), i + 1) -function indexed_iterate(p::Pair, i, state=1) - i = i::Int - (getfield(p, i), i + 1) -end +indexed_iterate(p::Pair, i::Int, state=1) = (getfield(p, i), i + 1) hash(p::Pair, h::UInt) = hash(p.second, hash(p.first, h)) diff --git a/base/tuple.jl b/base/tuple.jl index 64c495710a674..87bfd43eb02c5 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -160,16 +160,8 @@ end # this allows partial evaluation of bounded sequences of next() calls on tuples, # while reducing to plain next() for arbitrary iterables. -function indexed_iterate(t::Tuple, i, state=1) - @inline - i = i::Int - (getfield(t, i), i+1) -end -function indexed_iterate(a::Union{Array,Memory}, i, state=1) - @inline - i = i::Int - (a[i], i+1) -end +indexed_iterate(t::Tuple, i::Int, state=1) = (@inline; (getfield(t, i), i+1)) +indexed_iterate(a::Union{Array,Memory}, i::Int, state=1) = (@inline; (a[i], i+1)) function indexed_iterate(I, i) x = iterate(I) x === nothing && throw(BoundsError(I, i)) diff --git a/test/core.jl b/test/core.jl index 8544ee373619c..004a352f5f75f 100644 --- a/test/core.jl +++ b/test/core.jl @@ -8193,16 +8193,12 @@ foo46503(a::Int, b::Nothing) = @invoke foo46503(a::Any, b) foo46503(@nospecialize(a), b::Union{Nothing, Float64}) = rand() + 10 @test 10 <= foo46503(1, nothing) <= 11 -@testset "inference for `rest` and `indexed_iterate` with unknown state argument types" begin +@testset "inference for `rest` with unknown state argument types" begin @testset "`Array`, `Memory`" begin for typ in (Vector{Float32}, Matrix{Float32}, Memory{Float32}) @test (isconcretetype ∘ Base.infer_return_type)(Base.rest, Tuple{typ, Any}) - @test (isconcretetype ∘ Base.infer_return_type)(Base.indexed_iterate, Tuple{typ, Any}) - @test (isconcretetype ∘ Base.infer_return_type)(Base.indexed_iterate, Tuple{typ, Any, Any}) for e in (Base.Compiler.is_terminates, Base.Compiler.is_notaskstate, Base.Compiler.is_nonoverlayed) @test (e ∘ Base.infer_effects)(Base.rest, Tuple{typ, Any}) - @test (e ∘ Base.infer_effects)(Base.indexed_iterate, Tuple{typ, Any}) - @test (e ∘ Base.infer_effects)(Base.indexed_iterate, Tuple{typ, Any, Any}) end end end @@ -8214,14 +8210,6 @@ foo46503(@nospecialize(a), b::Union{Nothing, Float64}) = rand() + 10 typ = NamedTuple{(:a, :b, :c, :d, :e), NTuple{5, Float32}} @test Base.infer_return_type(Base.rest, Tuple{typ, Any}) <: NamedTuple end - @testset "`Pair`, `Tuple`, `NamedTuple`" begin - for typ in (Pair{Float32, Float32}, NTuple{5, Float32}, NamedTuple{(:a, :b, :c, :d, :e), NTuple{5, Float32}}) - @test (isconcretetype ∘ Base.infer_return_type)(Base.indexed_iterate, Tuple{typ, Any}) - @test (isconcretetype ∘ Base.infer_return_type)(Base.indexed_iterate, Tuple{typ, Any, Any}) - @test (Base.Compiler.is_foldable ∘ Base.infer_effects)(Base.indexed_iterate, Tuple{typ, Any}) - @test (Base.Compiler.is_foldable ∘ Base.infer_effects)(Base.indexed_iterate, Tuple{typ, Any, Any}) - end - end end @testset "effect override on Symbol(::String)" begin From 3c8cd841bbd3541675844e73ebcb14e35d3c2639 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Thu, 29 Jan 2026 14:29:56 +0100 Subject: [PATCH 3/3] style --- base/tuple.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/base/tuple.jl b/base/tuple.jl index 87bfd43eb02c5..c2c2240296195 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -211,10 +211,7 @@ function rest(t::Tuple, i) ntuple(x -> getfield(t, x+i-1), length(t)-i+1) end end -function rest(a::Union{Array,Memory,Core.SimpleVector}, i = 1) - i = i::Int - a[i:end] -end +rest(a::Union{Array,Memory,Core.SimpleVector}, i=1) = a[(i::Int):end] rest(itr, state...) = Iterators.rest(itr, state...) """