Skip to content

Commit 02551ca

Browse files
committed
improve construction of <:Tuple types from constant-length iterators
Make tuple construction from known constant-length iterators inferrable and prevent allocation. Fixes #52993
1 parent 6e7db14 commit 02551ca

File tree

3 files changed

+210
-7
lines changed

3 files changed

+210
-7
lines changed

base/tuple.jl

+36-5
Original file line numberDiff line numberDiff line change
@@ -449,9 +449,6 @@ end
449449

450450
(::Type{T})(x::Tuple) where {T<:Tuple} = x isa T ? x : convert(T, x) # still use `convert` for tuples
451451

452-
Tuple(x::Ref) = tuple(getindex(x)) # faster than iterator for one element
453-
Tuple(x::Array{T,0}) where {T} = tuple(getindex(x))
454-
455452
(::Type{T})(itr) where {T<:Tuple} = _totuple(T, itr)
456453

457454
_totuple(::Type{Tuple{}}, itr, s...) = ()
@@ -492,8 +489,42 @@ _totuple(::Type{Tuple}, itr, s...) = (collect(Iterators.rest(itr,s...))...,)
492489
_totuple(::Type{Tuple}, itr::Array) = (itr...,)
493490
_totuple(::Type{Tuple}, itr::SimpleVector) = (itr...,)
494491
_totuple(::Type{Tuple}, itr::NamedTuple) = (itr...,)
495-
_totuple(::Type{Tuple}, p::Pair) = (p.first, p.second)
496-
_totuple(::Type{Tuple}, x::Number) = (x,) # to make Tuple(x) inferable
492+
493+
const Tuple1OrMore = Tuple{Any,Vararg}
494+
const Tuple2OrMore = Tuple{Any,Any,Vararg}
495+
const Tuple3OrMore = Tuple{Any,Any,Any,Vararg}
496+
497+
const ConstantLengthIterator1 = Union{Number,Ref,AbstractArray{<:Any,0}}
498+
const ConstantLengthIterator2 = Pair
499+
const ConstantLengthIterator = Union{ConstantLengthIterator1,ConstantLengthIterator2}
500+
501+
function tuple_from_truncated(::Type{T}, iter, ::Val{1}) where {T}
502+
T1 = fieldtype(T, 1)
503+
x1 = first(iter)
504+
y1 = convert(T1, x1)
505+
(y1,)
506+
end
507+
508+
function tuple_from_truncated(::Type{T}, iter, ::Val{2}) where {T}
509+
T1 = fieldtype(T, 1)
510+
T2 = fieldtype(T, 2)
511+
(x1, x2) = iter
512+
y1 = convert(T1, x1)
513+
y2 = convert(T2, x2)
514+
(y1, y2)
515+
end
516+
517+
tuple_from_constant_length_iterator(::Type{T}, ::ConstantLengthIterator1) where {T<:Tuple{} } = ()
518+
tuple_from_constant_length_iterator(::Type{T}, i::ConstantLengthIterator1) where {T<:Tuple } = tuple_from_truncated(T, i, Val(1))
519+
tuple_from_constant_length_iterator(::Type{T}, ::ConstantLengthIterator1) where {T<:Tuple2OrMore} = _totuple_err(T)
520+
tuple_from_constant_length_iterator(::Type{T}, ::ConstantLengthIterator2) where {T<:Tuple{} } = ()
521+
tuple_from_constant_length_iterator(::Type{T}, i::ConstantLengthIterator2) where {T<:Tuple{Any} } = tuple_from_truncated(T, i, Val(1))
522+
tuple_from_constant_length_iterator(::Type{T}, i::ConstantLengthIterator2) where {T<:Tuple } = tuple_from_truncated(T, i, Val(2))
523+
tuple_from_constant_length_iterator(::Type{T}, ::ConstantLengthIterator2) where {T<:Tuple3OrMore} = _totuple_err(T)
524+
525+
function (::Type{T})(x::ConstantLengthIterator) where {T<:Tuple}
526+
tuple_from_constant_length_iterator(T, x)
527+
end
497528

498529
end
499530

test/core.jl

-2
Original file line numberDiff line numberDiff line change
@@ -6879,8 +6879,6 @@ let a = Foo17149()
68796879
end
68806880

68816881
# issue #21004
6882-
const PTuple_21004{N,T} = NTuple{N,VecElement{T}}
6883-
@test_throws ArgumentError("too few elements for tuple type $PTuple_21004") PTuple_21004(1)
68846882
@test_throws UndefVarError(:T, :static_parameter) PTuple_21004_2{N,T} = NTuple{N, VecElement{T}}(1)
68856883

68866884
#issue #22792

test/tuple.jl

+174
Original file line numberDiff line numberDiff line change
@@ -818,3 +818,177 @@ namedtup = (;a=1, b=2, c=3)
818818
@test (1, "2") == @inferred Tuple(pair)
819819
@test (1, "2") == @inferred Tuple{Int,String}(pair)
820820
end
821+
822+
@testset "from constant-length iterators, issue #52993" begin
823+
allocs = function(::Type{T}, iter) where {T}
824+
@allocated T(iter)
825+
end
826+
827+
@testset "conversion" begin
828+
@testset "length 1" begin
829+
iters = (7::Number, Ref(7)::Ref, fill(7)::AbstractArray{<:Any,0})
830+
831+
@testset "iter: $iter" for iter iters
832+
types = (
833+
Tuple{Float64}, Tuple{AbstractFloat},Tuple{Float64,Vararg{Float64}},
834+
Tuple{Vararg{Float64}}, Tuple{Vararg{AbstractFloat}},
835+
)
836+
@testset "T: $T" for T types
837+
@test (7.0,) === @inferred T(iter)
838+
@test iszero(allocs(T, iter))
839+
end
840+
end
841+
end
842+
843+
@testset "length 2" begin
844+
iters = ((10 => 20)::Pair,)
845+
846+
@testset "iter: $iter" for iter iters
847+
types = (
848+
Tuple{Float64,Float64}, Tuple{AbstractFloat,AbstractFloat},
849+
Tuple{Float64,AbstractFloat}, Tuple{AbstractFloat,Float64},
850+
Tuple{Vararg{Float64}}, Tuple{Vararg{AbstractFloat}},
851+
Tuple{Float64,Vararg{Float64}}, Tuple{Float64,Vararg{AbstractFloat}}
852+
)
853+
@testset "T: $T" for T types
854+
@test (10.0, 20.0) === @inferred T(iter)
855+
@test iszero(allocs(T, iter))
856+
end
857+
end
858+
end
859+
end
860+
861+
types_length_0 = (Tuple{},)
862+
types_length_1 = (
863+
Tuple{Int}, Tuple{Number}, Tuple{Any}, (Tuple{T} where {T<:Number}),
864+
(Tuple{T} where {Number<:T<:Any}), (Tuple{T} where {Int<:T<:Number})
865+
)
866+
types_length_2 = (
867+
Tuple{Int,Int}, Tuple{Number,Number}, Tuple{Number,Any}, Tuple{Number,Int},
868+
Tuple{Any,Any}, (Tuple{Number, T} where {Int<:T<:Number}),
869+
(Tuple{T, T} where {Int<:T<:Number}), (Tuple{T, T} where {T<:Number})
870+
)
871+
types_length_3 = (
872+
Tuple{Int,Int,Int}, Tuple{Number,Number,Number}, Tuple{Number,Any,Int},
873+
Tuple{Any,Any,Any}, (Tuple{T,T,T} where {T}),
874+
(Tuple{T,T,T} where {Number<:T<:Any}), (Tuple{Number,T,T} where {T<:Number})
875+
)
876+
types_length_4 = (
877+
Tuple{Int,Int,Int,Int}, Tuple{Any,Number,Int,Any}, (Tuple{T,T,T,T} where {T}),
878+
(Tuple{T,T,T,T} where {Int<:T<:Number}), (Tuple{Int,T,T,T} where {T})
879+
)
880+
881+
types_vararg_0 = (
882+
Tuple, Tuple{Vararg{Int}}, Tuple{Vararg{Number}}, (Tuple{Vararg{T}} where {T}),
883+
(Tuple{Vararg{T}} where {T<:Number}), (Tuple{Vararg{T}} where {T<:Int}),
884+
(Tuple{Vararg{T}} where {Number<:T<:Any}),
885+
(Tuple{Vararg{T}} where {Int<:T<:Number})
886+
)
887+
types_vararg_1 = (
888+
Tuple{Number,Vararg{Number}}, Tuple{Number,Vararg{Any}}, Tuple{Int,Vararg{Int}},
889+
(Tuple{Int,Vararg{T}} where {T}), (Tuple{Int,Vararg{T}} where {Int<:T<:Number})
890+
)
891+
types_vararg_2 = (
892+
Tuple{Any,Any,Vararg{Any}}, Tuple{Int,Int,Vararg{Number}},
893+
Tuple{Int,Int,Vararg{Int}}, (Tuple{Int,Number,Vararg{T}} where {T}),
894+
(Tuple{Int,Number,Vararg{T}} where {T}),
895+
(Tuple{Int,Number,Vararg{T}} where {T<:Number}),
896+
(Tuple{Int,Number,Vararg{T}} where {Int, T<:Number})
897+
)
898+
types_vararg_3 = (
899+
Tuple{Any,Any,Any,Vararg{Any}}, Tuple{Number,Int,Any,Vararg{Number}},
900+
Tuple{Int,Int,Int,Vararg{Int}}, (Tuple{T,Number,Any,Vararg{T}} where {T}),
901+
(Tuple{T,Number,Any,Vararg{T}} where {T<:Number}),
902+
(Tuple{T,Number,Any,Vararg{T}} where {Int<:T<:Number})
903+
)
904+
types_vararg_4 = (
905+
Tuple{Any,Number,Int,Number,Vararg{Number}},
906+
(Tuple{Any,Number,Int,Number,Vararg{T}} where {T}),
907+
(Tuple{Any,Number,T,Number,Vararg{T}} where {Int<:T<:Number})
908+
)
909+
910+
@testset "length 1" begin
911+
types_ok_0 = types_length_0
912+
types_ok_1 = (
913+
types_length_1..., types_vararg_0..., types_vararg_1...
914+
)
915+
types_throws = (
916+
types_length_2..., types_length_3..., types_length_4...,
917+
types_vararg_2..., types_vararg_3..., types_vararg_4...
918+
)
919+
iters = (7::Number, Ref(7)::Ref, fill(7)::AbstractArray{<:Any,0})
920+
921+
@testset "iter: $iter" for iter iters
922+
@testset "OK 0" begin
923+
@testset "T: $T" for T types_ok_0
924+
@test () === @inferred T(iter)
925+
@test iszero(allocs(T, iter))
926+
end
927+
end
928+
929+
@testset "OK 1" begin
930+
@testset "T: $T" for T types_ok_1
931+
@test (7,) === @inferred T(iter)
932+
@test iszero(allocs(T, iter))
933+
end
934+
end
935+
936+
@testset "throws `ArgumentError`" begin
937+
@testset "T: $T" for T types_throws
938+
@test_throws ArgumentError T(iter)
939+
end
940+
end
941+
942+
@testset "throws" begin
943+
@test_throws Exception Tuple{Nothing}(iter)
944+
@test_throws Exception Tuple{Matrix}(iter)
945+
end
946+
end
947+
end
948+
949+
@testset "length 2" begin
950+
types_ok_0 = types_length_0
951+
types_ok_1 = types_length_1
952+
types_ok_2 = (
953+
types_length_2..., types_vararg_0..., types_vararg_1..., types_vararg_2...
954+
)
955+
types_throws = (
956+
types_length_3..., types_length_4..., types_vararg_3..., types_vararg_4...
957+
)
958+
iters = ((10 => 20)::Pair,)
959+
960+
@testset "iter: $iter" for iter iters
961+
@testset "OK 0" begin
962+
@testset "T: $T" for T types_ok_0
963+
@test () === @inferred T(iter)
964+
@test iszero(allocs(T, iter))
965+
end
966+
end
967+
968+
@testset "OK 1" begin
969+
@testset "T: $T" for T types_ok_1
970+
@test (10,) === @inferred T(iter)
971+
@test iszero(allocs(T, iter))
972+
end
973+
end
974+
975+
@testset "OK 2" begin
976+
@testset "T: $T" for T types_ok_2
977+
@test (10, 20) === @inferred T(iter)
978+
@test iszero(allocs(T, iter))
979+
end
980+
end
981+
982+
@testset "throws `ArgumentError`" begin
983+
@testset "T: $T" for T types_throws
984+
@test_throws ArgumentError T(iter)
985+
end
986+
end
987+
988+
@testset "throws" begin
989+
@test_throws Exception Tuple{Nothing,Any}(iter)
990+
@test_throws Exception Tuple{Matrix,Any}(iter)
991+
end
992+
end
993+
end
994+
end

0 commit comments

Comments
 (0)