diff --git a/base/tuple.jl b/base/tuple.jl index 3e39e8d63cb86..5fd44943c88f9 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -449,9 +449,6 @@ end (::Type{T})(x::Tuple) where {T<:Tuple} = x isa T ? x : convert(T, x) # still use `convert` for tuples -Tuple(x::Ref) = tuple(getindex(x)) # faster than iterator for one element -Tuple(x::Array{T,0}) where {T} = tuple(getindex(x)) - (::Type{T})(itr) where {T<:Tuple} = _totuple(T, itr) _totuple(::Type{Tuple{}}, itr, s...) = () @@ -492,8 +489,66 @@ _totuple(::Type{Tuple}, itr, s...) = (collect(Iterators.rest(itr,s...))...,) _totuple(::Type{Tuple}, itr::Array) = (itr...,) _totuple(::Type{Tuple}, itr::SimpleVector) = (itr...,) _totuple(::Type{Tuple}, itr::NamedTuple) = (itr...,) -_totuple(::Type{Tuple}, p::Pair) = (p.first, p.second) -_totuple(::Type{Tuple}, x::Number) = (x,) # to make Tuple(x) inferable + +const Tuple1OrMore = Tuple{Any,Vararg} +const Tuple2OrMore = Tuple{Any,Any,Vararg} +const Tuple3OrMore = Tuple{Any,Any,Any,Vararg} + +const ConstantLengthIterator1 = Union{Number,Ref,AbstractArray{<:Any,0}} +const ConstantLengthIterator2 = Pair +const ConstantLengthIterator = Union{ConstantLengthIterator1,ConstantLengthIterator2} + +function tuple_from_truncated(::Type{T}, iter, ::Val{1}) where {T} + T1 = fieldtype(T, 1) + x1 = first(iter) + y1 = convert(T1, x1) + (y1,) +end + +function tuple_from_truncated(::Type{T}, iter, ::Val{2}) where {T} + T1 = fieldtype(T, 1) + T2 = fieldtype(T, 2) + (x1, x2) = iter + y1 = convert(T1, x1) + y2 = convert(T2, x2) + (y1, y2) +end + +tuple_from_constant_length_iterator(::Type{T}, ::ConstantLengthIterator1) where {T<:Tuple{} } = () +tuple_from_constant_length_iterator(::Type{T}, i::ConstantLengthIterator1) where {T<:Tuple } = tuple_from_truncated(T, i, Val(1)) +tuple_from_constant_length_iterator(::Type{T}, ::ConstantLengthIterator1) where {T<:Tuple2OrMore} = _totuple_err(T) +tuple_from_constant_length_iterator(::Type{T}, ::ConstantLengthIterator2) where {T<:Tuple{} } = () +tuple_from_constant_length_iterator(::Type{T}, i::ConstantLengthIterator2) where {T<:Tuple{Any} } = tuple_from_truncated(T, i, Val(1)) +tuple_from_constant_length_iterator(::Type{T}, i::ConstantLengthIterator2) where {T<:Tuple2OrMore} = tuple_from_truncated(T, i, Val(2)) +tuple_from_constant_length_iterator(::Type{T}, ::ConstantLengthIterator2) where {T<:Tuple3OrMore} = _totuple_err(T) + +function tuple_from_constant_length_iterator(::Type{T}, i::ConstantLengthIterator2) where {T<:Tuple1OrMore} + S = typesplit(T, Tuple{Any}) + tuple_from_truncated(S, i, Val(2)) +end + +function tuple_from_constant_length_iterator(::Type{T}, i::ConstantLengthIterator2) where {T<:Tuple} + T0 = Tuple{} + T1 = Tuple{Any} + T2 = Tuple{Any,Any} + X = typesplit(T, T0) + if !(typeintersect(X, T2) <: Union{}) + let Y = typesplit(X, T1) + tuple_from_truncated(Y, i, Val(2)) + end + elseif !(typeintersect(X, T1) <: Union{}) + tuple_from_truncated(X, i, Val(1)) + else + error("unexpected") + end::Union{T1,T2} +end + +# Some special cased types as an optimization +tuple_from_constant_length_iterator(::Type{Tuple }, i::ConstantLengthIterator2) = tuple_from_truncated(Tuple, i, Val(2)) +tuple_from_constant_length_iterator(::Type{Tuple{Any, Vararg}}, i::ConstantLengthIterator2) = tuple_from_truncated(Tuple, i, Val(2)) +tuple_from_constant_length_iterator(::Type{Tuple{Any,Any,Vararg}}, i::ConstantLengthIterator2) = tuple_from_truncated(Tuple, i, Val(2)) + +(::Type{T})(x::ConstantLengthIterator) where {T<:Tuple} = tuple_from_constant_length_iterator(T, x) end diff --git a/test/core.jl b/test/core.jl index ba42687acd0d0..b56dca92afde8 100644 --- a/test/core.jl +++ b/test/core.jl @@ -6879,8 +6879,6 @@ let a = Foo17149() end # issue #21004 -const PTuple_21004{N,T} = NTuple{N,VecElement{T}} -@test_throws ArgumentError("too few elements for tuple type $PTuple_21004") PTuple_21004(1) @test_throws UndefVarError(:T, :static_parameter) PTuple_21004_2{N,T} = NTuple{N, VecElement{T}}(1) #issue #22792 diff --git a/test/tuple.jl b/test/tuple.jl index ee9f3e9ac7a2c..6a7bbb847b40c 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -818,3 +818,177 @@ namedtup = (;a=1, b=2, c=3) @test (1, "2") == @inferred Tuple(pair) @test (1, "2") == @inferred Tuple{Int,String}(pair) end + +@testset "from constant-length iterators, issue #52993" begin + allocs = function(::Type{T}, iter) where {T} + @allocated T(iter) + end + + @testset "conversion" begin + @testset "length 1" begin + iters = (7::Number, Ref(7)::Ref, fill(7)::AbstractArray{<:Any,0}) + + @testset "iter: $iter" for iter ∈ iters + types = ( + Tuple{Float64}, Tuple{AbstractFloat},Tuple{Float64,Vararg{Float64}}, + Tuple{Vararg{Float64}}, Tuple{Vararg{AbstractFloat}}, + ) + @testset "T: $T" for T ∈ types + @test (7.0,) === @inferred T(iter) + @test iszero(allocs(T, iter)) + end + end + end + + @testset "length 2" begin + iters = ((10 => 20)::Pair,) + + @testset "iter: $iter" for iter ∈ iters + types = ( + Tuple{Float64,Float64}, Tuple{AbstractFloat,AbstractFloat}, + Tuple{Float64,AbstractFloat}, Tuple{AbstractFloat,Float64}, + Tuple{Vararg{Float64}}, Tuple{Vararg{AbstractFloat}}, + Tuple{Float64,Vararg{Float64}}, Tuple{Float64,Vararg{AbstractFloat}} + ) + @testset "T: $T" for T ∈ types + @test (10.0, 20.0) === @inferred T(iter) + @test iszero(allocs(T, iter)) + end + end + end + end + + types_length_0 = (Tuple{},) + types_length_1 = ( + Tuple{Int}, Tuple{Number}, Tuple{Any}, (Tuple{T} where {T<:Number}), + (Tuple{T} where {Number<:T<:Any}), (Tuple{T} where {Int<:T<:Number}) + ) + types_length_2 = ( + Tuple{Int,Int}, Tuple{Number,Number}, Tuple{Number,Any}, Tuple{Number,Int}, + Tuple{Any,Any}, (Tuple{Number, T} where {Int<:T<:Number}), + (Tuple{T, T} where {Int<:T<:Number}), (Tuple{T, T} where {T<:Number}) + ) + types_length_3 = ( + Tuple{Int,Int,Int}, Tuple{Number,Number,Number}, Tuple{Number,Any,Int}, + Tuple{Any,Any,Any}, (Tuple{T,T,T} where {T}), + (Tuple{T,T,T} where {Number<:T<:Any}), (Tuple{Number,T,T} where {T<:Number}) + ) + types_length_4 = ( + Tuple{Int,Int,Int,Int}, Tuple{Any,Number,Int,Any}, (Tuple{T,T,T,T} where {T}), + (Tuple{T,T,T,T} where {Int<:T<:Number}), (Tuple{Int,T,T,T} where {T}) + ) + + types_vararg_0 = ( + Tuple, Tuple{Vararg{Int}}, Tuple{Vararg{Number}}, (Tuple{Vararg{T}} where {T}), + (Tuple{Vararg{T}} where {T<:Number}), (Tuple{Vararg{T}} where {T<:Int}), + (Tuple{Vararg{T}} where {Number<:T<:Any}), + (Tuple{Vararg{T}} where {Int<:T<:Number}) + ) + types_vararg_1 = ( + Tuple{Number,Vararg{Number}}, Tuple{Number,Vararg{Any}}, Tuple{Int,Vararg{Int}}, + (Tuple{Int,Vararg{T}} where {T}), (Tuple{Int,Vararg{T}} where {Int<:T<:Number}) + ) + types_vararg_2 = ( + Tuple{Any,Any,Vararg{Any}}, Tuple{Int,Int,Vararg{Number}}, + Tuple{Int,Int,Vararg{Int}}, (Tuple{Int,Number,Vararg{T}} where {T}), + (Tuple{Int,Number,Vararg{T}} where {T}), + (Tuple{Int,Number,Vararg{T}} where {T<:Number}), + (Tuple{Int,Number,Vararg{T}} where {Int, T<:Number}) + ) + types_vararg_3 = ( + Tuple{Any,Any,Any,Vararg{Any}}, Tuple{Number,Int,Any,Vararg{Number}}, + Tuple{Int,Int,Int,Vararg{Int}}, (Tuple{T,Number,Any,Vararg{T}} where {T}), + (Tuple{T,Number,Any,Vararg{T}} where {T<:Number}), + (Tuple{T,Number,Any,Vararg{T}} where {Int<:T<:Number}) + ) + types_vararg_4 = ( + Tuple{Any,Number,Int,Number,Vararg{Number}}, + (Tuple{Any,Number,Int,Number,Vararg{T}} where {T}), + (Tuple{Any,Number,T,Number,Vararg{T}} where {Int<:T<:Number}) + ) + + @testset "length 1" begin + types_ok_0 = types_length_0 + types_ok_1 = ( + types_length_1..., types_vararg_0..., types_vararg_1... + ) + types_throws = ( + types_length_2..., types_length_3..., types_length_4..., + types_vararg_2..., types_vararg_3..., types_vararg_4... + ) + iters = (7::Number, Ref(7)::Ref, fill(7)::AbstractArray{<:Any,0}) + + @testset "iter: $iter" for iter ∈ iters + @testset "OK 0" begin + @testset "T: $T" for T ∈ types_ok_0 + @test () === @inferred T(iter) + @test iszero(allocs(T, iter)) + end + end + + @testset "OK 1" begin + @testset "T: $T" for T ∈ types_ok_1 + @test (7,) === @inferred T(iter) + @test iszero(allocs(T, iter)) + end + end + + @testset "throws `ArgumentError`" begin + @testset "T: $T" for T ∈ types_throws + @test_throws ArgumentError T(iter) + end + end + + @testset "throws" begin + @test_throws Exception Tuple{Nothing}(iter) + @test_throws Exception Tuple{Matrix}(iter) + end + end + end + + @testset "length 2" begin + types_ok_0 = types_length_0 + types_ok_1 = types_length_1 + types_ok_2 = ( + types_length_2..., types_vararg_0..., types_vararg_1..., types_vararg_2... + ) + types_throws = ( + types_length_3..., types_length_4..., types_vararg_3..., types_vararg_4... + ) + iters = ((10 => 20)::Pair,) + + @testset "iter: $iter" for iter ∈ iters + @testset "OK 0" begin + @testset "T: $T" for T ∈ types_ok_0 + @test () === @inferred T(iter) + @test iszero(allocs(T, iter)) + end + end + + @testset "OK 1" begin + @testset "T: $T" for T ∈ types_ok_1 + @test (10,) === @inferred T(iter) + @test iszero(allocs(T, iter)) + end + end + + @testset "OK 2" begin + @testset "T: $T" for T ∈ types_ok_2 + @test (10, 20) === @inferred T(iter) + @test iszero(allocs(T, iter)) + end + end + + @testset "throws `ArgumentError`" begin + @testset "T: $T" for T ∈ types_throws + @test_throws ArgumentError T(iter) + end + end + + @testset "throws" begin + @test_throws Exception Tuple{Nothing,Any}(iter) + @test_throws Exception Tuple{Matrix,Any}(iter) + end + end + end +end