diff --git a/base/essentials.jl b/base/essentials.jl index 66f7883097c32..3a8b946a9d9f8 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -531,8 +531,12 @@ julia> Base.tail(()) ERROR: ArgumentError: Cannot call tail on an empty tuple. ``` """ -tail(x::Tuple) = argtail(x...) -tail(::Tuple{}) = throw(ArgumentError("Cannot call tail on an empty tuple.")) +function tail(x::Tuple) + if x === () + throw(ArgumentError("Cannot call tail on an empty tuple.")) + end + argtail(x...) +end function unwrap_unionall(@nospecialize(a)) @_foldable_meta diff --git a/base/operators.jl b/base/operators.jl index 23f03aaff0f99..b5c18da7871d9 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -223,13 +223,6 @@ isless(x::AbstractFloat, y::AbstractFloat) = (!isnan(x) & (isnan(y) | signless(x isless(x::Real, y::AbstractFloat) = (!isnan(x) & (isnan(y) | signless(x, y))) | (x < y) isless(x::AbstractFloat, y::Real ) = (!isnan(x) & (isnan(y) | signless(x, y))) | (x < y) -# Performance optimization to reduce branching -# This is useful for sorting tuples of integers -# TODO: remove this when the compiler can optimize the generic version better -# See #48724 and #48753 -isless(a::Tuple{BitInteger, BitInteger}, b::Tuple{BitInteger, BitInteger}) = - isless(a[1], b[1]) | (isequal(a[1], b[1]) & isless(a[2], b[2])) - """ isgreater(x, y) diff --git a/base/tuple.jl b/base/tuple.jl index c2c2240296195..1b36393b64a70 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -268,9 +268,12 @@ end @eval split_rest(t::Tuple, n::Int, i=1) = ($(Expr(:meta, :aggressive_constprop)); _split_tuple(t, length(t)-n, Int(i))) -# Use dispatch to avoid a branch in first -first(::Tuple{}) = throw(ArgumentError("tuple must be non-empty")) -first(t::Tuple) = t[1] +function first(t::Tuple) + if t === () + throw(ArgumentError("tuple must be non-empty")) + end + t[1] +end # eltype @@ -575,46 +578,62 @@ function _eq(t1::Any32, t2::Any32) end const tuplehash_seed = UInt === UInt64 ? 0x77cfa1eef01bca90 : 0xf01bca90 -hash(::Tuple{}, h::UInt) = h ⊻ tuplehash_seed -hash(t::Tuple, h::UInt) = hash(t[1], hash(tail(t), h)) -function hash(t::Any32, h::UInt) - out = h ⊻ tuplehash_seed - for i = length(t):-1:1 - out = hash(t[i], out) +function hash(t::Tuple, h::UInt) + function f(t::Tuple, h::UInt) + if t === () + h ⊻ tuplehash_seed + else + hash(t[1], hash(tail(t), h)) + end end - return out + function f(t::Any32, h::UInt) + out = h ⊻ tuplehash_seed + for i = length(t):-1:1 + out = hash(t[i], out) + end + return out + end + f(t, h) end -<(::Tuple{}, ::Tuple{}) = false -<(::Tuple{}, ::Tuple) = true -<(::Tuple, ::Tuple{}) = false function <(t1::Tuple, t2::Tuple) - a, b = t1[1], t2[1] - eq = (a == b) - if ismissing(eq) - return missing - elseif !eq - return a < b - end - return tail(t1) < tail(t2) -end -function <(t1::Any32, t2::Any32) - n1, n2 = length(t1), length(t2) - for i = 1:min(n1, n2) - a, b = t1[i], t2[i] + function f(t1::Tuple, t2::Tuple) + if t2 === () + return false + end + if t1 === () + return true + end + a, b = t1[1], t2[1] eq = (a == b) if ismissing(eq) return missing elseif !eq - return a < b + return a < b end + return tail(t1) < tail(t2) end - return n1 < n2 + function f(t1::Any32, t2::Any32) + n1, n2 = length(t1), length(t2) + for i = 1:min(n1, n2) + a, b = t1[i], t2[i] + eq = (a == b) + if ismissing(eq) + return missing + elseif !eq + return a < b + end + end + return n1 < n2 + end + f(t1, t2) end -isless(::Tuple{}, ::Tuple{}) = false -isless(::Tuple{}, ::Tuple) = true -isless(::Tuple, ::Tuple{}) = false +# copy of `BitInteger` defined later during bootstrap in int.jl +const _BitInteger = Union{ + Int8, Int16, Int32, Int64, Int128, + UInt8, UInt16, UInt32, UInt64, UInt128, +} """ isless(t1::Tuple, t2::Tuple) @@ -622,24 +641,41 @@ isless(::Tuple, ::Tuple{}) = false Return `true` when `t1` is less than `t2` in lexicographic order. """ function isless(t1::Tuple, t2::Tuple) - a, b = t1[1], t2[1] - isless(a, b) || (isequal(a, b) && isless(tail(t1), tail(t2))) -end -function isless(t1::Any32, t2::Any32) - n1, n2 = length(t1), length(t2) - for i = 1:min(n1, n2) - a, b = t1[i], t2[i] - if !isequal(a, b) - return isless(a, b) + function f(t1::Tuple, t2::Tuple) + if t2 === () + return false + end + if t1 === () + return true + end + a, b = t1[1], t2[1] + isless(a, b) || (isequal(a, b) && isless(tail(t1), tail(t2))) + end + function f(t1::Any32, t2::Any32) + n1, n2 = length(t1), length(t2) + for i = 1:min(n1, n2) + a, b = t1[i], t2[i] + if !isequal(a, b) + return isless(a, b) + end end + return n1 < n2 end - return n1 < n2 + # Performance optimization to reduce branching + # This is useful for sorting tuples of integers + # TODO: remove this when the compiler can optimize the generic version better + # See #48724 and #48753 + function f(a::Tuple{_BitInteger, _BitInteger}, b::Tuple{_BitInteger, _BitInteger}) + isless(a[1], b[1]) | (isequal(a[1], b[1]) & isless(a[2], b[2])) + end + f(t1, t2) end ## functions ## -isempty(x::Tuple{}) = true -isempty(@nospecialize x::Tuple) = false +function isempty(x::Tuple) + x === () +end revargs() = () revargs(x, r...) = (revargs(r...)..., x) @@ -677,11 +713,19 @@ empty(@nospecialize x::Tuple) = () foreach(f, itr::Tuple) = foldl((_, x) -> (f(x); nothing), itr, init=nothing) foreach(f, itr::Tuple, itrs::Tuple...) = foldl((_, xs) -> (f(xs...); nothing), zip(itr, itrs...), init=nothing) -circshift((@nospecialize t::Union{Tuple{},Tuple{Any}}), @nospecialize _::Integer) = t -circshift(t::Tuple{Any,Any}, shift::Integer) = iseven(shift) ? t : reverse(t) -function circshift(x::Tuple{Any,Any,Any,Vararg{Any,N}}, shift::Integer) where {N} +function circshift(x::Tuple, shift::Integer) @inline - len = N + 3 + if (x === ()) || (x isa Tuple{Any}) + return x + end + if x isa Tuple{Any,Any} + return if iseven(shift) + x + else + reverse(x) + end + end + len = length(x) j = mod1(shift, len) ntuple(k -> getindex(x, k-j+ifelse(k>j,0,len)), Val(len))::Tuple end diff --git a/test/tuple.jl b/test/tuple.jl index 1f5714415e21c..2e71e26286f05 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -855,6 +855,18 @@ end @test Val{Tuple{Int64, Vararg{Int32,N}} where N} === Val{Tuple{Int64, Vararg{Int32}}} @test Val{Tuple{Int32, Vararg{Int64}}} === Val{Tuple{Int32, Vararg{Int64,N}} where N} +@testset "avoid method proliferation" begin + t = isone ∘ length ∘ methods + @test t(circshift, Tuple{Tuple, Integer}) + @test t(hash, Tuple{Tuple, UInt}) + for f in (Base.tail, first, isempty) + @test t(f, Tuple{Tuple}) + end + for f in (<, isless, ==, isequal) + @test t(f, Tuple{Tuple, Tuple}) + end +end + @testset "from Pair, issue #52636" begin pair = (1 => "2") @test (1, "2") == @inferred Tuple(pair)