Skip to content

improve construction of <:Tuple types from constant-length iterators #53152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 60 additions & 5 deletions base/tuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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...) = ()
Expand Down Expand Up @@ -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

Expand Down
2 changes: 0 additions & 2 deletions test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
174 changes: 174 additions & 0 deletions test/tuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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